From bb0d6e29938ecbc70fa994e31980d69b8904ab8d Mon Sep 17 00:00:00 2001
From: phroi <90913182+phroi@users.noreply.github.com>
Date: Thu, 26 Feb 2026 22:34:25 +0000
Subject: [PATCH 1/5] docs: add planning docs for CCC UDT adoption refactor
Add phase 04 (CCC UDT investigation) and phase 05 (iCKB core UDT
refactor) planning documents including research, context, plans,
execution summaries, and verification reports.
Update PROJECT.md, REQUIREMENTS.md, ROADMAP.md, and STATE.md to
reflect the new milestone scope and phase progression.
---
.planning/PROJECT.md | 9 +-
.planning/REQUIREMENTS.md | 20 +-
.planning/ROADMAP.md | 55 +-
.planning/STATE.md | 69 +-
.../03-DECISION.md | 52 +-
.../04-01-PLAN.md | 214 +++++++
.../04-01-SUMMARY.md | 104 ++++
.../04-CONTEXT.md | 65 ++
.../04-RESEARCH.md | 365 +++++++++++
.../04-VERIFICATION.md | 120 ++++
.../05-ickb-core-udt-refactor/05-01-PLAN.md | 497 +++++++++++++++
.../05-01-SUMMARY.md | 120 ++++
.../05-ickb-core-udt-refactor/05-02-PLAN.md | 418 +++++++++++++
.../05-02-SUMMARY.md | 105 ++++
.../05-ickb-core-udt-refactor/05-CONTEXT.md | 84 +++
.../05-ickb-core-udt-refactor/05-RESEARCH.md | 589 ++++++++++++++++++
.../05-VERIFICATION.md | 102 +++
17 files changed, 2913 insertions(+), 75 deletions(-)
create mode 100644 .planning/phases/04-deprecated-ccc-api-replacement/04-01-PLAN.md
create mode 100644 .planning/phases/04-deprecated-ccc-api-replacement/04-01-SUMMARY.md
create mode 100644 .planning/phases/04-deprecated-ccc-api-replacement/04-CONTEXT.md
create mode 100644 .planning/phases/04-deprecated-ccc-api-replacement/04-RESEARCH.md
create mode 100644 .planning/phases/04-deprecated-ccc-api-replacement/04-VERIFICATION.md
create mode 100644 .planning/phases/05-ickb-core-udt-refactor/05-01-PLAN.md
create mode 100644 .planning/phases/05-ickb-core-udt-refactor/05-01-SUMMARY.md
create mode 100644 .planning/phases/05-ickb-core-udt-refactor/05-02-PLAN.md
create mode 100644 .planning/phases/05-ickb-core-udt-refactor/05-02-SUMMARY.md
create mode 100644 .planning/phases/05-ickb-core-udt-refactor/05-CONTEXT.md
create mode 100644 .planning/phases/05-ickb-core-udt-refactor/05-RESEARCH.md
create mode 100644 .planning/phases/05-ickb-core-udt-refactor/05-VERIFICATION.md
diff --git a/.planning/PROJECT.md b/.planning/PROJECT.md
index 3659620..e1222ed 100644
--- a/.planning/PROJECT.md
+++ b/.planning/PROJECT.md
@@ -24,7 +24,7 @@ Clean, CCC-aligned library packages published to npm that frontends can depend o
### Active
- [ ] Remove SmartTransaction — replace with `ccc.Transaction` + utility functions
-- [ ] Adopt CCC UDT handling — investigate subclassing `Udt` for iCKB's multi-representation value (xUDT + receipts + deposits)
+- [ ] Adopt CCC UDT handling — `IckbUdt extends udt.Udt` decided (Phase 3); managers get plain `ccc.Script` not `udt.Udt` instances (Phase 4); implementation in Phase 5
- [ ] Systematic CCC alignment audit — replace local utilities with CCC equivalents from merged upstream PRs
- [ ] Migrate bot app from Lumos to CCC + new packages
- [ ] Migrate interface app from Lumos to CCC + new packages (straight swap, same UI)
@@ -73,10 +73,13 @@ Clean, CCC-aligned library packages published to npm that frontends can depend o
| Decision | Rationale | Outcome |
|----------|-----------|---------|
| Remove SmartTransaction, use ccc.Transaction directly | SmartTransaction concept abandoned by ecosystem, no adoption from CCC maintainers | — Pending |
-| Investigate CCC Udt subclassing for iCKB | iCKB value is multi-representation (xUDT + receipts + deposits); need to determine if CCC's Udt can be extended | — Pending |
+| Subclass CCC Udt for iCKB | iCKB value is multi-representation (xUDT + receipts + deposits); Phase 3 confirmed `IckbUdt extends udt.Udt` with `infoFrom` override is feasible and chosen | Decided (Phase 3) |
+| IckbUdt uses individual code deps | CCC author dislikes dep groups for breaking semantics; IckbUdt overrides `addCellDeps` with xUDT + Logic code OutPoints; other managers keep dep groups for now | Decided (Phase 5 context) |
+| Drop compressState, accept CCC errors | `completeInputsByBalance` replaces `completeUdt`; CCC's `ErrorUdtInsufficientCoin` replaces custom error class; callers format messages | Decided (Phase 5 context) |
+| Managers get plain ccc.Script only | LogicManager/OwnedOwnerManager udtHandler removed (Phase 5), matching OrderManager pattern (Phase 4); udt.Udt instance lives at SDK level | Decided (Phase 5 context) |
| Library refactor before app migration | Clean packages first, then migrate apps on stable foundation | — Pending |
| Interface app: straight migration only | No UI/UX redesign — swap Lumos internals for CCC packages | — Pending |
| Track CCC PR #328 (FeePayer) | Could become CCC-native solution for what SmartTransaction does for fee completion | — Pending |
---
-*Last updated: 2026-02-20 after initialization*
+*Last updated: 2026-02-26 after Phase 5 context (code deps for IckbUdt, manager udtHandler removal, compressState dropped, CCC error adoption)*
diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md
index 9a68233..ba36c29 100644
--- a/.planning/REQUIREMENTS.md
+++ b/.planning/REQUIREMENTS.md
@@ -15,10 +15,10 @@ Requirements for initial milestone. Each maps to roadmap phases.
- [x] **SMTX-04**: `getHeader()` function and `HeaderKey` type are removed from `@ickb/utils`; all call sites inline CCC client calls (`client.getTransactionWithHeader()`, `client.getHeaderByNumber()`); header caching handled transparently by `ccc.Client.cache`
- [x] **SMTX-05**: UDT handler registration (`addUdtHandlers()`) is replaced by direct `Udt` instance usage or standalone utility functions
- [x] **SMTX-06**: 64-output NervosDAO limit check is consolidated into a single utility function (currently scattered across 6 locations)
-- [ ] **SMTX-07**: `IckbUdtManager` multi-representation UDT balance logic (xUDT + receipts + deposits) survives removal intact -- conservation law `Input UDT + Input Receipts = Output UDT + Input Deposits` is preserved
+- [x] **SMTX-07**: `IckbUdtManager` multi-representation UDT balance logic (xUDT + receipts + deposits) survives removal intact -- conservation law `Input UDT + Input Receipts = Output UDT + Input Deposits` preserved via accurate balance reporting in `IckbUdt.infoFrom` (on-chain script is authoritative enforcer)
- [ ] **SMTX-08**: `IckbSdk.estimate()` and `IckbSdk.maturity()` continue working after SmartTransaction removal
- [ ] **SMTX-09**: All 5 library packages (`@ickb/utils`, `@ickb/dao`, `@ickb/order`, `@ickb/core`, `@ickb/sdk`) compile and pass type checking after removal
-- [ ] **SMTX-10**: Deprecated CCC API calls (`udtBalanceFrom`, `getInputsUdtBalance`, `getOutputsUdtBalance`, `completeInputsByUdt`) are replaced with `@ckb-ccc/udt` equivalents
+- [x] **SMTX-10**: Deprecated CCC API calls (`udtBalanceFrom`, `getInputsUdtBalance`, `getOutputsUdtBalance`, `completeInputsByUdt`) are replaced with `@ckb-ccc/udt` equivalents
### CCC Utility Deduplication
@@ -33,8 +33,8 @@ Requirements for initial milestone. Each maps to roadmap phases.
- [x] **UDT-01**: Feasibility assessment completed: can `IckbUdt extends udt.Udt` override `infoFrom()` or `getInputsInfo()`/`getOutputsInfo()` to account for receipt cells and deposit cells alongside xUDT cells
- [x] **UDT-02**: Header access pattern for receipt value calculation is designed -- determine whether `client.getCellWithHeader()`, `client.getTransactionWithHeader()`, or direct CCC client calls are used within the Udt override (`getHeader()` utility removed in Phase 1)
- [x] **UDT-03**: Decision documented: subclass CCC `Udt` vs. keep custom `UdtHandler` interface vs. hybrid approach
-- [ ] **UDT-04**: If subclassing is viable, `IckbUdt` class is implemented in `@ickb/core` with multi-representation balance calculation
-- [ ] **UDT-05**: If subclassing is not viable, `IckbUdtManager` is refactored to work with plain `ccc.Transaction` (no SmartTransaction dependency) while maintaining a compatible interface
+- [x] **UDT-04**: `IckbUdt extends udt.Udt` implemented in `@ickb/core` with `infoFrom` override for multi-representation balance; individual code deps via overridden `addCellDeps`; `typeScriptFrom` static method; LogicManager/OwnedOwnerManager `udtHandler` removed
+- [x] **UDT-05**: N/A -- Phase 3 confirmed subclassing IS viable (option a chosen)
## v2 Requirements
@@ -85,12 +85,12 @@ Which phases cover which requirements. Updated during roadmap creation.
| SMTX-02 | Phase 1 | Complete | SmartTransaction class deleted from @ickb/utils (01-03) |
| SMTX-03 | Phase 6 | Pending | |
| SMTX-04 | Phase 1 | Complete | getHeader()/HeaderKey removed, CCC client calls inlined |
-| SMTX-05 | Phase 1, 4, 5 | Complete | addUdtHandlers() replaced with tx.addCellDeps(udtHandler.cellDeps) (01-03); UdtHandler/UdtManager replacement deferred to Phase 4-5 |
+| SMTX-05 | Phase 1, 4, 5 | In Progress | addUdtHandlers() replaced with tx.addCellDeps(udtHandler.cellDeps) (01-03); Phase 4: OrderManager.udtHandler→udtScript; Phase 5: LogicManager/OwnedOwnerManager.udtHandler removed + UdtHandler/UdtManager/ErrorTransactionInsufficientCoin/findUdts/UdtCell deleted from utils |
| SMTX-06 | Phase 1 | Complete | DAO check contributed to CCC core via forks/ccc/ (01-01) |
-| SMTX-07 | Phase 5 | Pending | |
+| SMTX-07 | Phase 5 | Complete | |
| SMTX-08 | Phase 6 | Pending | |
| SMTX-09 | Phase 7 | Pending | |
-| SMTX-10 | Phase 4, 5 | Pending | Deprecated calls in dao/order (Phase 4) and core (Phase 5) |
+| SMTX-10 | Phase 5 | Complete | Deprecated ccc.udtBalanceFrom() calls are in utils (UdtManager) and core (IckbUdtManager), not dao/order. All replaced when UdtManager deleted and IckbUdt implemented in Phase 5 |
| DEDUP-01 | Phase 2 | Complete | max()/min() replaced with Math.max()/Math.min() for number-typed contexts, local deleted (02-01) |
| DEDUP-02 | Phase 2 | Complete | gcd() replaced with ccc.gcd(), local deleted (02-01) |
| DEDUP-03 | Phase 2 | Complete | isHex() deleted, only used internally by deleted hexFrom() (02-01) |
@@ -99,8 +99,8 @@ Which phases cover which requirements. Updated during roadmap creation.
| UDT-01 | Phase 3 | Complete | |
| UDT-02 | Phase 3 | Complete | |
| UDT-03 | Phase 3 | Complete | |
-| UDT-04 | Phase 5 | Pending | |
-| UDT-05 | Phase 5 | Pending | |
+| UDT-04 | Phase 5 | Complete | IckbUdt with infoFrom override, code deps, typeScriptFrom, manager cleanup |
+| UDT-05 | Phase 3 | N/A | Subclassing confirmed viable — option (a) chosen |
**Coverage:**
- v1 requirements: 20 total
@@ -109,4 +109,4 @@ Which phases cover which requirements. Updated during roadmap creation.
---
*Requirements defined: 2026-02-21*
-*Last updated: 2026-02-24 after Phase 3 completion (UDT-01 through UDT-03 completed; Phase 3 complete)*
+*Last updated: 2026-02-26 after Phase 5 context (UDT-05 N/A — subclassing viable; SMTX-05 expanded with LogicManager/OwnedOwnerManager cleanup; UDT-04 detailed with code deps, typeScriptFrom, manager cleanup)*
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 27dc965..79373c3 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -15,8 +15,8 @@ Decimal phases appear between their surrounding integers in numeric order.
- [x] **Phase 1: SmartTransaction Removal (feature-slice)** - Delete SmartTransaction class and infrastructure across all packages; contribute 64-output DAO limit check to CCC core; migrate all method signatures to ccc.TransactionLike
- [x] **Phase 2: CCC Utility Adoption** - Replace local utility functions that duplicate CCC equivalents across all packages; preserve iCKB-unique utilities
- [x] **Phase 3: CCC Udt Integration Investigation** - Assess feasibility of subclassing CCC's Udt class for iCKB's multi-representation value; design header access pattern; document decision
-- [ ] **Phase 4: Deprecated CCC API Replacement** - Replace deprecated CCC API calls (`udtBalanceFrom`, etc.) with `@ckb-ccc/udt` equivalents in dao and order packages; finalize UDT handler replacement pattern based on Phase 3 findings
-- [ ] **Phase 5: @ickb/core UDT Refactor** - Implement IckbUdt class or refactor IckbUdtManager based on Phase 3 findings; preserve iCKB conservation law; replace deprecated CCC API calls in core
+- [x] **Phase 4: Deprecated CCC API Replacement** - Replace UdtHandler dependency in `@ickb/order` with plain `ccc.Script`; remove UDT cellDeps management from OrderManager; correct Phase 3 docs; verify `@ickb/dao` already clean
+- [ ] **Phase 5: @ickb/core UDT Refactor** - Implement `IckbUdt extends udt.Udt` with `infoFrom` override; remove `udtHandler` from LogicManager/OwnedOwnerManager; delete UdtHandler/UdtManager/ErrorTransactionInsufficientCoin/findUdts from utils; adopt individual code deps for IckbUdt; update SDK construction and error handling
- [ ] **Phase 6: SDK Completion Pipeline** - Wire IckbSdk facade to CCC-native fee completion; verify estimate() and maturity() work end-to-end
- [ ] **Phase 7: Full Stack Verification** - Verify all 5 library packages compile clean with no SmartTransaction remnants and no type errors
@@ -70,36 +70,39 @@ Plans:
- [x] 03-02-PLAN.md — Write formal decision document (feasibility assessment, header access pattern, decision with rationale)
### Phase 4: Deprecated CCC API Replacement
-**Goal**: Deprecated CCC API calls are replaced with `@ckb-ccc/udt` equivalents in `@ickb/dao` and `@ickb/order`; UDT handler usage is finalized based on Phase 3 findings (method signatures and `addUdtHandlers()` removal already done in Phase 1)
-**Depends on**: Phase 1 (signatures migrated), Phase 3 (UDT decision — determines replacement pattern for UdtHandler usage)
-**Requirements**: SMTX-05, SMTX-10
+**Goal**: `UdtHandler` dependency in `@ickb/order` is replaced with plain `ccc.Script` (`udtScript`); UDT cellDeps management removed from OrderManager (caller/CCC Udt handles externally during balance completion); `@ickb/dao` verified clean (no UdtHandler, no deprecated APIs); Phase 3 decision doc corrected to match actual codebase state
+**Depends on**: Phase 1 (signatures migrated), Phase 3 (UDT decision)
+**Requirements**: SMTX-05
**Success Criteria** (what must be TRUE):
- 1. No calls to deprecated CCC APIs (`udtBalanceFrom`, `getInputsUdtBalance`, `getOutputsUdtBalance`, `completeInputsByUdt`) exist in `@ickb/dao` or `@ickb/order`
- 2. UDT-related operations in `@ickb/dao` and `@ickb/order` use the pattern chosen in Phase 3 (direct `Udt` instance methods, refactored UdtManager, or hybrid)
- 3. Both `@ickb/dao` and `@ickb/order` compile successfully
-**Plans**: TBD
+ 1. `OrderManager` constructor accepts `udtScript: ccc.Script` instead of `udtHandler: UdtHandler` -- all 9 `this.udtHandler.script` references replaced with `this.udtScript`
+ 2. All `tx.addCellDeps(this.udtHandler.cellDeps)` calls removed from `mint()`, `addMatch()`, `melt()` -- UDT cellDeps are caller responsibility
+ 3. `@ickb/order` no longer imports `UdtHandler` from `@ickb/utils`; no new `@ckb-ccc/udt` dependency added
+ 4. `@ickb/dao` has no UdtHandler references and no deprecated CCC API calls (verified, no changes needed)
+ 5. Phase 3 decision doc `03-DECISION.md` "Implementation Guidance for Phase 4" section corrected
+ 6. `pnpm check:full` passes
+**Plans**: 1 plan
Plans:
-- [ ] 04-01: TBD
-- [ ] 04-02: TBD
+- [x] 04-01-PLAN.md — Replace OrderManager udtHandler with udtScript: ccc.Script, remove UDT cellDeps, update SDK caller, verify dao and decision doc clean
### Phase 5: @ickb/core UDT Refactor
-**Goal**: IckbUdt class is implemented or IckbUdtManager is refactored based on Phase 3 findings; the iCKB conservation law is preserved through the refactor; deprecated CCC API calls are replaced in `@ickb/core`; UdtHandler/UdtManager are removed from `@ickb/utils` (manager method signatures already migrated to `ccc.TransactionLike` in Phase 1)
-**Depends on**: Phase 3 (UDT decision), Phase 4 (dao+order UDT pattern finalized)
-**Requirements**: SMTX-05, SMTX-07, SMTX-10, UDT-04, UDT-05
+**Goal**: `IckbUdt extends udt.Udt` implemented in `@ickb/core` with `infoFrom` override for multi-representation balance; iCKB conservation law preserved via accurate balance reporting; `udtHandler` removed from LogicManager/OwnedOwnerManager (Phase 4 pattern); `UdtHandler`, `UdtManager`, `ErrorTransactionInsufficientCoin`, `UdtCell`, `findUdts`, `addUdts` deleted from `@ickb/utils`; IckbUdt uses individual code deps (not dep group) with overridden `addCellDeps`; SDK updated to construct `IckbUdt` and handle CCC's `ErrorUdtInsufficientCoin`. See `05-CONTEXT.md` for full decisions.
+**Depends on**: Phase 3 (UDT decision), Phase 4 (order UDT pattern finalized)
+**Requirements**: SMTX-05, SMTX-07, SMTX-10, UDT-04
**Success Criteria** (what must be TRUE):
- 1. The iCKB conservation law (`Input UDT + Input Receipts = Output UDT + Input Deposits`) is enforced correctly in the refactored code -- multi-representation UDT balance logic survives intact
- 2. If Phase 3 concluded subclassing is viable: `IckbUdt extends udt.Udt` exists in `@ickb/core` with overridden `infoFrom()` that accounts for xUDT cells, receipt cells, and deposit cells
- 3. If Phase 3 concluded subclassing is not viable: `IckbUdtManager` is refactored to work with plain `ccc.Transaction` while maintaining a compatible interface for balance calculation
- 4. `UdtHandler` interface and `UdtManager` class are removed from `@ickb/utils` (their responsibilities absorbed by the Phase 3 outcome implementation)
- 5. No calls to deprecated CCC APIs exist in `@ickb/core`
- 6. `@ickb/core` compiles successfully with no SmartTransaction imports
-**Plans**: TBD
+ 1. `IckbUdt extends udt.Udt` exists in `@ickb/core` with overridden `infoFrom()` that accurately values xUDT cells (positive), receipt cells (positive, input only), and deposit cells (negative, input only) -- conservation law preserved via correct sign conventions
+ 2. `IckbUdt.addCellDeps` overridden to add individual code deps (xUDT code OutPoint + iCKB Logic code OutPoint) instead of dep group
+ 3. `IckbUdtManager.calculateScript` renamed to `IckbUdt.typeScriptFrom` (static, same params)
+ 4. `LogicManager` and `OwnedOwnerManager` no longer take `udtHandler` parameter; `tx.addCellDeps(this.udtHandler.cellDeps)` calls removed (4 sites) -- UDT cellDeps are caller responsibility (Phase 4 pattern)
+ 5. `UdtHandler` interface, `UdtManager` class, `ErrorTransactionInsufficientCoin` class, `UdtCell` interface, `findUdts`, `addUdts`, `isUdtSymbol` deleted from `@ickb/utils`
+ 6. No calls to deprecated CCC APIs (`ccc.udtBalanceFrom`) exist in `@ickb/core` or `@ickb/utils`
+ 7. SDK constructs `IckbUdt` instead of `IckbUdtManager`; passes `ickbUdt.script` to managers; handles `ErrorUdtInsufficientCoin` from CCC (not old `ErrorTransactionInsufficientCoin`)
+ 8. `pnpm check:full` passes
+**Plans**: 2 plans
Plans:
-- [ ] 05-01: TBD
-- [ ] 05-02: TBD
-- [ ] 05-03: TBD
+- [ ] 05-01-PLAN.md — Implement IckbUdt extends udt.Udt in core (infoFrom, addCellDeps, typeScriptFrom); remove udtHandler from LogicManager/OwnedOwnerManager; add @ckb-ccc/udt dependency
+- [ ] 05-02-PLAN.md — Delete UDT infrastructure from utils (UdtHandler, UdtManager, ErrorTransactionInsufficientCoin, UdtCell, findUdts, addUdts); update SDK to construct IckbUdt with code OutPoints; verify pnpm check:full
### Phase 6: SDK Completion Pipeline
**Goal**: IckbSdk facade uses CCC-native fee completion pipeline; estimate() and maturity() continue working after SmartTransaction removal
@@ -140,7 +143,7 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7
| 1. SmartTransaction Removal (feature-slice) | 3/3 | Complete | 2026-02-22 |
| 2. CCC Utility Adoption | 1/1 | Complete | 2026-02-23 |
| 3. CCC Udt Integration Investigation | 2/2 | Complete | 2026-02-24 |
-| 4. Deprecated CCC API Replacement | 0/2 | Not started | - |
-| 5. @ickb/core UDT Refactor | 0/3 | Not started | - |
+| 4. Deprecated CCC API Replacement | 1/1 | Complete | 2026-02-26 |
+| 5. @ickb/core UDT Refactor | 0/2 | Not started | - |
| 6. SDK Completion Pipeline | 0/2 | Not started | - |
| 7. Full Stack Verification | 0/1 | Not started | - |
diff --git a/.planning/STATE.md b/.planning/STATE.md
index 2d2714f..1a8a8d3 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -1,3 +1,16 @@
+---
+gsd_state_version: 1.0
+milestone: v1.0
+milestone_name: milestone
+status: unknown
+last_updated: "2026-02-26T16:31:09.127Z"
+progress:
+ total_phases: 5
+ completed_phases: 5
+ total_plans: 9
+ completed_plans: 9
+---
+
# Project State
## Project Reference
@@ -5,23 +18,23 @@
See: .planning/PROJECT.md (updated 2026-02-20)
**Core value:** Clean, CCC-aligned library packages published to npm that frontends can depend on to interact with iCKB contracts -- no Lumos, no abandoned abstractions, no duplicated functionality with CCC.
-**Current focus:** Phase 4: Deprecated CCC API Replacement
+**Current focus:** Phase 5: @ickb/core UDT Refactor
## Current Position
-Phase: 3 of 7 (CCC Udt Integration Investigation) -- COMPLETE
-Plan: 2 of 2 in current phase (all plans complete)
-Status: Phase 3 complete, ready for Phase 4
-Last activity: 2026-02-24 -- Plan 03-02 decision document complete (execute-phase)
+Phase: 5 of 7 (@ickb/core UDT Refactor) -- Complete
+Plan: 2 of 2 in current phase (all complete)
+Status: Phase 5 complete -- all UDT infrastructure deleted, SDK rewired
+Last activity: 2026-02-26 -- Completed 05-02-PLAN.md (delete UDT infra + wire SDK)
-Progress: [████░░░░░░] 43%
+Progress: [██████████] 100%
## Performance Metrics
**Velocity:**
-- Total plans completed: 6
-- Average duration: ~12min
-- Total execution time: 1.2 hours
+- Total plans completed: 9
+- Average duration: ~10min
+- Total execution time: 1.5 hours
**By Phase:**
@@ -30,10 +43,12 @@ Progress: [████░░░░░░] 43%
| 01 | 3/3 | 52min | 17min |
| 02 | 1/1 | 7min | 7min |
| 03 | 2/2 | 9min | 4.5min |
+| 04 | 1/1 | 4min | 4min |
+| 05 | 2/2 | 11min | 5.5min |
**Recent Trend:**
-- Last 5 plans: 01-03 (~16min), 02-01 (~7min), 03-01 (~5min), 03-02 (~4min)
-- Trend: accelerating
+- Last 5 plans: 03-02 (~4min), 04-01 (~4min), 05-01 (~7min), 05-02 (~4min)
+- Trend: stable at ~5min per plan
*Updated after each plan completion*
@@ -70,6 +85,31 @@ Recent decisions affecting current work:
- [03-02]: Conservation law: accurate balance reporting only; on-chain script is authoritative enforcer; build-time validation optional later
- [03-02]: Cell discovery boundary: infoFrom values cells already in transaction; callers (LogicManager/OwnedOwnerManager) find and add receipt/deposit cells
- [03-02]: UdtHandler interface and UdtManager class to be deleted in Phase 5, replaced by udt.Udt type
+- [04-context]: DaoManager never had UdtHandler parameter -- @ickb/dao has no UDT-related code, no changes needed in Phase 4
+- [04-context]: OrderManager gets udtScript: ccc.Script (not udt.Udt) -- simpler pattern than Phase 3 anticipated
+- [04-context]: UDT cellDeps removed from OrderManager -- caller/CCC Udt manages externally during balance completion
+- [04-context]: No deprecated CCC API calls in dao or order packages -- those calls (ccc.udtBalanceFrom) are in utils (UdtManager) and core (IckbUdtManager), handled in Phase 5
+- [04-context]: Pattern established: managers receive plain ccc.Script for UDT type identification; udt.Udt instance lives at SDK/caller level
+- [04-context]: Phase 3 decision doc corrected (DaoManager row removed, OrderManager updated, Phase 4 guidance rewritten)
+- [04-01]: OrderManager receives ccc.Script (not udt.Udt) -- simpler than Phase 3 anticipated
+- [04-01]: UDT cellDeps are caller responsibility -- documented via JSDoc @remarks on mint/addMatch/melt
+- [05-context]: IckbUdt uses individual code deps (xUDT + Logic OutPoints), not dep group -- CCC author dislikes dep groups for breaking semantics
+- [05-context]: Override addCellDeps on IckbUdt to add both code deps; other managers keep dep groups for now (mixed coexistence OK)
+- [05-context]: Drop compressState feature -- CCC completeInputsByBalance handles completion; no wrapper needed
+- [05-context]: Accept CCC's ErrorUdtInsufficientCoin; delete ErrorTransactionInsufficientCoin from utils; callers format messages
+- [05-context]: Delete findUdts/addUdts/UdtCell/isUdtSymbol -- all internal to UdtManager, no external consumers
+- [05-context]: LogicManager/OwnedOwnerManager: remove udtHandler param + cellDeps calls (Phase 4 pattern); UdtHandler interface has zero consumers → delete
+- [05-context]: calculateScript renamed to IckbUdt.typeScriptFrom (static, same params)
+- [05-context]: ValueComponents kept as-is for Phase 5; redesign deferred (ambiguous naming in multi-UDT context)
+- [05-context]: Phase 5 handles SDK error handling updates (not deferred to Phase 6)
+- [05-context]: CCC isUdt length check (>=16 bytes) equivalent to old >=34 hex chars -- no iCKB-specific reason for old threshold
+- [05-context]: Matching bot infoFrom caching concern noted for researcher -- CCC Client.cache handles header dedup
+- [05-01]: IckbUdt.infoFrom handles three cell types: xUDT (positive), receipt (positive via ickbValue), deposit (negative via ickbValue)
+- [05-01]: addCellDeps adds individual code deps (xUDT + Logic OutPoints), not dep group
+- [05-01]: CellAny cast to Cell for daoManager.isDeposit is safe: input cells are Cell instances, output cells gated by !cell.outPoint check
+- [05-02]: IckbUdt.typeScriptFrom computes type script dynamically from raw UDT and Logic scripts (not hardcoded)
+- [05-02]: Devnet code OutPoints fallback to cellDeps[0].outPoint -- pragmatic since devnet deps are typically depType: code
+- [05-02]: ErrorTransactionInsufficientCoin had zero catch blocks across entire codebase -- clean deletion with no migration
### Pending Todos
@@ -85,6 +125,7 @@ None yet.
## Session Continuity
-Last session: 2026-02-24
-Stopped at: Completed 03-02-PLAN.md (Phase 3 complete)
-Resume file: Phase 4 planning needed
+Last session: 2026-02-26
+Stopped at: Completed 05-02-PLAN.md -- Phase 5 complete
+Resume file: N/A (all phases complete)
+Next action: PR preparation or Phase 6 planning
diff --git a/.planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md b/.planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md
index 94048c4..9bf35b9 100644
--- a/.planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md
+++ b/.planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md
@@ -260,15 +260,17 @@ This code sketch is derived from:
| Current | Replacement | Package |
|---------|-------------|---------|
-| `UdtHandler` interface | Deleted -- replaced by `udt.Udt` instance | `@ickb/utils` |
-| `UdtManager` class | Deleted -- replaced by `udt.Udt` base class | `@ickb/utils` |
-| `IckbUdtManager` class | `IckbUdt extends udt.Udt` | `@ickb/core` |
-| `DaoManager` constructor `udtHandler` param | `DaoManager` receives `udt.Udt` instance | `@ickb/dao` |
-| `OrderManager` constructor `udtHandler` param | `OrderManager` receives `udt.Udt` instance | `@ickb/order` |
-| `tx.getInputsUdtBalance()` (deprecated) | `ickbUdt.getInputsInfo(client, tx)` | All packages |
-| `tx.getOutputsUdtBalance()` (deprecated) | `ickbUdt.getOutputsInfo(client, tx)` | All packages |
-| `tx.completeInputsByUdt()` (deprecated) | `ickbUdt.completeInputsByBalance(tx, signer)` | All packages |
-| `ccc.udtBalanceFrom()` (deprecated) | `udt.Udt.balanceFromUnsafe(outputData)` | All packages |
+| `UdtHandler` interface | Deleted (Phase 5) | `@ickb/utils` |
+| `UdtManager` class | Deleted (Phase 5) | `@ickb/utils` |
+| `IckbUdtManager` class | `IckbUdt extends udt.Udt` (Phase 5) | `@ickb/core` |
+| `OrderManager` constructor `udtHandler: UdtHandler` | `udtScript: ccc.Script` (Phase 4) | `@ickb/order` |
+| `tx.addCellDeps(this.udtHandler.cellDeps)` in OrderManager | Removed -- caller/CCC Udt handles UDT cellDeps (Phase 4) | `@ickb/order` |
+| `tx.getInputsUdtBalance()` (deprecated) | `ickbUdt.getInputsInfo(client, tx)` (Phase 5) | `@ickb/core` |
+| `tx.getOutputsUdtBalance()` (deprecated) | `ickbUdt.getOutputsInfo(client, tx)` (Phase 5) | `@ickb/core` |
+| `tx.completeInputsByUdt()` (deprecated) | `ickbUdt.completeInputsByBalance(tx, signer)` (Phase 5) | `@ickb/core` |
+| `ccc.udtBalanceFrom()` (deprecated) | `udt.Udt.balanceFromUnsafe(outputData)` (Phase 5) | `@ickb/utils`, `@ickb/core` |
+
+**Note:** `DaoManager` never had a `UdtHandler` parameter -- it only manages DAO operations (deposit, withdrawal request, withdraw). No UDT-related changes needed in `@ickb/dao`.
### What CCC Features It Gains
@@ -366,24 +368,29 @@ The correct pattern is:
## Implementation Guidance for Phases 4-5
-### Phase 4: dao and order Packages (Deprecated API Replacement)
+### Phase 4: order Package (UdtHandler Replacement)
-Replace deprecated CCC API calls with `udt.Udt` instance methods:
+*Updated 2026-02-26 based on Phase 4 discuss-phase. See `04-CONTEXT.md` for full decisions.*
-| Deprecated API | Replacement | Notes |
-|----------------|-------------|-------|
-| `ccc.udtBalanceFrom(outputData)` | `udt.Udt.balanceFromUnsafe(outputData)` | Static method on Udt class |
-| `tx.getInputsUdtBalance(client)` | `ickbUdt.getInputsInfo(client, tx)` | Returns `UdtInfo` not `[FixedPoint, FixedPoint]` |
-| `tx.getOutputsUdtBalance(client)` | `ickbUdt.getOutputsInfo(client, tx)` | Returns `UdtInfo` not `[FixedPoint, FixedPoint]` |
-| `tx.completeInputsByUdt(signer, handler)` | `ickbUdt.completeInputsByBalance(tx, signer)` | On Udt instance, not Transaction |
+**Scope clarification:** `@ickb/dao` has no `UdtHandler` references and no deprecated CCC API calls -- it only manages DAO operations. No changes needed. `@ickb/order` uses `UdtHandler` for two things: the UDT type script (`.script`) and cellDeps (`.cellDeps`). Neither package calls deprecated CCC APIs like `udtBalanceFrom` directly -- those calls are in `@ickb/utils`'s `UdtManager` class (deleted in Phase 5).
-Manager constructor changes:
-- `DaoManager` and `OrderManager` receive a `udt.Udt` instance instead of `UdtHandler`
-- The `UdtHandler.cellDeps` pattern is replaced by `udt.addCellDeps(tx)` (inherited from `ssri.Trait`)
-- Balance queries use the Udt instance: `this.udt.getInputsInfo(client, tx)` instead of `this.udtHandler.getInputsUdtBalance(client, tx)`
+**OrderManager constructor change:**
+- `udtHandler: UdtHandler` replaced with `udtScript: ccc.Script`
+- All 9 `this.udtHandler.script` references become `this.udtScript`
+- No `@ckb-ccc/udt` dependency needed -- `ccc.Script` comes from existing `@ckb-ccc/core`
+
+**UDT cellDeps removal:**
+- Remove all `tx.addCellDeps(this.udtHandler.cellDeps)` calls from `mint()`, `addMatch()`, `melt()`
+- UDT cellDeps are caller responsibility -- CCC Udt adds its own cellDeps during balance completion
+- OrderManager keeps `tx.addCellDeps(this.cellDeps)` for its own order script deps
+- Add JSDoc note on affected methods: caller must ensure UDT cellDeps are present
+
+**Pattern established:** Managers receive plain `ccc.Script` for UDT type identification. The `udt.Udt` instance (including `IckbUdt`) lives at SDK/caller level and handles cellDeps + balance operations externally. This is simpler than initially anticipated.
### Phase 5: core Package (IckbUdt Implementation)
+*Updated 2026-02-26: Phase 5 discuss-phase expanded scope significantly. See `05-CONTEXT.md` in `.planning/phases/05-ickb-core-udt-refactor/` for authoritative decisions including: individual code deps (not dep group), LogicManager/OwnedOwnerManager udtHandler removal, typeScriptFrom rename, findUdts/UdtCell/ErrorTransactionInsufficientCoin deletion, CCC error adoption. The guidance below is the Phase 3 starting point; 05-CONTEXT.md supersedes it.*
+
**Create `IckbUdt extends udt.Udt`** in `packages/core/src/udt.ts`:
```
@@ -417,7 +424,8 @@ Override:
**Update SDK (`packages/sdk/src/sdk.ts`):**
- Construct `IckbUdt` instance instead of `IckbUdtManager`
-- Pass `IckbUdt` instance to managers that need UDT operations
+- Pass `ickbUdt.script` (plain `ccc.Script`) to `OrderManager` constructor, not the `IckbUdt` instance itself
+- `IckbUdt` instance used directly by SDK for balance queries and completion -- not propagated to dao/order managers
- Update balance queries from `[FixedPoint, FixedPoint]` tuple to `UdtInfo` destructuring
---
diff --git a/.planning/phases/04-deprecated-ccc-api-replacement/04-01-PLAN.md b/.planning/phases/04-deprecated-ccc-api-replacement/04-01-PLAN.md
new file mode 100644
index 0000000..fcb42b1
--- /dev/null
+++ b/.planning/phases/04-deprecated-ccc-api-replacement/04-01-PLAN.md
@@ -0,0 +1,214 @@
+---
+phase: 04-deprecated-ccc-api-replacement
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - packages/order/src/order.ts
+ - packages/sdk/src/constants.ts
+autonomous: true
+requirements:
+ - SMTX-05
+
+must_haves:
+ truths:
+ - "OrderManager constructor accepts udtScript: ccc.Script as its third parameter"
+ - "OrderManager methods do not add UDT cellDeps to transactions"
+ - "@ickb/order does not import UdtHandler from @ickb/utils"
+ - "SDK constructs OrderManager with ickbUdt.script (a ccc.Script), not the full IckbUdtManager instance"
+ - "pnpm check:full passes with zero errors"
+ artifacts:
+ - path: "packages/order/src/order.ts"
+ provides: "OrderManager with udtScript: ccc.Script parameter, no UDT cellDeps"
+ contains: "public readonly udtScript: ccc.Script"
+ - path: "packages/sdk/src/constants.ts"
+ provides: "SDK caller passing ickbUdt.script to OrderManager"
+ contains: "ickbUdt.script"
+ key_links:
+ - from: "packages/sdk/src/constants.ts"
+ to: "packages/order/src/order.ts"
+ via: "OrderManager constructor call"
+ pattern: "new OrderManager\\(.*ickbUdt\\.script\\)"
+ - from: "packages/order/src/order.ts"
+ to: "@ckb-ccc/core"
+ via: "ccc.Script type for udtScript"
+ pattern: "udtScript: ccc\\.Script"
+---
+
+
+Replace UdtHandler dependency in @ickb/order with plain ccc.Script; remove UDT cellDeps management from OrderManager; update SDK caller site.
+
+Purpose: OrderManager only needs the UDT type script for cell identification (type script matching, output cell construction). The full UdtHandler interface with balance/completion methods is unnecessary coupling. UDT cellDeps management is moved to the caller/CCC Udt during balance completion, aligning with CCC's completion pipeline pattern.
+
+Output: Modified order.ts and constants.ts; verified @ickb/dao is already clean; verified Phase 3 decision doc already corrected.
+
+
+
+@/home/node/.claude/get-shit-done/workflows/execute-plan.md
+@/home/node/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/04-deprecated-ccc-api-replacement/04-CONTEXT.md
+@.planning/phases/04-deprecated-ccc-api-replacement/04-RESEARCH.md
+
+
+
+
+From packages/order/src/order.ts (BEFORE -- current state):
+```typescript
+import { type UdtHandler } from "@ickb/utils";
+
+export class OrderManager implements ScriptDeps {
+ constructor(
+ public readonly script: ccc.Script,
+ public readonly cellDeps: ccc.CellDep[],
+ public readonly udtHandler: UdtHandler, // CHANGE THIS
+ ) {}
+}
+```
+
+9 udtHandler references to replace (verified line-by-line):
+- Line 7: `type UdtHandler,` in import -> REMOVE from import
+- Line 22: JSDoc `@param udtHandler` -> `@param udtScript`
+- Line 27: constructor `public readonly udtHandler: UdtHandler` -> `public readonly udtScript: ccc.Script`
+- Line 42: `this.udtHandler.script` -> `this.udtScript` (isOrder)
+- Line 190: `tx.addCellDeps(this.udtHandler.cellDeps)` -> DELETE LINE (mint)
+- Line 196: `this.udtHandler.script` -> `this.udtScript` (mint output)
+- Line 229: `tx.addCellDeps(this.udtHandler.cellDeps)` -> DELETE LINE (addMatch)
+- Line 236: `this.udtHandler.script` -> `this.udtScript` (addMatch output)
+- Line 519: `tx.addCellDeps(this.udtHandler.cellDeps)` -> DELETE LINE (melt)
+- Line 635: `this.udtHandler.script` -> `this.udtScript` (findSimpleOrders)
+
+From packages/sdk/src/constants.ts line 78:
+```typescript
+const order = new OrderManager(d.order.script, d.order.cellDeps, ickbUdt);
+// ickbUdt is IckbUdtManager which implements UdtHandler
+// Change to: ickbUdt.script
+```
+
+From @ickb/utils (UdtHandler interface -- reference only, NOT modified):
+```typescript
+export interface UdtHandler extends ScriptDeps {
+ script: ccc.Script;
+ cellDeps: ccc.CellDep[];
+}
+```
+
+
+
+
+
+
+ Task 1: Replace UdtHandler with udtScript in OrderManager and update SDK caller
+ packages/order/src/order.ts, packages/sdk/src/constants.ts
+
+In `packages/order/src/order.ts`:
+
+1. Remove `type UdtHandler,` from the `@ickb/utils` import statement (line 7). Keep all other imports (`BufferedGenerator`, `defaultFindCellsLimit`, `ExchangeRatio`, `ScriptDeps`, `ValueComponents`).
+
+2. Update JSDoc on line 22: change `@param udtHandler - The handler for UDT (User Defined Token).` to `@param udtScript - The UDT (User Defined Token) type script.`
+
+3. Update constructor parameter on line 27: change `public readonly udtHandler: UdtHandler,` to `public readonly udtScript: ccc.Script,`
+
+4. In `isOrder()` (line 42): change `this.udtHandler.script` to `this.udtScript`
+
+5. In `mint()`:
+ - DELETE line 190: `tx.addCellDeps(this.udtHandler.cellDeps);`
+ - Change line 196: `type: this.udtHandler.script,` to `type: this.udtScript,`
+ - Add JSDoc `@remarks` to mint()'s existing JSDoc block (after the existing content, before `@param`): `@remarks Caller must ensure UDT cellDeps are added to the transaction (e.g., via CCC Udt balance completion).`
+
+6. In `addMatch()`:
+ - DELETE line 229: `tx.addCellDeps(this.udtHandler.cellDeps);`
+ - Change line 236: `type: this.udtHandler.script,` to `type: this.udtScript,`
+ - Add JSDoc `@remarks` to addMatch()'s existing JSDoc block: `@remarks Caller must ensure UDT cellDeps are added to the transaction (e.g., via CCC Udt balance completion).`
+
+7. In `melt()`:
+ - DELETE line 519: `tx.addCellDeps(this.udtHandler.cellDeps);`
+ - Add JSDoc `@remarks` to melt()'s existing JSDoc block: `@remarks Caller must ensure UDT cellDeps are added to the transaction (e.g., via CCC Udt balance completion).`
+
+8. In `findSimpleOrders()` (line 635): change `script: this.udtHandler.script,` to `script: this.udtScript,`
+
+In `packages/sdk/src/constants.ts`:
+
+9. Change line 78 from:
+ `const order = new OrderManager(d.order.script, d.order.cellDeps, ickbUdt);`
+ to:
+ `const order = new OrderManager(d.order.script, d.order.cellDeps, ickbUdt.script);`
+
+Apply changes top-to-bottom within each file to avoid line number drift issues.
+
+
+ cd /workspaces/stack && pnpm check:full
+
+
+ - OrderManager constructor parameter is `udtScript: ccc.Script` (not `udtHandler: UdtHandler`)
+ - All 4 `.script` accesses simplified from `this.udtHandler.script` to `this.udtScript`
+ - All 3 `tx.addCellDeps(this.udtHandler.cellDeps)` lines deleted from mint/addMatch/melt
+ - `UdtHandler` no longer appears in @ickb/order imports
+ - JSDoc @remarks added to mint(), addMatch(), melt() about caller cellDeps responsibility
+ - SDK passes `ickbUdt.script` to OrderManager constructor
+ - `pnpm check:full` passes
+
+
+
+
+ Task 2: Verify @ickb/dao and Phase 3 decision doc are already clean
+
+
+Verify (grep-based, no changes expected):
+
+1. Confirm @ickb/dao has no UdtHandler references:
+ `grep -r "UdtHandler\|udtHandler" packages/dao/src/` should return no matches.
+
+2. Confirm @ickb/dao has no deprecated CCC API calls:
+ `grep -r "udtBalanceFrom\|getInputsUdtBalance\|getOutputsUdtBalance\|completeInputsByUdt" packages/dao/src/` should return no matches.
+
+3. Confirm Phase 3 decision doc `03-DECISION.md` lines 369-388 already contain the correct Phase 4 guidance (updated 2026-02-26 in commit c7ba503):
+ - States DaoManager never had UdtHandler
+ - States OrderManager gets `udtScript: ccc.Script`
+ - States UDT cellDeps removed from OrderManager
+ If content is correct, no changes needed (verification only).
+
+4. Confirm @ickb/order no longer imports UdtHandler after Task 1:
+ `grep -r "UdtHandler" packages/order/src/` should return no matches.
+
+All four checks must pass. If any check fails, investigate and fix. These are expected to all pass (dao was always clean, decision doc was pre-corrected, order was cleaned in Task 1).
+
+
+ cd /workspaces/stack && grep -r "UdtHandler\|udtHandler" packages/dao/src/ packages/order/src/ ; echo "exit: $?"
+
+
+ - @ickb/dao confirmed clean: no UdtHandler references, no deprecated CCC API calls
+ - Phase 3 decision doc verified correct (no changes needed)
+ - @ickb/order confirmed clean: no remaining UdtHandler references after Task 1
+
+
+
+
+
+
+1. `pnpm check:full` passes -- all 5 library packages compile with zero type errors
+2. `grep -r "UdtHandler\|udtHandler" packages/order/src/ packages/dao/src/` returns no matches
+3. `grep -r "udtBalanceFrom\|getInputsUdtBalance\|getOutputsUdtBalance\|completeInputsByUdt" packages/dao/src/ packages/order/src/` returns no matches
+4. `grep "udtScript: ccc.Script" packages/order/src/order.ts` confirms new parameter
+5. `grep "ickbUdt.script" packages/sdk/src/constants.ts` confirms SDK caller update
+
+
+
+- OrderManager uses `udtScript: ccc.Script` instead of `udtHandler: UdtHandler`
+- No UDT cellDeps added by OrderManager (3 lines deleted)
+- @ickb/order has zero imports of UdtHandler
+- SDK constructs OrderManager with `ickbUdt.script`
+- @ickb/dao verified clean (no changes needed)
+- Phase 3 decision doc verified correct (no changes needed)
+- `pnpm check:full` passes
+
+
+
diff --git a/.planning/phases/04-deprecated-ccc-api-replacement/04-01-SUMMARY.md b/.planning/phases/04-deprecated-ccc-api-replacement/04-01-SUMMARY.md
new file mode 100644
index 0000000..8db6f50
--- /dev/null
+++ b/.planning/phases/04-deprecated-ccc-api-replacement/04-01-SUMMARY.md
@@ -0,0 +1,104 @@
+---
+phase: 04-deprecated-ccc-api-replacement
+plan: 01
+subsystem: api
+tags: [ccc, udt, order, refactor, decoupling]
+
+# Dependency graph
+requires:
+ - phase: 03-ccc-udt-integration-investigation
+ provides: Decision to use ccc.Script for UDT type identification in managers
+provides:
+ - OrderManager with udtScript: ccc.Script parameter (no UdtHandler dependency)
+ - UDT cellDeps removed from OrderManager (caller/CCC Udt responsibility)
+ - SDK caller site updated to pass ickbUdt.script
+affects: [05-remaining-ccc-migration]
+
+# Tech tracking
+tech-stack:
+ added: []
+ patterns: [managers-receive-plain-script, caller-manages-udt-celldeps]
+
+key-files:
+ created: []
+ modified:
+ - packages/order/src/order.ts
+ - packages/sdk/src/constants.ts
+
+key-decisions:
+ - "OrderManager receives ccc.Script (not udt.Udt) -- simpler than Phase 3 anticipated"
+ - "UDT cellDeps are caller responsibility -- documented via JSDoc @remarks on mint/addMatch/melt"
+
+patterns-established:
+ - "Manager plain-script pattern: managers receive plain ccc.Script for UDT type identification, udt.Udt instance lives at SDK/caller level"
+ - "Caller cellDeps pattern: transaction-building methods do not add UDT cellDeps; callers ensure them via CCC Udt balance completion"
+
+requirements-completed: [SMTX-05]
+
+# Metrics
+duration: 4min
+completed: 2026-02-26
+---
+
+# Phase 4 Plan 01: Replace UdtHandler with udtScript Summary
+
+**OrderManager decoupled from UdtHandler: constructor takes ccc.Script, UDT cellDeps removed, SDK caller updated**
+
+## Performance
+
+- **Duration:** 4 min
+- **Started:** 2026-02-26T11:11:01Z
+- **Completed:** 2026-02-26T11:14:56Z
+- **Tasks:** 2
+- **Files modified:** 2
+
+## Accomplishments
+- OrderManager constructor parameter changed from `udtHandler: UdtHandler` to `udtScript: ccc.Script`
+- All 4 `this.udtHandler.script` accesses simplified to `this.udtScript`
+- All 3 `tx.addCellDeps(this.udtHandler.cellDeps)` lines deleted from mint/addMatch/melt
+- `UdtHandler` import removed from @ickb/order
+- JSDoc `@remarks` added to mint(), addMatch(), melt() documenting caller cellDeps responsibility
+- SDK caller updated to pass `ickbUdt.script` instead of `ickbUdt`
+- @ickb/dao verified clean (no UdtHandler, no deprecated CCC APIs)
+- Phase 3 decision doc verified correct (no changes needed)
+
+## Task Commits
+
+Each task was committed atomically:
+
+1. **Task 1: Replace UdtHandler with udtScript in OrderManager and update SDK caller** - `9e6e3d8` (feat)
+2. **Task 2: Verify @ickb/dao and Phase 3 decision doc are already clean** - no commit (verification-only, no changes)
+
+## Files Created/Modified
+- `packages/order/src/order.ts` - OrderManager: udtScript parameter, removed UDT cellDeps, updated JSDoc
+- `packages/sdk/src/constants.ts` - SDK getConfig: passes ickbUdt.script to OrderManager constructor
+
+## Decisions Made
+- OrderManager receives `ccc.Script` directly (not `udt.Udt`) -- simpler pattern than Phase 3 anticipated, since OrderManager only needs the type script for cell identification
+- UDT cellDeps responsibility moved to caller, documented via JSDoc `@remarks` on affected methods
+
+## Deviations from Plan
+
+None - plan executed exactly as written.
+
+## Issues Encountered
+None
+
+## User Setup Required
+None - no external service configuration required.
+
+## Next Phase Readiness
+- Phase 4 complete (single plan phase)
+- Pattern established: managers receive plain `ccc.Script`, `udt.Udt` lives at caller level
+- Ready for Phase 5: IckbUdt implementation in @ickb/core, UdtHandler/UdtManager deletion in @ickb/utils
+
+## Self-Check: PASSED
+
+- FOUND: packages/order/src/order.ts
+- FOUND: packages/sdk/src/constants.ts
+- FOUND: 04-01-SUMMARY.md
+- FOUND: commit 9e6e3d8
+
+---
+*Phase: 04-deprecated-ccc-api-replacement*
+*Completed: 2026-02-26*
diff --git a/.planning/phases/04-deprecated-ccc-api-replacement/04-CONTEXT.md b/.planning/phases/04-deprecated-ccc-api-replacement/04-CONTEXT.md
new file mode 100644
index 0000000..8aaea70
--- /dev/null
+++ b/.planning/phases/04-deprecated-ccc-api-replacement/04-CONTEXT.md
@@ -0,0 +1,65 @@
+# Phase 4: Deprecated CCC API Replacement - Context
+
+**Gathered:** 2026-02-26
+**Status:** Ready for planning
+
+
+## Phase Boundary
+
+Replace `UdtHandler` dependency in `@ickb/order` with a plain `ccc.Script` parameter; remove UDT cellDeps management from OrderManager (caller/CCC Udt handles it externally during balance completion); verify `@ickb/dao` has no deprecated CCC API calls (it doesn't); correct Phase 3 decision document inaccuracies. UdtHandler/UdtManager deletion happens in Phase 5, not here.
+
+
+
+
+## Implementation Decisions
+
+### OrderManager parameter design
+- Replace `udtHandler: UdtHandler` constructor parameter with `udtScript: ccc.Script`
+- OrderManager only needs the UDT type script -- not the full `udt.Udt` class or `UdtHandler` interface
+- No new `@ckb-ccc/udt` dependency needed on `@ickb/order` -- `ccc.Script` comes from existing `@ckb-ccc/core`
+- Strict parameter swap: all 9 `this.udtHandler.script` references become `this.udtScript`
+- Keep `ScriptDeps` interface on OrderManager (still describes its own script + cellDeps)
+- Keep `ExchangeRatio`, `ValueComponents`, and other `@ickb/utils` imports unchanged
+- Update JSDoc `@param` for the renamed parameter
+- Do NOT audit unrelated imports -- only replace UdtHandler
+
+### CellDeps migration pattern
+- Remove all `tx.addCellDeps(this.udtHandler.cellDeps)` calls from `mint()`, `addMatch()`, and `melt()`
+- UDT cellDeps are now caller responsibility -- CCC Udt adds its own cellDeps during balance completion
+- OrderManager still adds its own cellDeps via `tx.addCellDeps(this.cellDeps)` (order script deps)
+- Add JSDoc note on `mint()`, `addMatch()`, `melt()`: caller must ensure UDT cellDeps are added to the transaction
+- `ScriptDeps` interface unchanged -- still correctly describes OrderManager's own deps
+
+### Phase scope boundaries
+- `@ickb/dao`: No changes needed. Already clean (no UdtHandler, no deprecated CCC APIs). Verified by `pnpm check:full`
+- `@ickb/order`: Replace UdtHandler with udtScript, remove UDT cellDeps calls
+- `@ickb/utils`: Leave UdtManager's 3 deprecated `ccc.udtBalanceFrom()` calls for Phase 5 (UdtManager is being deleted there)
+- Update roadmap success criteria to reflect actual changes (UdtHandler replacement, not deprecated API removal in dao/order)
+- Correct Phase 3 decision document: rewrite the "Implementation Guidance for Phase 4" section to match actual decisions (DaoManager never had UdtHandler; OrderManager gets ccc.Script not udt.Udt)
+- Import audit of remaining @ickb/utils imports: out of scope
+
+### Claude's Discretion
+- Exact JSDoc wording for the cellDeps caller-responsibility notes
+- Whether to update the Phase 3 decision's replacement mapping table or restructure the section
+
+
+
+
+## Specific Ideas
+
+- The pattern established here is simpler than Phase 3 anticipated: managers get `ccc.Script`, not `udt.Udt` instances. The `udt.Udt` instance (including `IckbUdt`) lives at the SDK/caller level, not in dao/order managers.
+- Phase 5 should note that dao/order managers don't need `udt.Udt` propagated to them -- the Udt instance handles completion externally.
+
+
+
+
+## Deferred Ideas
+
+None -- discussion stayed within phase scope.
+
+
+
+---
+
+*Phase: 04-deprecated-ccc-api-replacement*
+*Context gathered: 2026-02-26*
diff --git a/.planning/phases/04-deprecated-ccc-api-replacement/04-RESEARCH.md b/.planning/phases/04-deprecated-ccc-api-replacement/04-RESEARCH.md
new file mode 100644
index 0000000..a64a6f0
--- /dev/null
+++ b/.planning/phases/04-deprecated-ccc-api-replacement/04-RESEARCH.md
@@ -0,0 +1,365 @@
+# Phase 4: Deprecated CCC API Replacement - Research
+
+**Researched:** 2026-02-26
+**Domain:** CKB UDT handler interface replacement in @ickb/order package
+**Confidence:** HIGH
+
+## Summary
+
+Phase 4 is a focused refactoring of `OrderManager` in `@ickb/order` to replace the `UdtHandler` interface dependency with a plain `ccc.Script` parameter. The scope is narrow and well-defined: 9 lines in one file (`order.ts`) need modification, one SDK caller site needs updating, and `@ickb/dao` needs verification (already confirmed clean). The Phase 3 decision document was already corrected in commit `c7ba503` during the discuss-phase -- this should be verified rather than re-done.
+
+This is a straightforward mechanical refactoring with no new dependencies, no architecture decisions, and no deprecated API calls to replace in the target packages. The pattern established here (managers receive `ccc.Script`, not `udt.Udt`) is the foundation that Phase 5 builds upon.
+
+**Primary recommendation:** Single plan covering OrderManager parameter swap, cellDeps removal, SDK caller update, dao verification, and Phase 3 doc verification. All changes are interdependent and should be in one atomic commit.
+
+
+## User Constraints (from CONTEXT.md)
+
+### Locked Decisions
+- Replace `udtHandler: UdtHandler` constructor parameter with `udtScript: ccc.Script`
+- OrderManager only needs the UDT type script -- not the full `udt.Udt` class or `UdtHandler` interface
+- No new `@ckb-ccc/udt` dependency needed on `@ickb/order` -- `ccc.Script` comes from existing `@ckb-ccc/core`
+- Strict parameter swap: all 9 `this.udtHandler.script` references become `this.udtScript`
+- Keep `ScriptDeps` interface on OrderManager (still describes its own script + cellDeps)
+- Keep `ExchangeRatio`, `ValueComponents`, and other `@ickb/utils` imports unchanged
+- Update JSDoc `@param` for the renamed parameter
+- Do NOT audit unrelated imports -- only replace UdtHandler
+- Remove all `tx.addCellDeps(this.udtHandler.cellDeps)` calls from `mint()`, `addMatch()`, and `melt()`
+- UDT cellDeps are now caller responsibility -- CCC Udt adds its own cellDeps during balance completion
+- OrderManager still adds its own cellDeps via `tx.addCellDeps(this.cellDeps)` (order script deps)
+- Add JSDoc note on `mint()`, `addMatch()`, `melt()`: caller must ensure UDT cellDeps are added to the transaction
+- `ScriptDeps` interface unchanged -- still correctly describes OrderManager's own deps
+- `@ickb/dao`: No changes needed. Already clean (no UdtHandler, no deprecated CCC APIs). Verified by `pnpm check:full`
+- `@ickb/order`: Replace UdtHandler with udtScript, remove UDT cellDeps calls
+- `@ickb/utils`: Leave UdtManager's 3 deprecated `ccc.udtBalanceFrom()` calls for Phase 5 (UdtManager is being deleted there)
+- Update roadmap success criteria to reflect actual changes (UdtHandler replacement, not deprecated API removal in dao/order)
+- Correct Phase 3 decision document: rewrite the "Implementation Guidance for Phase 4" section to match actual decisions
+- Import audit of remaining @ickb/utils imports: out of scope
+
+### Claude's Discretion
+- Exact JSDoc wording for the cellDeps caller-responsibility notes
+- Whether to update the Phase 3 decision's replacement mapping table or restructure the section
+
+### Deferred Ideas (OUT OF SCOPE)
+None -- discussion stayed within phase scope.
+
+
+
+## Phase Requirements
+
+| ID | Description | Research Support |
+|----|-------------|-----------------|
+| SMTX-05 | UDT handler registration (`addUdtHandlers()`) is replaced by direct `Udt` instance usage or standalone utility functions | Phase 4 portion: OrderManager.udtHandler replaced with udtScript: ccc.Script; UDT cellDeps removed from OrderManager methods. The `UdtHandler` import is removed from @ickb/order. Full deletion of UdtHandler/UdtManager deferred to Phase 5. |
+
+
+## Standard Stack
+
+### Core
+| Library | Version | Purpose | Why Standard |
+|---------|---------|---------|--------------|
+| `@ckb-ccc/core` | Already in package.json (catalog:) | Provides `ccc.Script`, `ccc.CellDep`, `ccc.Transaction`, `ccc.TransactionLike` | Core CCC types -- `ccc.Script` is the replacement for `UdtHandler.script` |
+| `@ickb/utils` | workspace:* | Provides `ScriptDeps`, `ExchangeRatio`, `ValueComponents`, `BufferedGenerator`, `defaultFindCellsLimit` | Shared types/utilities -- UdtHandler import removed but other imports remain |
+
+### Supporting
+No new libraries needed. No dependency changes to package.json.
+
+### Alternatives Considered
+| Instead of | Could Use | Tradeoff |
+|------------|-----------|----------|
+| `ccc.Script` | `udt.Udt` instance | Over-engineered -- OrderManager only needs type script for cell identification, not balance/completion capabilities. Locked decision: `ccc.Script`. |
+
+**Installation:**
+No installation needed. All required types already available from existing `@ckb-ccc/core` dependency.
+
+## Architecture Patterns
+
+### Current Structure (Before Phase 4)
+```
+packages/order/src/
+ order.ts # OrderManager class -- 9 udtHandler references to change
+ cells.ts # OrderCell, MasterCell, OrderGroup -- no changes needed
+ entities.ts # OrderData, Info, Ratio -- no changes needed
+ index.ts # barrel export -- no changes needed
+```
+
+### Pattern 1: Manager Receives Plain Script
+**What:** OrderManager constructor takes `udtScript: ccc.Script` instead of `udtHandler: UdtHandler`. The manager only uses the UDT type script for cell identification (type script matching in `isOrder`, `findSimpleOrders`, output cell construction in `mint`, `addMatch`).
+**When to use:** When a manager needs to identify cells by type script but does not need balance calculation, completion, or cellDeps management for that script.
+**Example:**
+```typescript
+// Before (current):
+constructor(
+ public readonly script: ccc.Script,
+ public readonly cellDeps: ccc.CellDep[],
+ public readonly udtHandler: UdtHandler,
+) {}
+
+// After (Phase 4):
+constructor(
+ public readonly script: ccc.Script,
+ public readonly cellDeps: ccc.CellDep[],
+ public readonly udtScript: ccc.Script,
+) {}
+```
+
+### Pattern 2: Caller-Managed CellDeps
+**What:** OrderManager no longer adds UDT cellDeps to transactions. The UDT's cellDeps are the caller's responsibility -- CCC's `udt.Udt` adds its own cellDeps during balance completion (`Trait.addCellDeps(tx)`), so OrderManager does not need to duplicate this.
+**When to use:** When a component constructs partial transactions that are completed by a higher-level orchestrator (SDK) using CCC's completion pipeline.
+**Example:**
+```typescript
+// Before (current) -- mint():
+tx.addCellDeps(this.cellDeps);
+tx.addCellDeps(this.udtHandler.cellDeps); // REMOVE this line
+
+// After (Phase 4) -- mint():
+tx.addCellDeps(this.cellDeps);
+// UDT cellDeps handled by caller/CCC Udt during balance completion
+```
+
+### Pattern 3: SDK Caller Passes Script
+**What:** The SDK's `getConfig()` constructs `OrderManager` with `ickbUdt.script` (a `ccc.Script`) instead of the full `IckbUdtManager` instance.
+**Example:**
+```typescript
+// Before (packages/sdk/src/constants.ts:78):
+const order = new OrderManager(d.order.script, d.order.cellDeps, ickbUdt);
+
+// After:
+const order = new OrderManager(d.order.script, d.order.cellDeps, ickbUdt.script);
+```
+
+### Anti-Patterns to Avoid
+- **Passing full UdtHandler/UdtManager to managers that only need Script:** Over-couples components. Managers should receive the minimum data they need.
+- **Adding UDT cellDeps in multiple places:** Creates duplication and ordering issues. CCC's completion pipeline handles cellDeps via `Trait.addCellDeps`.
+- **Changing cells.ts or entities.ts:** These files have no UdtHandler dependency. Do not modify them.
+
+## Don't Hand-Roll
+
+| Problem | Don't Build | Use Instead | Why |
+|---------|-------------|-------------|-----|
+| UDT type script identification | Custom `UdtHandler` interface with `.script` property | Plain `ccc.Script` parameter | `ccc.Script` already has `.eq()` for comparison -- no wrapper needed |
+| UDT cellDeps management in partial transactions | `tx.addCellDeps(udtHandler.cellDeps)` in every method | CCC's `udt.Udt.addCellDeps(tx)` during completion | CCC manages its own deps -- managers should not duplicate |
+
+**Key insight:** OrderManager's relationship to UDT is purely type-script-based identification. It checks "is this cell a UDT cell?" and "construct a UDT-typed output cell". Both operations only need `ccc.Script`, not the full `UdtHandler` interface with balance/completion methods.
+
+## Common Pitfalls
+
+### Pitfall 1: Forgetting the SDK Caller Site
+**What goes wrong:** Changing `OrderManager` constructor but not updating `packages/sdk/src/constants.ts:78` where `new OrderManager(d.order.script, d.order.cellDeps, ickbUdt)` passes the full `IckbUdtManager` instance.
+**Why it happens:** The change is in `@ickb/order` but the caller is in `@ickb/sdk`, a different package.
+**How to avoid:** Update `constants.ts:78` to pass `ickbUdt.script` instead of `ickbUdt`. TypeScript will catch this at compile time since `IckbUdtManager` is not assignable to `ccc.Script`.
+**Warning signs:** Type error during `pnpm check:full` -- "Argument of type 'IckbUdtManager' is not assignable to parameter of type 'Script'".
+
+### Pitfall 2: Missing the Import Removal
+**What goes wrong:** Removing all `udtHandler` usage but leaving `type UdtHandler` in the import statement on line 7 of `order.ts`.
+**Why it happens:** Mechanical replacement of property accesses misses the import declaration.
+**How to avoid:** After replacing all 9 references, remove `type UdtHandler` from the import statement. The linter will also catch unused imports.
+**Warning signs:** `@typescript-eslint/no-unused-vars` or `no-unused-imports` lint error.
+
+### Pitfall 3: Incorrect Reference Count
+**What goes wrong:** Thinking there are only 4 `.script` references when there are actually 9 total `udtHandler` mentions (including JSDoc, constructor parameter, `.script` accesses, and `.cellDeps` accesses).
+**Why it happens:** Counting only one property pattern instead of all mentions.
+**How to avoid:** The 9 references break down as:
+ - Line 22: JSDoc `@param udtHandler` -> update to `@param udtScript`
+ - Line 27: Constructor `public readonly udtHandler: UdtHandler` -> `public readonly udtScript: ccc.Script`
+ - Line 42: `this.udtHandler.script` -> `this.udtScript` (isOrder)
+ - Line 190: `this.udtHandler.cellDeps` -> DELETE LINE (mint)
+ - Line 196: `this.udtHandler.script` -> `this.udtScript` (mint output)
+ - Line 229: `this.udtHandler.cellDeps` -> DELETE LINE (addMatch)
+ - Line 236: `this.udtHandler.script` -> `this.udtScript` (addMatch output)
+ - Line 519: `this.udtHandler.cellDeps` -> DELETE LINE (melt)
+ - Line 635: `this.udtHandler.script` -> `this.udtScript` (findSimpleOrders filter)
+
+### Pitfall 4: Phase 3 Decision Doc Re-correction
+**What goes wrong:** Attempting to re-correct the Phase 3 decision document when it was already corrected in commit `c7ba503` during the discuss-phase.
+**Why it happens:** The roadmap success criterion #5 says to correct it, but the correction was applied proactively.
+**How to avoid:** Verify the current content of `03-DECISION.md` lines 369-388 already contains the correct Phase 4 guidance (updated 2026-02-26). If correct, document verification rather than making changes.
+**Warning signs:** If lines 369-388 mention "DaoManager UdtHandler replacement" or "udt.Udt instance" for OrderManager, correction is still needed. Current content correctly says neither.
+
+### Pitfall 5: Breaking @ickb/core Callers of UdtHandler
+**What goes wrong:** Modifying `UdtHandler` interface or `UdtManager` class in `@ickb/utils` during Phase 4.
+**Why it happens:** Desire to clean up the source of `UdtHandler`.
+**How to avoid:** Phase 4 only touches `@ickb/order` (consumer) and `@ickb/sdk` (caller). The `UdtHandler` interface and `UdtManager` class in `@ickb/utils` remain untouched until Phase 5. `@ickb/core`'s `LogicManager` and `OwnedOwnerManager` still use `udtHandler: UdtHandler` -- they are out of scope.
+
+## Code Examples
+
+Verified patterns from codebase investigation:
+
+### OrderManager Constructor Change
+```typescript
+// packages/order/src/order.ts
+// Source: Verified from current codebase line 16-28
+
+import { ccc } from "@ckb-ccc/core";
+import {
+ BufferedGenerator,
+ defaultFindCellsLimit,
+ type ExchangeRatio,
+ type ScriptDeps,
+ // NOTE: UdtHandler import REMOVED
+ type ValueComponents,
+} from "@ickb/utils";
+
+export class OrderManager implements ScriptDeps {
+ /**
+ * Creates an instance of OrderManager.
+ *
+ * @param script - The order script.
+ * @param cellDeps - The cell dependencies for the order.
+ * @param udtScript - The UDT (User Defined Token) type script.
+ */
+ constructor(
+ public readonly script: ccc.Script,
+ public readonly cellDeps: ccc.CellDep[],
+ public readonly udtScript: ccc.Script,
+ ) {}
+```
+
+### isOrder Method Change
+```typescript
+// packages/order/src/order.ts
+// Source: Verified from current codebase line 39-44
+
+isOrder(cell: ccc.Cell): boolean {
+ return (
+ cell.cellOutput.lock.eq(this.script) &&
+ Boolean(cell.cellOutput.type?.eq(this.udtScript)) // was: this.udtHandler.script
+ );
+}
+```
+
+### mint() cellDeps Removal and JSDoc Update
+```typescript
+// packages/order/src/order.ts
+// Source: Verified from current codebase lines 154-209
+
+/**
+ * Mints a new order cell and appends it to the transaction.
+ *
+ * ...existing JSDoc...
+ *
+ * @remarks Caller must ensure UDT cellDeps are added to the transaction
+ * (e.g., via CCC Udt balance completion).
+ */
+mint(
+ txLike: ccc.TransactionLike,
+ lock: ccc.Script,
+ info: InfoLike,
+ amounts: ValueComponents,
+): ccc.Transaction {
+ const tx = ccc.Transaction.from(txLike);
+ // ...data creation...
+
+ tx.addCellDeps(this.cellDeps);
+ // REMOVED: tx.addCellDeps(this.udtHandler.cellDeps);
+
+ const position = tx.addOutput(
+ {
+ lock: this.script,
+ type: this.udtScript, // was: this.udtHandler.script
+ },
+ data.toBytes(),
+ );
+ // ...rest unchanged...
+}
+```
+
+### SDK Caller Update
+```typescript
+// packages/sdk/src/constants.ts
+// Source: Verified from current codebase line 78
+
+// Before:
+const order = new OrderManager(d.order.script, d.order.cellDeps, ickbUdt);
+
+// After:
+const order = new OrderManager(d.order.script, d.order.cellDeps, ickbUdt.script);
+```
+
+## State of the Art
+
+| Old Approach | Current Approach | When Changed | Impact |
+|--------------|------------------|--------------|--------|
+| `UdtHandler` interface with script + cellDeps + balance methods | Plain `ccc.Script` for type identification; `udt.Udt` for cellDeps/balance at SDK level | Phase 4 (this phase) | Managers are simpler; UDT completion logic centralized in CCC |
+| `tx.addCellDeps(udtHandler.cellDeps)` in every method | CCC Udt handles its own cellDeps during completion | Phase 4 (this phase) | No duplicate cellDep management; aligns with CCC patterns |
+
+**Deprecated/outdated:**
+- `UdtHandler` interface: Still exists in `@ickb/utils` -- deleted in Phase 5. Phase 4 only removes its usage from `@ickb/order`.
+- `UdtManager` class: Still exists in `@ickb/utils` -- deleted in Phase 5. Not touched in Phase 4.
+
+## Precise Change Map
+
+### File: `packages/order/src/order.ts`
+
+| Line | Current | After | Category |
+|------|---------|-------|----------|
+| 7 | `type UdtHandler,` | (remove from import) | Import cleanup |
+| 22 | `@param udtHandler - The handler for UDT` | `@param udtScript - The UDT type script.` | JSDoc |
+| 27 | `public readonly udtHandler: UdtHandler,` | `public readonly udtScript: ccc.Script,` | Constructor |
+| 42 | `this.udtHandler.script` | `this.udtScript` | isOrder() |
+| 190 | `tx.addCellDeps(this.udtHandler.cellDeps);` | (remove line) | mint() cellDeps |
+| 196 | `type: this.udtHandler.script,` | `type: this.udtScript,` | mint() output |
+| 229 | `tx.addCellDeps(this.udtHandler.cellDeps);` | (remove line) | addMatch() cellDeps |
+| 236 | `type: this.udtHandler.script,` | `type: this.udtScript,` | addMatch() output |
+| 519 | `tx.addCellDeps(this.udtHandler.cellDeps);` | (remove line) | melt() cellDeps |
+| 635 | `script: this.udtHandler.script,` | `script: this.udtScript,` | findSimpleOrders() |
+
+Additionally: JSDoc `@remarks` notes on `mint()`, `addMatch()`, `melt()` for caller responsibility.
+
+### File: `packages/sdk/src/constants.ts`
+
+| Line | Current | After | Category |
+|------|---------|-------|----------|
+| 78 | `new OrderManager(d.order.script, d.order.cellDeps, ickbUdt)` | `new OrderManager(d.order.script, d.order.cellDeps, ickbUdt.script)` | Caller update |
+
+### Files NOT Changed (verified clean)
+- `packages/dao/src/dao.ts` -- No UdtHandler, no deprecated CCC APIs
+- `packages/dao/src/cells.ts` -- No UdtHandler, no deprecated CCC APIs
+- `packages/dao/src/index.ts` -- No UdtHandler
+- `packages/order/src/cells.ts` -- No UdtHandler
+- `packages/order/src/entities.ts` -- No UdtHandler
+- `packages/order/src/index.ts` -- No UdtHandler
+- `packages/utils/src/udt.ts` -- UdtHandler/UdtManager remain (Phase 5 scope)
+- `packages/core/src/logic.ts` -- udtHandler references remain (Phase 5 scope)
+- `packages/core/src/owned_owner.ts` -- udtHandler references remain (Phase 5 scope)
+- `packages/core/src/udt.ts` -- IckbUdtManager remains (Phase 5 scope)
+
+### Phase 3 Decision Doc Verification
+- `03-DECISION.md` lines 369-388: Already corrected in commit `c7ba503` (2026-02-26)
+- Content now correctly states: DaoManager never had UdtHandler; OrderManager gets `udtScript: ccc.Script`; UDT cellDeps removed from OrderManager
+- Action: Verify content is correct, no additional changes needed
+
+## Open Questions
+
+1. **Line numbers may shift after edits**
+ - What we know: Line numbers referenced above are from the current file state
+ - What's unclear: Removing 3 cellDeps lines shifts all subsequent line numbers
+ - Recommendation: Apply changes top-to-bottom or use pattern matching rather than absolute line numbers
+
+2. **SDK type return change**
+ - What we know: `getConfig()` return type includes `order: OrderManager`. After this change, `OrderManager.udtScript` is `ccc.Script` instead of `OrderManager.udtHandler` being `UdtHandler`
+ - What's unclear: Whether any SDK callers access `order.udtHandler` directly
+ - Recommendation: Search for `order.udtHandler` usage in SDK tests/apps before finalizing. None found in library packages.
+
+## Sources
+
+### Primary (HIGH confidence)
+- Direct codebase inspection of `packages/order/src/order.ts` (all 9 udtHandler references verified line by line)
+- Direct codebase inspection of `packages/dao/src/dao.ts`, `packages/dao/src/cells.ts` (confirmed zero UdtHandler/deprecated API usage)
+- Direct codebase inspection of `packages/sdk/src/constants.ts:78` (single OrderManager construction site in libraries)
+- Git log of `03-DECISION.md` (correction already in commit `c7ba503`)
+
+### Secondary (MEDIUM confidence)
+- Phase 3 decision document `03-DECISION.md` sections on Phase 4 guidance (verified current and accurate)
+
+### Tertiary (LOW confidence)
+- None -- all findings verified from codebase
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard stack: HIGH - No new dependencies, all types from existing `@ckb-ccc/core`
+- Architecture: HIGH - Mechanical refactoring with exact change map verified line-by-line
+- Pitfalls: HIGH - All pitfalls discovered from codebase analysis (cross-package caller, import cleanup, reference count)
+
+**Research date:** 2026-02-26
+**Valid until:** Indefinite -- this is a mechanical refactoring with no external dependency concerns
diff --git a/.planning/phases/04-deprecated-ccc-api-replacement/04-VERIFICATION.md b/.planning/phases/04-deprecated-ccc-api-replacement/04-VERIFICATION.md
new file mode 100644
index 0000000..47f2061
--- /dev/null
+++ b/.planning/phases/04-deprecated-ccc-api-replacement/04-VERIFICATION.md
@@ -0,0 +1,120 @@
+---
+phase: 04-deprecated-ccc-api-replacement
+verified: 2026-02-26T12:00:00Z
+status: passed
+score: 5/5 must-haves verified
+re_verification: false
+---
+
+# Phase 4: Deprecated CCC API Replacement — Verification Report
+
+**Phase Goal:** UdtHandler dependency in @ickb/order is replaced with plain ccc.Script (udtScript); UDT cellDeps management removed from OrderManager (caller/CCC Udt handles externally during balance completion); @ickb/dao verified clean (no UdtHandler, no deprecated APIs); Phase 3 decision doc corrected to match actual codebase state
+**Verified:** 2026-02-26T12:00:00Z
+**Status:** passed
+**Re-verification:** No — initial verification
+
+---
+
+## Goal Achievement
+
+### Observable Truths
+
+| # | Truth | Status | Evidence |
+|---|-------|--------|----------|
+| 1 | OrderManager constructor accepts `udtScript: ccc.Script` as its third parameter | VERIFIED | `packages/order/src/order.ts` line 26: `public readonly udtScript: ccc.Script,` |
+| 2 | OrderManager methods do not add UDT cellDeps to transactions | VERIFIED | No `tx.addCellDeps(this.udtHandler.cellDeps)` in order.ts; grep returns exit 1 (no matches) |
+| 3 | @ickb/order does not import UdtHandler from @ickb/utils | VERIFIED | Import block at top of order.ts has no `UdtHandler`; `grep -r "UdtHandler" packages/order/src/` returns exit 1 |
+| 4 | SDK constructs OrderManager with ickbUdt.script (a ccc.Script), not full IckbUdtManager instance | VERIFIED | `packages/sdk/src/constants.ts` line 78: `new OrderManager(d.order.script, d.order.cellDeps, ickbUdt.script)` |
+| 5 | pnpm check:full passes with zero errors | VERIFIED | `pnpm check` exit code 0; lint, build, test all pass; only warnings are chunk size and missing codec exports (pre-existing, unrelated to Phase 4) |
+
+**Score:** 5/5 truths verified
+
+---
+
+### Required Artifacts
+
+| Artifact | Expected | Status | Details |
+|----------|----------|--------|---------|
+| `packages/order/src/order.ts` | OrderManager with udtScript: ccc.Script parameter, no UDT cellDeps | VERIFIED | Contains `public readonly udtScript: ccc.Script` at line 26; no `udtHandler` references; @remarks JSDoc on mint (line 162), addMatch (line 218), melt (line 499) |
+| `packages/sdk/src/constants.ts` | SDK caller passing ickbUdt.script to OrderManager | VERIFIED | Line 78: `new OrderManager(d.order.script, d.order.cellDeps, ickbUdt.script)` |
+
+---
+
+### Key Link Verification
+
+| From | To | Via | Status | Details |
+|------|----|-----|--------|---------|
+| `packages/sdk/src/constants.ts` | `packages/order/src/order.ts` | OrderManager constructor call | VERIFIED | Line 78 matches pattern `new OrderManager(.*ickbUdt\.script)` |
+| `packages/order/src/order.ts` | `@ckb-ccc/core` | ccc.Script type for udtScript | VERIFIED | Line 26: `udtScript: ccc.Script`; imports `ccc` from `@ckb-ccc/core` at line 1 |
+
+---
+
+### Requirements Coverage
+
+| Requirement | Source Plan | Description | Status | Evidence |
+|-------------|-------------|-------------|--------|----------|
+| SMTX-05 | 04-01-PLAN.md | UDT handler registration replaced by direct Udt instance usage or standalone utility functions | PARTIAL (Phase 4 contribution verified) | Phase 4 scope: OrderManager.udtHandler replaced with udtScript: ccc.Script, UDT cellDeps removed (caller responsibility). Per REQUIREMENTS.md traceability, SMTX-05 spans Phases 1, 4, and 5. Phase 4's portion is fully delivered. Final completion awaits Phase 5 (UdtHandler/UdtManager deletion from @ickb/utils). |
+
+**Note on SMTX-05 scope:** The requirement spans three phases. Phase 4 owns the OrderManager portion only. `UdtHandler` interface and `UdtManager` class intentionally remain in `@ickb/utils` and `@ickb/core` — their deletion is Phase 5 work. The REQUIREMENTS.md traceability table and the Phase 3 decision doc both confirm this split. No orphaned requirements found.
+
+---
+
+### Supplementary Verifications
+
+**@ickb/dao clean check:**
+- `grep -r "UdtHandler|udtHandler" packages/dao/src/` — exit 1 (no matches). Clean.
+- `grep -r "udtBalanceFrom|getInputsUdtBalance|getOutputsUdtBalance|completeInputsByUdt" packages/dao/src/` — exit 1 (no matches). Clean.
+
+**Phase 3 decision doc correction:**
+- `03-DECISION.md` line 373 contains: `*Updated 2026-02-26 based on Phase 4 discuss-phase. See 04-CONTEXT.md for full decisions.*`
+- Line 266 references `udtScript: ccc.Script (Phase 4)` in the change table.
+- Line 378 states `udtHandler: UdtHandler replaced with udtScript: ccc.Script`.
+- Correction is present and accurate.
+
+**Commit verification:**
+- Commit `9e6e3d8` exists: `feat(04-01): replace UdtHandler with udtScript in OrderManager`
+- Modified files: `packages/order/src/order.ts` and `packages/sdk/src/constants.ts` — exactly the planned files.
+
+---
+
+### Anti-Patterns Found
+
+| File | Line | Pattern | Severity | Impact |
+|------|------|---------|----------|--------|
+| `packages/order/src/order.ts` | 33 | JSDoc still says "UDT handler's script" in `isOrder()` description | Info | Cosmetic only — code uses `this.udtScript` correctly; JSDoc description is stale prose, not a stub |
+| `packages/order/src/order.ts` | 534 | JSDoc says "lock-script cells matching order & UDT handler" in `findOrders()` | Info | Cosmetic only — implementation uses `this.udtScript` correctly at line 637 |
+
+Neither anti-pattern affects correctness or the phase goal. Both are stale JSDoc prose from before the refactor.
+
+---
+
+### Human Verification Required
+
+None. All phase goals are verifiable programmatically:
+- Parameter type changes are statically verified by TypeScript (pnpm check passes).
+- cellDeps calls are verifiable by grep.
+- SDK wiring is verifiable by grep.
+- Phase 3 decision doc correction is verifiable by grep.
+
+---
+
+### Summary
+
+Phase 4 goal is fully achieved. Every must-have truth is verified against the actual codebase:
+
+1. `OrderManager` constructor parameter is `udtScript: ccc.Script` — confirmed in source.
+2. All three `tx.addCellDeps(this.udtHandler.cellDeps)` calls are removed from `mint()`, `addMatch()`, `melt()` — grep returns no matches.
+3. `UdtHandler` import is absent from `@ickb/order` — grep returns no matches across the entire `packages/order/src/` directory.
+4. SDK passes `ickbUdt.script` (not `ickbUdt`) to the OrderManager constructor — confirmed at constants.ts line 78.
+5. `pnpm check` passes with exit code 0, confirming TypeScript type safety across all 5 library packages.
+
+The presence of `UdtHandler` in `@ickb/utils` and `@ickb/core` is expected and intentional — those are deferred to Phase 5 per the roadmap and REQUIREMENTS.md traceability.
+
+Two stale JSDoc strings (lines 33 and 534 of order.ts) describe the old "UDT handler" concept — these are cosmetic and do not affect goal achievement.
+
+SMTX-05 is "In Progress" spanning Phases 1, 4, and 5. Phase 4's contribution (OrderManager refactor) is complete. Phase 5 will complete it by deleting `UdtHandler`/`UdtManager` from `@ickb/utils`.
+
+---
+
+_Verified: 2026-02-26T12:00:00Z_
+_Verifier: AI Coworker (gsd-verifier)_
diff --git a/.planning/phases/05-ickb-core-udt-refactor/05-01-PLAN.md b/.planning/phases/05-ickb-core-udt-refactor/05-01-PLAN.md
new file mode 100644
index 0000000..6176e68
--- /dev/null
+++ b/.planning/phases/05-ickb-core-udt-refactor/05-01-PLAN.md
@@ -0,0 +1,497 @@
+---
+phase: 05-ickb-core-udt-refactor
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - pnpm-workspace.yaml
+ - packages/core/package.json
+ - packages/core/src/udt.ts
+ - packages/core/src/logic.ts
+ - packages/core/src/owned_owner.ts
+autonomous: true
+requirements:
+ - SMTX-05
+ - SMTX-07
+ - SMTX-10
+ - UDT-04
+
+must_haves:
+ truths:
+ - "IckbUdt extends udt.Udt exists in @ickb/core with infoFrom override"
+ - "infoFrom correctly values xUDT cells (positive), receipt cells (positive, input only), deposit cells (negative, input only)"
+ - "IckbUdt.addCellDeps adds individual code deps (xUDT + Logic OutPoints), not dep group"
+ - "IckbUdt.typeScriptFrom static method computes iCKB UDT type script"
+ - "LogicManager no longer takes udtHandler parameter"
+ - "OwnedOwnerManager no longer takes udtHandler parameter"
+ - "No tx.addCellDeps(this.udtHandler.cellDeps) calls remain in LogicManager or OwnedOwnerManager"
+ artifacts:
+ - path: "packages/core/src/udt.ts"
+ provides: "IckbUdt class extending udt.Udt with infoFrom, addCellDeps, typeScriptFrom"
+ contains: "class IckbUdt extends udt.Udt"
+ - path: "packages/core/src/logic.ts"
+ provides: "LogicManager without udtHandler parameter"
+ - path: "packages/core/src/owned_owner.ts"
+ provides: "OwnedOwnerManager without udtHandler parameter"
+ - path: "packages/core/package.json"
+ provides: "@ckb-ccc/udt dependency"
+ contains: "@ckb-ccc/udt"
+ - path: "pnpm-workspace.yaml"
+ provides: "Catalog entry for @ckb-ccc/udt"
+ contains: "@ckb-ccc/udt"
+ key_links:
+ - from: "packages/core/src/udt.ts"
+ to: "@ckb-ccc/udt"
+ via: "import { udt } from '@ckb-ccc/udt'"
+ pattern: "import.*udt.*from.*@ckb-ccc/udt"
+ - from: "packages/core/src/udt.ts"
+ to: "packages/core/src/entities.ts"
+ via: "ReceiptData.decode for receipt cell valuation"
+ pattern: "ReceiptData\\.decode"
+ - from: "packages/core/src/udt.ts"
+ to: "@ickb/dao"
+ via: "DaoManager for deposit cell identification"
+ pattern: "daoManager\\.isDeposit"
+---
+
+
+Implement IckbUdt class extending CCC's udt.Udt in @ickb/core, replacing IckbUdtManager. Remove udtHandler parameter from LogicManager and OwnedOwnerManager. Add @ckb-ccc/udt dependency.
+
+Purpose: This is the core CCC UDT integration -- IckbUdt.infoFrom provides accurate multi-representation balance (xUDT + receipts + deposits) consumed by CCC's completeInputsByBalance pipeline. Removing udtHandler from managers follows the Phase 4 OrderManager pattern, making UDT cellDeps a caller responsibility.
+
+Output: Modified core/src/udt.ts (IckbUdt class), modified logic.ts and owned_owner.ts (no udtHandler), updated package.json and catalog.
+
+
+
+@/home/node/.claude/get-shit-done/workflows/execute-plan.md
+@/home/node/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/05-ickb-core-udt-refactor/05-CONTEXT.md
+@.planning/phases/05-ickb-core-udt-refactor/05-RESEARCH.md
+
+
+
+
+From @ckb-ccc/udt (CCC Udt class -- the base class to extend):
+```typescript
+import { udt } from "@ckb-ccc/udt";
+
+// Constructor: (code: OutPointLike, script: ScriptLike, config?: UdtConfigLike | null)
+// - code: xUDT code cell OutPoint (passed to ssri.Trait)
+// - script: iCKB UDT type script (token identity)
+// No executor needed (legacy xUDT, not SSRI)
+
+class Udt extends ssri.Trait {
+ public readonly script: ccc.Script;
+ public readonly filter: ccc.ClientIndexerSearchKeyFilter;
+
+ constructor(code: ccc.OutPointLike, script: ccc.ScriptLike, config?: UdtConfigLike | null);
+
+ // Override this for multi-representation balance
+ async infoFrom(
+ _client: ccc.Client,
+ cells: ccc.CellAnyLike | ccc.CellAnyLike[],
+ acc?: UdtInfoLike,
+ ): Promise;
+
+ // Override this for custom cellDeps
+ addCellDeps(txLike: ccc.TransactionLike): ccc.Transaction;
+
+ // Checks cell.type matches this.script AND outputData >= 16 bytes
+ isUdt(cell: ccc.CellAnyLike): boolean;
+
+ // Extracts balance from outputData (LE uint128)
+ static balanceFromUnsafe(outputData: ccc.HexLike): ccc.Num;
+}
+
+class UdtInfo {
+ balance: ccc.Num;
+ capacity: ccc.Num;
+ count: number;
+ static from(infoLike?: UdtInfoLike): UdtInfo;
+ clone(): UdtInfo;
+ addAssign(infoLike: UdtInfoLike): this;
+}
+```
+
+From packages/core/src/udt.ts (BEFORE -- current IckbUdtManager to replace):
+```typescript
+export class IckbUdtManager extends UdtManager implements UdtHandler {
+ constructor(
+ script: ccc.Script,
+ cellDeps: ccc.CellDep[],
+ public readonly logicScript: ccc.Script,
+ public readonly daoManager: DaoManager,
+ ) { super(script, cellDeps, "iCKB", "iCKB", 8); }
+
+ static calculateScript(udt: ccc.Script, ickbLogic: ccc.Script): ccc.Script;
+ override async getInputsUdtBalance(client, txLike): Promise<[FixedPoint, FixedPoint]>;
+}
+
+// These functions/constants MUST be preserved unchanged:
+export function ickbValue(ckbUnoccupiedCapacity, header): FixedPoint;
+export function convert(isCkb2Udt, amount, rate, accountDepositCapacity?): FixedPoint;
+export function ickbExchangeRatio(header, accountDepositCapacity?): ExchangeRatio;
+export const ICKB_DEPOSIT_CAP;
+// Internal constants (AR_0, depositUsedCapacity, depositCapacityDelta) also preserved
+```
+
+From packages/core/src/entities.ts:
+```typescript
+export const ReceiptData = {
+ decode(data: ccc.HexLike): { depositQuantity: bigint; depositAmount: bigint };
+ encode(data: { depositQuantity: number; depositAmount: bigint }): ccc.Hex;
+};
+```
+
+From @ickb/dao:
+```typescript
+export class DaoManager {
+ isDeposit(cell: ccc.Cell): boolean; // Checks outputData for 8 zero bytes
+}
+```
+
+From packages/core/src/logic.ts (BEFORE -- udtHandler to remove):
+```typescript
+export class LogicManager implements ScriptDeps {
+ constructor(
+ public readonly script: ccc.Script,
+ public readonly cellDeps: ccc.CellDep[],
+ public readonly daoManager: DaoManager,
+ public readonly udtHandler: UdtHandler, // REMOVE
+ ) {}
+ // Line 88: tx.addCellDeps(this.udtHandler.cellDeps); // REMOVE (in deposit)
+ // Line 125: tx.addCellDeps(this.udtHandler.cellDeps); // REMOVE (in completeDeposit)
+}
+```
+
+From packages/core/src/owned_owner.ts (BEFORE -- udtHandler to remove):
+```typescript
+export class OwnedOwnerManager implements ScriptDeps {
+ constructor(
+ public readonly script: ccc.Script,
+ public readonly cellDeps: ccc.CellDep[],
+ public readonly daoManager: DaoManager,
+ public readonly udtHandler: UdtHandler, // REMOVE
+ ) {}
+ // Line 95: tx.addCellDeps(this.udtHandler.cellDeps); // REMOVE (in requestWithdrawal)
+ // Line 140: tx.addCellDeps(this.udtHandler.cellDeps); // REMOVE (in withdraw)
+}
+```
+
+
+
+
+
+
+ Task 1: Add @ckb-ccc/udt dependency and implement IckbUdt class
+ pnpm-workspace.yaml, packages/core/package.json, packages/core/src/udt.ts
+
+**Step 1: Add catalog entry in `pnpm-workspace.yaml`**
+
+Add `"@ckb-ccc/udt": ^1.12.2` to the catalog section, after the existing `"@ckb-ccc/core": ^1.12.2` entry. This ensures consistency with how `@ckb-ccc/core` is referenced. The `.pnpmfile.cjs` hook will rewrite to `workspace:*` when the local CCC fork is present.
+
+**Step 2: Add dependency in `packages/core/package.json`**
+
+Add `"@ckb-ccc/udt": "catalog:"` to the dependencies object, after `"@ckb-ccc/core": "catalog:"`. The dependencies section should become:
+```json
+"dependencies": {
+ "@ckb-ccc/core": "catalog:",
+ "@ckb-ccc/udt": "catalog:",
+ "@ickb/dao": "workspace:*",
+ "@ickb/utils": "workspace:*"
+}
+```
+
+**Step 3: Run `pnpm install` to update lockfile**
+
+**Step 4: Rewrite `packages/core/src/udt.ts`**
+
+Replace the entire `IckbUdtManager` class with `IckbUdt extends udt.Udt`. Keep all standalone functions (`ickbValue`, `convert`, `ickbExchangeRatio`) and constants (`ICKB_DEPOSIT_CAP`, `AR_0`, `depositUsedCapacity`, `depositCapacityDelta`) UNCHANGED.
+
+New imports (replacing current imports):
+```typescript
+import { ccc } from "@ckb-ccc/core";
+import { udt } from "@ckb-ccc/udt";
+import { ReceiptData } from "./entities.js";
+import type { DaoManager } from "@ickb/dao";
+import type { ExchangeRatio } from "@ickb/utils";
+```
+
+Note: `UdtManager` and `UdtHandler` imports are removed. `ExchangeRatio` is kept (used by `convert` function). `udt` namespace import is added from `@ckb-ccc/udt`.
+
+New class definition:
+```typescript
+/**
+ * IckbUdt extends CCC's Udt class to provide accurate multi-representation
+ * balance for iCKB tokens. The iCKB conservation law is:
+ * Input UDT + Input Receipts = Output UDT + Input Deposits
+ *
+ * `infoFrom` values three cell types:
+ * - xUDT cells: positive balance (standard UDT)
+ * - Receipt cells: positive balance (input only, valued via ickbValue)
+ * - Deposit cells: negative balance (input only, withdrawal reduces UDT supply)
+ *
+ * Output cells without outPoint are naturally excluded from receipt/deposit
+ * processing, since only input cells (resolved by CellInput.getCell()) have outPoint.
+ */
+export class IckbUdt extends udt.Udt {
+ public readonly logicCode: ccc.OutPoint;
+ public readonly logicScript: ccc.Script;
+ public readonly daoManager: DaoManager;
+
+ /**
+ * Creates an instance of IckbUdt.
+ *
+ * @param code - The xUDT code cell OutPoint (passed to base Udt/Trait).
+ * @param script - The iCKB UDT type script (token identity via args).
+ * @param logicCode - The iCKB Logic code cell OutPoint.
+ * @param logicScript - The iCKB Logic script.
+ * @param daoManager - The DAO manager instance for deposit cell identification.
+ */
+ constructor(
+ code: ccc.OutPointLike,
+ script: ccc.ScriptLike,
+ logicCode: ccc.OutPointLike,
+ logicScript: ccc.ScriptLike,
+ daoManager: DaoManager,
+ ) {
+ super(code, script);
+ this.logicCode = ccc.OutPoint.from(logicCode);
+ this.logicScript = ccc.Script.from(logicScript);
+ this.daoManager = daoManager;
+ }
+
+ /**
+ * Computes the iCKB UDT type script from raw UDT and Logic scripts.
+ *
+ * Concatenates the iCKB logic script hash with a fixed 4-byte LE length
+ * postfix ("00000080") to form the UDT type script args.
+ *
+ * @param udt - The raw xUDT script (codeHash and hashType reused).
+ * @param ickbLogic - The iCKB logic script (hash used for args).
+ * @returns A new Script with the computed args.
+ */
+ static typeScriptFrom(udt: ccc.Script, ickbLogic: ccc.Script): ccc.Script {
+ const { codeHash, hashType } = udt;
+ return new ccc.Script(
+ codeHash,
+ hashType,
+ [ickbLogic.hash(), "00000080"].join("") as ccc.Hex,
+ );
+ }
+
+ /**
+ * Computes UDT balance info for iCKB's three cell representations.
+ *
+ * For each cell:
+ * - xUDT cell (type === this.script, data >= 16 bytes): adds positive balance
+ * - Receipt cell (type === logicScript, has outPoint): adds positive balance
+ * via ickbValue of deposit amount * quantity
+ * - Deposit cell (lock === logicScript, isDeposit, has outPoint): adds negative
+ * balance via ickbValue of free capacity (withdrawal reduces UDT supply)
+ *
+ * Cells without outPoint (output cells from getOutputsInfo) skip receipt/deposit
+ * processing -- correct by design since these only appear as inputs.
+ *
+ * @param client - CKB client for header fetches (receipt/deposit valuation).
+ * @param cells - Cell or array of cells to evaluate.
+ * @param acc - Optional accumulator for running totals.
+ * @returns UdtInfo with balance, capacity, and count.
+ */
+ override async infoFrom(
+ client: ccc.Client,
+ cells: ccc.CellAnyLike | ccc.CellAnyLike[],
+ acc?: udt.UdtInfoLike,
+ ): Promise {
+ const info = udt.UdtInfo.from(acc).clone();
+
+ for (const cellLike of [cells].flat()) {
+ const cell = ccc.CellAny.from(cellLike);
+
+ // Standard xUDT cell -- delegate to base class pattern
+ if (this.isUdt(cell)) {
+ info.addAssign({
+ balance: udt.Udt.balanceFromUnsafe(cell.outputData),
+ capacity: cell.cellOutput.capacity,
+ count: 1,
+ });
+ continue;
+ }
+
+ // Receipt and deposit cells need outPoint for header fetch.
+ // Output cells (no outPoint) are skipped -- correct by design.
+ if (!cell.outPoint) {
+ continue;
+ }
+
+ const { type, lock } = cell.cellOutput;
+
+ // Receipt cell: type === logicScript
+ if (type && this.logicScript.eq(type)) {
+ const txWithHeader = await client.getTransactionWithHeader(
+ cell.outPoint.txHash,
+ );
+ if (!txWithHeader?.header) {
+ throw new Error("Header not found for txHash");
+ }
+
+ const { depositQuantity, depositAmount } =
+ ReceiptData.decode(cell.outputData);
+ info.addAssign({
+ balance: ickbValue(depositAmount, txWithHeader.header) *
+ depositQuantity,
+ capacity: cell.cellOutput.capacity,
+ count: 1,
+ });
+ continue;
+ }
+
+ // Deposit cell: lock === logicScript AND isDeposit
+ if (this.logicScript.eq(lock) && this.daoManager.isDeposit(cell)) {
+ const txWithHeader = await client.getTransactionWithHeader(
+ cell.outPoint.txHash,
+ );
+ if (!txWithHeader?.header) {
+ throw new Error("Header not found for txHash");
+ }
+
+ info.addAssign({
+ balance: -ickbValue(cell.capacityFree, txWithHeader.header),
+ capacity: cell.cellOutput.capacity,
+ count: 1,
+ });
+ continue;
+ }
+ }
+
+ return info;
+ }
+
+ /**
+ * Adds iCKB-specific cell dependencies to a transaction.
+ *
+ * Adds individual code deps (not dep group) for:
+ * - xUDT code cell (this.code from ssri.Trait)
+ * - iCKB Logic code cell (this.logicCode)
+ *
+ * @param txLike - The transaction to add cell deps to.
+ * @returns The transaction with cell deps added.
+ */
+ override addCellDeps(txLike: ccc.TransactionLike): ccc.Transaction {
+ const tx = ccc.Transaction.from(txLike);
+ // xUDT code dep
+ tx.addCellDeps({ outPoint: this.code, depType: "code" });
+ // iCKB Logic code dep
+ tx.addCellDeps({ outPoint: this.logicCode, depType: "code" });
+ return tx;
+ }
+}
+```
+
+**Key implementation notes:**
+- `daoManager.isDeposit(cell)` receives a `CellAny` from `CellAny.from(cellLike)`. In `infoFrom` called from `getInputsInfo`, input cells are resolved via `CellInput.getCell()` which returns `Cell` (extends `CellAny`). `DaoManager.isDeposit` takes `ccc.Cell`, and `Cell extends CellAny`, so this works for input cells. For output cells, the `!cell.outPoint` check gates them before `isDeposit` is reached.
+- `balance` in `UdtInfo` uses `ccc.Num` (bigint), which can go negative for deposit cells. This is intentional -- CCC's `completeInputsByBalance` checks `info.balance >= 0` to stop.
+- The `ickbValue` call for receipt cells multiplies by `depositQuantity` (a receipt can represent multiple deposits).
+- The `ickbValue` call for deposit cells uses `cell.capacityFree` (available via `CellAny.capacityFree` getter).
+
+**PRESERVE all code below the class definition unchanged:**
+- `ickbValue` function
+- `ICKB_DEPOSIT_CAP` constant
+- `convert` function
+- `ickbExchangeRatio` function
+- `AR_0`, `depositUsedCapacity`, `depositCapacityDelta` constants
+
+
+ cd /workspaces/stack && pnpm install && grep -q "@ckb-ccc/udt" pnpm-workspace.yaml && grep -q "@ckb-ccc/udt" packages/core/package.json && grep -q "class IckbUdt extends udt.Udt" packages/core/src/udt.ts && grep -q "typeScriptFrom" packages/core/src/udt.ts && echo "PASS"
+
+
+ - @ckb-ccc/udt added to pnpm-workspace.yaml catalog
+ - @ckb-ccc/udt added to @ickb/core package.json dependencies
+ - IckbUdt extends udt.Udt class exists with infoFrom, addCellDeps, typeScriptFrom
+ - IckbUdtManager class no longer exists
+ - calculateScript renamed to typeScriptFrom (static)
+ - No imports of UdtManager or UdtHandler from @ickb/utils in core/src/udt.ts
+ - All standalone functions and constants preserved unchanged
+
+
+
+
+ Task 2: Remove udtHandler from LogicManager and OwnedOwnerManager
+ packages/core/src/logic.ts, packages/core/src/owned_owner.ts
+
+**In `packages/core/src/logic.ts`:**
+
+1. Remove `type UdtHandler,` from the `@ickb/utils` import on line 6. Keep `defaultFindCellsLimit`, `type ScriptDeps`, `unique`.
+
+2. Update JSDoc on line 29: remove `@param udtHandler - The handler for User Defined Tokens (UDTs).`
+
+3. Remove constructor parameter on line 34: delete `public readonly udtHandler: UdtHandler,`
+
+4. In `deposit()` method (line 88): delete `tx.addCellDeps(this.udtHandler.cellDeps);`
+ Add JSDoc `@remarks` to deposit()'s existing JSDoc block: `@remarks Caller must ensure UDT cellDeps are added to the transaction (e.g., via ickbUdt.addCellDeps(tx)).`
+
+5. In `completeDeposit()` method (line 125): delete `tx.addCellDeps(this.udtHandler.cellDeps);`
+ Add JSDoc `@remarks` to completeDeposit()'s existing JSDoc block: `@remarks Caller must ensure UDT cellDeps are added to the transaction (e.g., via ickbUdt.addCellDeps(tx)).`
+
+**In `packages/core/src/owned_owner.ts`:**
+
+1. Remove `type UdtHandler,` from the `@ickb/utils` import on line 6. Keep `defaultFindCellsLimit`, `unique`, `type ScriptDeps`.
+
+2. Update JSDoc on line 24: remove `@param udtHandler - The handler for User Defined Tokens (UDTs).`
+
+3. Remove constructor parameter on line 29: delete `public readonly udtHandler: UdtHandler,`
+
+4. In `requestWithdrawal()` method (line 95): delete `tx.addCellDeps(this.udtHandler.cellDeps);`
+ Add JSDoc `@remarks` to requestWithdrawal()'s existing JSDoc block: `@remarks Caller must ensure UDT cellDeps are added to the transaction (e.g., via ickbUdt.addCellDeps(tx)).`
+
+5. In `withdraw()` method (line 140): delete `tx.addCellDeps(this.udtHandler.cellDeps);`
+ Add JSDoc `@remarks` to withdraw()'s existing JSDoc block: `@remarks Caller must ensure UDT cellDeps are added to the transaction (e.g., via ickbUdt.addCellDeps(tx)).`
+
+This matches Phase 4's OrderManager pattern exactly. UDT cellDeps are now caller responsibility -- documented via JSDoc @remarks.
+
+
+ cd /workspaces/stack && ! grep -rq "UdtHandler\|udtHandler" packages/core/src/logic.ts packages/core/src/owned_owner.ts && echo "PASS"
+
+
+ - LogicManager constructor has 3 parameters (script, cellDeps, daoManager) -- no udtHandler
+ - OwnedOwnerManager constructor has 3 parameters (script, cellDeps, daoManager) -- no udtHandler
+ - All 4 tx.addCellDeps(this.udtHandler.cellDeps) calls deleted (2 in logic.ts, 2 in owned_owner.ts)
+ - UdtHandler no longer imported in either file
+ - JSDoc @remarks added to deposit(), completeDeposit(), requestWithdrawal(), withdraw() about caller cellDeps responsibility
+
+
+
+
+
+
+1. `grep -q "class IckbUdt extends udt.Udt" packages/core/src/udt.ts` confirms IckbUdt class exists
+2. `grep -q "typeScriptFrom" packages/core/src/udt.ts` confirms renamed static method
+3. `grep -q "override async infoFrom" packages/core/src/udt.ts` confirms infoFrom override
+4. `grep -q "override addCellDeps" packages/core/src/udt.ts` confirms addCellDeps override
+5. `grep -r "UdtHandler\|udtHandler" packages/core/src/logic.ts packages/core/src/owned_owner.ts` returns no matches
+6. `grep -r "UdtManager\|IckbUdtManager" packages/core/src/udt.ts` returns no matches (old class removed)
+7. `grep -q "@ckb-ccc/udt" packages/core/package.json` confirms dependency added
+8. `grep -q "@ckb-ccc/udt" pnpm-workspace.yaml` confirms catalog entry added
+
+
+
+- IckbUdt extends udt.Udt with infoFrom override for multi-representation balance
+- addCellDeps override adds individual code deps (xUDT + Logic)
+- typeScriptFrom static method replaces calculateScript
+- LogicManager and OwnedOwnerManager no longer take udtHandler parameter
+- All 4 udtHandler.cellDeps calls removed from managers
+- @ckb-ccc/udt dependency added to core package.json and workspace catalog
+- No imports of UdtManager, UdtHandler, or IckbUdtManager remain in @ickb/core
+- All standalone functions (ickbValue, convert, ickbExchangeRatio) and constants preserved unchanged
+
+
+
diff --git a/.planning/phases/05-ickb-core-udt-refactor/05-01-SUMMARY.md b/.planning/phases/05-ickb-core-udt-refactor/05-01-SUMMARY.md
new file mode 100644
index 0000000..a2793ed
--- /dev/null
+++ b/.planning/phases/05-ickb-core-udt-refactor/05-01-SUMMARY.md
@@ -0,0 +1,120 @@
+---
+phase: 05-ickb-core-udt-refactor
+plan: 01
+subsystem: core
+tags: [udt, ccc, xudt, ickb, balance, cellDeps]
+
+# Dependency graph
+requires:
+ - phase: 04-deprecated-api-udt-pattern
+ provides: UDT pattern (managers receive ccc.Script, cellDeps are caller responsibility)
+ - phase: 03-udt-investigation
+ provides: IckbUdt subclass design (infoFrom override for multi-representation balance)
+provides:
+ - IckbUdt class extending udt.Udt with infoFrom, addCellDeps, typeScriptFrom
+ - LogicManager without udtHandler parameter
+ - OwnedOwnerManager without udtHandler parameter
+ - "@ckb-ccc/udt" dependency in @ickb/core
+affects: [05-02, sdk, bot]
+
+# Tech tracking
+tech-stack:
+ added: ["@ckb-ccc/udt"]
+ patterns: [IckbUdt subclass with multi-representation infoFrom, individual code deps]
+
+key-files:
+ created: []
+ modified:
+ - packages/core/src/udt.ts
+ - packages/core/src/logic.ts
+ - packages/core/src/owned_owner.ts
+ - packages/core/package.json
+ - pnpm-workspace.yaml
+
+key-decisions:
+ - "IckbUdt.infoFrom handles three cell types: xUDT (positive), receipt (positive via ickbValue), deposit (negative via ickbValue)"
+ - "addCellDeps adds individual code deps (xUDT + Logic OutPoints), not dep group"
+ - "Widened DaoManager.isDeposit to accept CellAny — cleaner than type assertion, only inspects fields CellAny provides"
+
+patterns-established:
+ - "IckbUdt subclass pattern: extend udt.Udt, override infoFrom for custom balance, override addCellDeps for custom deps"
+ - "Manager cellDeps responsibility: managers document caller responsibility via JSDoc @remarks"
+
+requirements-completed: [SMTX-05, SMTX-07, SMTX-10, UDT-04]
+
+# Metrics
+duration: 7min
+completed: 2026-02-26
+---
+
+# Phase 5 Plan 1: IckbUdt Implementation Summary
+
+**IckbUdt extends CCC udt.Udt with multi-representation balance (xUDT + receipts + deposits) and individual code deps; udtHandler removed from LogicManager and OwnedOwnerManager**
+
+## Performance
+
+- **Duration:** 7 min
+- **Started:** 2026-02-26T16:05:59Z
+- **Completed:** 2026-02-26T16:12:50Z
+- **Tasks:** 2
+- **Files modified:** 5
+
+## Accomplishments
+- Replaced IckbUdtManager with IckbUdt extending CCC's udt.Udt class, providing accurate multi-representation balance via infoFrom override
+- Added addCellDeps override with individual code deps (xUDT + Logic OutPoints) instead of dep group
+- Renamed calculateScript to typeScriptFrom static method
+- Removed udtHandler parameter and cellDeps calls from LogicManager (deposit, completeDeposit) and OwnedOwnerManager (requestWithdrawal, withdraw)
+- Added @ckb-ccc/udt dependency to workspace catalog and @ickb/core
+
+## Task Commits
+
+Each task was committed atomically:
+
+1. **Task 1: Add @ckb-ccc/udt dependency and implement IckbUdt class** - `4cd87ea` (feat)
+2. **Task 2: Remove udtHandler from LogicManager and OwnedOwnerManager** - `e5dd4c3` (refactor)
+
+## Files Created/Modified
+- `packages/core/src/udt.ts` - IckbUdt class extending udt.Udt with infoFrom, addCellDeps, typeScriptFrom; standalone functions preserved
+- `packages/core/src/logic.ts` - LogicManager without udtHandler; JSDoc @remarks on deposit/completeDeposit
+- `packages/core/src/owned_owner.ts` - OwnedOwnerManager without udtHandler; JSDoc @remarks on requestWithdrawal/withdraw
+- `packages/core/package.json` - Added @ckb-ccc/udt dependency
+- `pnpm-workspace.yaml` - Added @ckb-ccc/udt catalog entry
+
+## Decisions Made
+- Widened DaoManager.isDeposit() to accept CellAny instead of adding a type assertion — cleaner than casting, and isDeposit only inspects cellOutput.type and outputData which CellAny provides
+- addCellDeps adds individual code deps (not dep group) per CCC author preference, matching 05-CONTEXT decision
+
+## Deviations from Plan
+
+### Auto-fixed Issues
+
+**1. [Rule 1 - Bug] Widened DaoManager.isDeposit to accept CellAny**
+- **Found during:** Task 1 (IckbUdt class implementation)
+- **Issue:** daoManager.isDeposit() takes ccc.Cell but infoFrom processes CellAny; TypeScript type mismatch
+- **Fix:** Widened DaoManager.isDeposit() parameter from ccc.Cell to ccc.CellAny in packages/dao/src/dao.ts (cleaner than a type assertion)
+- **Files modified:** packages/dao/src/dao.ts
+- **Verification:** TypeScript compilation passes with no errors across all packages
+
+---
+
+**Total deviations:** 1 auto-fixed (1 bug fix)
+**Impact on plan:** Upstream signature widened for type correctness. Runtime behavior unchanged. No scope creep.
+
+## Issues Encountered
+None
+
+## User Setup Required
+None - no external service configuration required.
+
+## Next Phase Readiness
+- IckbUdt class ready for SDK integration (05-02)
+- UdtHandler interface has zero consumers in core after this plan -- ready for deletion in 05-02
+- All standalone functions (ickbValue, convert, ickbExchangeRatio) preserved unchanged for downstream consumers
+
+## Self-Check: PASSED
+
+All files exist, all commits verified.
+
+---
+*Phase: 05-ickb-core-udt-refactor*
+*Completed: 2026-02-26*
diff --git a/.planning/phases/05-ickb-core-udt-refactor/05-02-PLAN.md b/.planning/phases/05-ickb-core-udt-refactor/05-02-PLAN.md
new file mode 100644
index 0000000..f844bb1
--- /dev/null
+++ b/.planning/phases/05-ickb-core-udt-refactor/05-02-PLAN.md
@@ -0,0 +1,418 @@
+---
+phase: 05-ickb-core-udt-refactor
+plan: 02
+type: execute
+wave: 2
+depends_on: ["05-01"]
+files_modified:
+ - packages/utils/src/udt.ts
+ - packages/utils/src/index.ts
+ - packages/sdk/src/constants.ts
+autonomous: true
+requirements:
+ - SMTX-05
+ - SMTX-07
+ - SMTX-10
+ - UDT-04
+
+must_haves:
+ truths:
+ - "UdtHandler interface, UdtManager class, ErrorTransactionInsufficientCoin, UdtCell, findUdts, addUdts, isUdtSymbol are deleted from @ickb/utils"
+ - "packages/utils/src/udt.ts file no longer exists"
+ - "@ickb/utils no longer exports any UDT-related types"
+ - "SDK constructs IckbUdt instead of IckbUdtManager"
+ - "SDK passes ickbUdt.script to OrderManager"
+ - "LogicManager and OwnedOwnerManager constructed without ickbUdt argument"
+ - "pnpm check:full passes with zero errors"
+ - "SDK audited for ErrorTransactionInsufficientCoin catch blocks -- zero found, no migration required; ErrorUdtInsufficientCoin is CCC's responsibility and surfaces to callers unchanged"
+ artifacts:
+ - path: "packages/utils/src/index.ts"
+ provides: "Barrel export without udt.ts"
+ - path: "packages/sdk/src/constants.ts"
+ provides: "getConfig() constructing IckbUdt with code OutPoints"
+ contains: "new IckbUdt"
+ key_links:
+ - from: "packages/sdk/src/constants.ts"
+ to: "packages/core/src/udt.ts"
+ via: "import { IckbUdt } and constructor call"
+ pattern: "new IckbUdt\\("
+ - from: "packages/sdk/src/constants.ts"
+ to: "packages/core/src/udt.ts"
+ via: "IckbUdt.typeScriptFrom for script computation"
+ pattern: "IckbUdt\\.typeScriptFrom"
+---
+
+
+Delete UDT infrastructure from @ickb/utils (UdtHandler, UdtManager, ErrorTransactionInsufficientCoin, UdtCell, findUdts, addUdts, isUdtSymbol). Update SDK to construct IckbUdt with individual code OutPoints instead of IckbUdtManager with dep group. Verify the full stack compiles.
+
+Purpose: With IckbUdt replacing IckbUdtManager (Plan 05-01) and all managers cleaned of udtHandler references, the entire UDT infrastructure in @ickb/utils has zero consumers. Deleting it eliminates ~400 lines of duplicated code. The SDK wiring completes the refactor by constructing IckbUdt with the CCC-aligned code OutPoint pattern.
+
+Output: Deleted utils/src/udt.ts, updated utils/src/index.ts, rewritten SDK constants.ts. Full stack compiles via pnpm check:full.
+
+
+
+@/home/node/.claude/get-shit-done/workflows/execute-plan.md
+@/home/node/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/05-ickb-core-udt-refactor/05-CONTEXT.md
+@.planning/phases/05-ickb-core-udt-refactor/05-RESEARCH.md
+@.planning/phases/05-ickb-core-udt-refactor/05-01-SUMMARY.md
+
+
+
+
+From packages/core/src/udt.ts (AFTER Plan 05-01 -- new IckbUdt class):
+```typescript
+import { udt } from "@ckb-ccc/udt";
+
+export class IckbUdt extends udt.Udt {
+ constructor(
+ code: ccc.OutPointLike, // xUDT code cell OutPoint
+ script: ccc.ScriptLike, // iCKB UDT type script
+ logicCode: ccc.OutPointLike, // iCKB Logic code cell OutPoint
+ logicScript: ccc.ScriptLike, // iCKB Logic script
+ daoManager: DaoManager, // DAO manager for deposit detection
+ );
+ static typeScriptFrom(udt: ccc.Script, ickbLogic: ccc.Script): ccc.Script;
+ override async infoFrom(client, cells, acc?): Promise;
+ override addCellDeps(txLike): ccc.Transaction;
+}
+```
+
+From packages/core/src/logic.ts (AFTER Plan 05-01 -- no udtHandler):
+```typescript
+export class LogicManager implements ScriptDeps {
+ constructor(
+ public readonly script: ccc.Script,
+ public readonly cellDeps: ccc.CellDep[],
+ public readonly daoManager: DaoManager,
+ // udtHandler REMOVED
+ ) {}
+}
+```
+
+From packages/core/src/owned_owner.ts (AFTER Plan 05-01 -- no udtHandler):
+```typescript
+export class OwnedOwnerManager implements ScriptDeps {
+ constructor(
+ public readonly script: ccc.Script,
+ public readonly cellDeps: ccc.CellDep[],
+ public readonly daoManager: DaoManager,
+ // udtHandler REMOVED
+ ) {}
+}
+```
+
+From packages/sdk/src/constants.ts (BEFORE -- current state to modify):
+```typescript
+import { IckbUdtManager, LogicManager, OwnedOwnerManager } from "@ickb/core";
+
+export function getConfig(d, bots): {
+ managers: {
+ dao: DaoManager;
+ ickbUdt: IckbUdtManager; // CHANGE to IckbUdt
+ logic: LogicManager;
+ ownedOwner: OwnedOwnerManager;
+ order: OrderManager;
+ };
+ bots: ccc.Script[];
+}
+
+// Current construction:
+const ickbUdt = new IckbUdtManager(
+ d.udt.script, // script
+ d.udt.cellDeps, // cellDeps (dep group)
+ d.logic.script, // logicScript
+ dao, // daoManager
+);
+const logic = new LogicManager(d.logic.script, d.logic.cellDeps, dao, ickbUdt);
+const ownedOwner = new OwnedOwnerManager(d.ownedOwner.script, d.ownedOwner.cellDeps, dao, ickbUdt);
+```
+
+Code cell OutPoints for IckbUdt construction (from forks/contracts/scripts/deployment/):
+```
+Mainnet xUDT code: txHash 0xc07844ce21b38e4b071dd0e1ee3b0e27afd8d7532491327f39b786343f558ab7, index 0x0
+Mainnet Logic code: txHash 0xd7309191381f5a8a2904b8a79958a9be2752dbba6871fa193dab6aeb29dc8f44, index 0x0
+Testnet xUDT code: txHash 0xbf6fb538763efec2a70a6a3dcb7242787087e1030c4e7d86585bc63a9d337f5f, index 0x0
+Testnet Logic code: txHash 0x9ac989b3355764f76cdce02c69dedb819fdfbcbda49a7db1a2c9facdfdb9a7fe, index 0x0
+```
+
+ScriptDeps interface (PRESERVED in @ickb/utils -- NOT deleted):
+```typescript
+// From packages/utils/src/utils.ts
+export interface ScriptDeps {
+ script: ccc.Script;
+ cellDeps: ccc.CellDep[];
+}
+```
+
+
+
+
+
+
+ Task 1: Delete utils/src/udt.ts and update barrel export
+ packages/utils/src/udt.ts, packages/utils/src/index.ts
+
+**Step 1: Delete `packages/utils/src/udt.ts`**
+
+Delete the entire file. This removes:
+- `UdtHandler` interface (zero consumers after Plan 05-01)
+- `ErrorTransactionInsufficientCoin` class (zero catch blocks across entire codebase)
+- `UdtManager` class (replaced by `IckbUdt extends udt.Udt`)
+- `UdtCell` interface (internal to `UdtManager`)
+- `findUdts` method (internal to `UdtManager`)
+- `addUdts` method (internal to `UdtManager`)
+- `isUdtSymbol` constant (internal to `UdtManager`)
+
+~406 lines deleted.
+
+**Step 2: Update `packages/utils/src/index.ts`**
+
+Remove the line `export * from "./udt.js";`. The file should become:
+```typescript
+export * from "./codec.js";
+export * from "./heap.js";
+export * from "./utils.js";
+```
+
+**IMPORTANT: `ScriptDeps` interface is NOT in udt.ts.** It lives in `packages/utils/src/utils.ts` and has 6+ consumers remaining (DaoManager, LogicManager, OwnedOwnerManager, OrderManager, getConfig). It is preserved by this change. `ExchangeRatio` is also in `utils.ts`, not `udt.ts`. `ValueComponents` is also in `utils.ts`. All of these survive.
+
+
+ cd /workspaces/stack && ! test -f packages/utils/src/udt.ts && ! grep -q "udt.js" packages/utils/src/index.ts && grep -q "ScriptDeps" packages/utils/src/utils.ts && echo "PASS"
+
+
+ - packages/utils/src/udt.ts deleted (~406 lines removed)
+ - export * from "./udt.js" removed from index.ts
+ - UdtHandler, UdtManager, ErrorTransactionInsufficientCoin, UdtCell, findUdts, addUdts, isUdtSymbol all gone
+ - ScriptDeps, ExchangeRatio, ValueComponents preserved in utils.ts (unaffected)
+
+
+
+
+ Task 2: Update SDK getConfig to construct IckbUdt with code OutPoints and verify full stack
+ packages/sdk/src/constants.ts
+
+**Step 1: Update imports in `packages/sdk/src/constants.ts`**
+
+Change the `@ickb/core` import from:
+```typescript
+import { IckbUdtManager, LogicManager, OwnedOwnerManager } from "@ickb/core";
+```
+to:
+```typescript
+import { IckbUdt, LogicManager, OwnedOwnerManager } from "@ickb/core";
+```
+
+**Step 2: Add code cell OutPoint constants**
+
+Add these constants AFTER the existing script constants (after `ORDER` on line 154) and BEFORE the dep group constants (`MAINNET_DEP_GROUP` on line 159):
+
+```typescript
+/**
+ * Mainnet xUDT code cell OutPoint.
+ * Source: forks/contracts/scripts/deployment/mainnet/deployment.toml
+ */
+const MAINNET_XUDT_CODE = {
+ txHash:
+ "0xc07844ce21b38e4b071dd0e1ee3b0e27afd8d7532491327f39b786343f558ab7",
+ index: "0x0",
+};
+
+/**
+ * Mainnet iCKB Logic code cell OutPoint.
+ * Source: forks/contracts/scripts/deployment/mainnet/deployment.toml
+ */
+const MAINNET_LOGIC_CODE = {
+ txHash:
+ "0xd7309191381f5a8a2904b8a79958a9be2752dbba6871fa193dab6aeb29dc8f44",
+ index: "0x0",
+};
+
+/**
+ * Testnet xUDT code cell OutPoint.
+ * Source: forks/contracts/scripts/deployment/testnet/deployment.toml
+ */
+const TESTNET_XUDT_CODE = {
+ txHash:
+ "0xbf6fb538763efec2a70a6a3dcb7242787087e1030c4e7d86585bc63a9d337f5f",
+ index: "0x0",
+};
+
+/**
+ * Testnet iCKB Logic code cell OutPoint.
+ * Source: forks/contracts/scripts/deployment/testnet/deployment.toml
+ */
+const TESTNET_LOGIC_CODE = {
+ txHash:
+ "0x9ac989b3355764f76cdce02c69dedb819fdfbcbda49a7db1a2c9facdfdb9a7fe",
+ index: "0x0",
+};
+```
+
+**Step 3: Update return type annotation**
+
+Change the return type from `ickbUdt: IckbUdtManager` to `ickbUdt: IckbUdt` (line 37).
+
+**Step 4: Rewrite IckbUdt construction in getConfig()**
+
+Replace the existing IckbUdtManager construction (lines 60-65):
+```typescript
+const ickbUdt = new IckbUdtManager(
+ d.udt.script,
+ d.udt.cellDeps,
+ d.logic.script,
+ dao,
+);
+```
+
+For mainnet/testnet paths, the construction needs code OutPoints. The current flow creates `d` from `from(UDT, depGroup)` etc. which gives `ScriptDeps` with `script` and `cellDeps`. The `IckbUdt` constructor needs `code: OutPointLike` (xUDT code OutPoint) and `logicCode: OutPointLike` (Logic code OutPoint) which are NOT in `ScriptDeps`.
+
+For the mainnet/testnet case: extract code OutPoints based on the network string BEFORE it gets reassigned. The cleanest approach:
+
+Replace the entire IckbUdt construction block with:
+```typescript
+const isMainnet = typeof d !== "string" ? undefined : d === "mainnet";
+// ... after d is reassigned to ScriptDeps ...
+const ickbUdt = new IckbUdt(
+ isMainnet !== undefined
+ ? (isMainnet ? MAINNET_XUDT_CODE : TESTNET_XUDT_CODE)
+ : d.udt.cellDeps[0]?.outPoint ?? { txHash: "0x", index: "0x0" },
+ IckbUdt.typeScriptFrom(
+ ccc.Script.from(d.udt.script),
+ ccc.Script.from(d.logic.script),
+ ),
+ isMainnet !== undefined
+ ? (isMainnet ? MAINNET_LOGIC_CODE : TESTNET_LOGIC_CODE)
+ : d.logic.cellDeps[0]?.outPoint ?? { txHash: "0x", index: "0x0" },
+ d.logic.script,
+ dao,
+);
+```
+
+Wait -- the existing code reassigns `d` inside the `if` block. For devnet, `d` is already a config object. Let me trace the flow more carefully.
+
+Actually, the cleanest approach: capture the network string before `d` gets reassigned.
+
+Add `const isMainnet = ...` BEFORE the `if (d === "mainnet" || d === "testnet")` block:
+
+```typescript
+const network = typeof d === "string" ? d : undefined;
+```
+
+Then the construction becomes:
+```typescript
+const ickbUdt = new IckbUdt(
+ network ? (network === "mainnet" ? MAINNET_XUDT_CODE : TESTNET_XUDT_CODE)
+ : d.udt.cellDeps[0]?.outPoint ?? { txHash: "0x", index: "0x0" },
+ IckbUdt.typeScriptFrom(
+ ccc.Script.from(d.udt.script),
+ ccc.Script.from(d.logic.script),
+ ),
+ network ? (network === "mainnet" ? MAINNET_LOGIC_CODE : TESTNET_LOGIC_CODE)
+ : d.logic.cellDeps[0]?.outPoint ?? { txHash: "0x", index: "0x0" },
+ d.logic.script,
+ dao,
+);
+```
+
+Note: For devnet, the OutPoints are derived from the first cellDep's outPoint (best available from the ScriptDeps interface). The devnet config shape remains unchanged -- this is a pragmatic fallback. Document this with a comment.
+
+Alternative (simpler, per Claude's discretion): Since the devnet path is secondary and code OutPoints are only used by `addCellDeps`, devnet users can also use their dep group OutPoints as code OutPoints if their deps happen to be `depType: "code"` (which they are in local dev). Use `d.udt.cellDeps[0]?.outPoint` with a fallback.
+
+**Step 5: Update LogicManager and OwnedOwnerManager construction**
+
+Remove the `ickbUdt` argument from LogicManager and OwnedOwnerManager constructors:
+
+Change:
+```typescript
+const logic = new LogicManager(
+ d.logic.script,
+ d.logic.cellDeps,
+ dao,
+ ickbUdt,
+);
+const ownedOwner = new OwnedOwnerManager(
+ d.ownedOwner.script,
+ d.ownedOwner.cellDeps,
+ dao,
+ ickbUdt,
+);
+```
+
+To:
+```typescript
+const logic = new LogicManager(d.logic.script, d.logic.cellDeps, dao);
+const ownedOwner = new OwnedOwnerManager(
+ d.ownedOwner.script,
+ d.ownedOwner.cellDeps,
+ dao,
+);
+```
+
+The OrderManager line already passes `ickbUdt.script` (from Phase 4).
+
+**Step 6: Audit SDK and apps for ErrorTransactionInsufficientCoin catch blocks**
+
+Run: `grep -r "ErrorTransactionInsufficientCoin" packages/sdk/ apps/` and confirm zero catch blocks exist. RESEARCH.md already found zero catch blocks across the entire codebase (the error was only thrown, never caught). Document this finding: `ErrorUdtInsufficientCoin` is CCC's responsibility -- it surfaces to callers unchanged from `completeInputsByBalance`. No SDK-side migration of catch blocks is needed.
+
+If any catch blocks ARE found (unexpected), replace `ErrorTransactionInsufficientCoin` with `ErrorUdtInsufficientCoin` from `@ckb-ccc/udt` and update the import.
+
+**Step 7: Run `pnpm check:full`**
+
+This validates the entire stack compiles: all 5 library packages with strict TypeScript settings. This is the final gate for Phase 5.
+
+If `pnpm check:full` fails, investigate and fix the errors. Common issues:
+- Missing catalog entry for `@ckb-ccc/udt` (should be added in Plan 05-01)
+- Type mismatch on `IckbUdt` constructor parameters
+- ScriptDeps import breakage (should not happen -- ScriptDeps is in utils.ts, not udt.ts)
+
+
+ cd /workspaces/stack && pnpm check:full
+
+
+ - SDK imports IckbUdt (not IckbUdtManager) from @ickb/core
+ - getConfig() return type uses IckbUdt (not IckbUdtManager)
+ - IckbUdt constructed with code OutPoints (MAINNET_XUDT_CODE, MAINNET_LOGIC_CODE / testnet equivalents)
+ - IckbUdt.typeScriptFrom used to compute type script (not hardcoded or from ScriptDeps)
+ - LogicManager constructed without ickbUdt argument (3 params)
+ - OwnedOwnerManager constructed without ickbUdt argument (3 params)
+ - Code OutPoint constants added for both mainnet and testnet
+ - Devnet path uses cellDeps[0].outPoint as fallback for code OutPoints
+ - SDK and apps audited for ErrorTransactionInsufficientCoin catch blocks -- zero found, no migration required
+ - pnpm check:full passes with zero errors across all 5 library packages
+
+
+
+
+
+
+1. `pnpm check:full` passes -- all 5 library packages compile with zero type errors
+2. `! test -f packages/utils/src/udt.ts` -- udt.ts deleted from utils
+3. `! grep -q "udt.js" packages/utils/src/index.ts` -- barrel export removed
+4. `grep -r "UdtHandler\|UdtManager\|UdtCell\|findUdts\|addUdts\|isUdtSymbol\|ErrorTransactionInsufficientCoin\|IckbUdtManager" packages/` returns no matches
+5. `grep -q "new IckbUdt" packages/sdk/src/constants.ts` -- SDK constructs IckbUdt
+6. `grep -q "IckbUdt.typeScriptFrom" packages/sdk/src/constants.ts` -- SDK uses typeScriptFrom
+7. `grep -r "udtBalanceFrom" packages/` returns no matches (deprecated API eliminated)
+8. `grep -r "getInputsUdtBalance\|getOutputsUdtBalance\|completeInputsByUdt" packages/` returns no matches
+
+
+
+- packages/utils/src/udt.ts fully deleted (~406 lines removed)
+- UdtHandler, UdtManager, ErrorTransactionInsufficientCoin, UdtCell, findUdts, addUdts, isUdtSymbol no longer exist in codebase
+- No deprecated CCC API calls (udtBalanceFrom, getInputsUdtBalance, etc.) remain in any package
+- SDK constructs IckbUdt with individual code OutPoints (mainnet + testnet)
+- SDK uses IckbUdt.typeScriptFrom to compute type script
+- LogicManager and OwnedOwnerManager constructed without ickbUdt argument
+- ScriptDeps, ExchangeRatio, ValueComponents preserved (not deleted)
+- pnpm check:full passes across all 5 library packages
+
+
+
diff --git a/.planning/phases/05-ickb-core-udt-refactor/05-02-SUMMARY.md b/.planning/phases/05-ickb-core-udt-refactor/05-02-SUMMARY.md
new file mode 100644
index 0000000..636dc52
--- /dev/null
+++ b/.planning/phases/05-ickb-core-udt-refactor/05-02-SUMMARY.md
@@ -0,0 +1,105 @@
+---
+phase: 05-ickb-core-udt-refactor
+plan: 02
+subsystem: core
+tags: [udt, ccc, xudt, ickb, sdk, deletion, code-deps]
+
+# Dependency graph
+requires:
+ - phase: 05-ickb-core-udt-refactor
+ plan: 01
+ provides: IckbUdt class extending udt.Udt, LogicManager/OwnedOwnerManager without udtHandler
+provides:
+ - Deleted UDT infrastructure from @ickb/utils (~406 lines)
+ - SDK getConfig() constructing IckbUdt with individual code OutPoints
+ - Code cell OutPoint constants for mainnet and testnet (xUDT + Logic)
+affects: [sdk, bot, apps]
+
+# Tech tracking
+tech-stack:
+ added: []
+ patterns: [IckbUdt constructed with individual code OutPoints, network capture for code OutPoint selection]
+
+key-files:
+ created: []
+ modified:
+ - packages/utils/src/index.ts
+ - packages/sdk/src/constants.ts
+ deleted:
+ - packages/utils/src/udt.ts
+
+key-decisions:
+ - "IckbUdt.typeScriptFrom computes type script dynamically from raw UDT and Logic scripts (not hardcoded)"
+ - "Devnet code OutPoints fallback to cellDeps[0].outPoint -- pragmatic since devnet deps are typically depType: code"
+ - "ErrorTransactionInsufficientCoin had zero catch blocks across entire codebase -- clean deletion with no migration"
+
+patterns-established:
+ - "SDK network capture pattern: extract network string before d gets reassigned, use for conditional constants"
+ - "Code OutPoint constants: hardcoded per-network OutPoints for individual code deps, sourced from deployment.toml"
+
+requirements-completed: [SMTX-05, SMTX-07, SMTX-10, UDT-04]
+
+# Metrics
+duration: 4min
+completed: 2026-02-26
+---
+
+# Phase 5 Plan 2: Delete UDT Infrastructure and Wire SDK Summary
+
+**Deleted UdtHandler/UdtManager/ErrorTransactionInsufficientCoin from @ickb/utils (~406 lines) and rewired SDK to construct IckbUdt with individual xUDT and Logic code OutPoints**
+
+## Performance
+
+- **Duration:** 4 min
+- **Started:** 2026-02-26T16:16:16Z
+- **Completed:** 2026-02-26T16:20:08Z
+- **Tasks:** 2
+- **Files modified:** 2 modified, 1 deleted
+
+## Accomplishments
+- Deleted packages/utils/src/udt.ts removing UdtHandler, UdtManager, ErrorTransactionInsufficientCoin, UdtCell, findUdts, addUdts, isUdtSymbol (~406 lines)
+- Updated SDK getConfig() to construct IckbUdt with individual code OutPoints (mainnet/testnet) and IckbUdt.typeScriptFrom for script computation
+- Removed ickbUdt argument from LogicManager and OwnedOwnerManager construction in SDK
+- Added 4 code cell OutPoint constants (mainnet xUDT, mainnet Logic, testnet xUDT, testnet Logic) sourced from deployment.toml
+- Confirmed zero ErrorTransactionInsufficientCoin catch blocks across entire codebase -- clean deletion
+
+## Task Commits
+
+Each task was committed atomically:
+
+1. **Task 1: Delete utils/src/udt.ts and update barrel export** - `c5d4363` (refactor)
+2. **Task 2: Update SDK getConfig to construct IckbUdt with code OutPoints and verify full stack** - `6400cc4` (feat)
+
+## Files Created/Modified
+- `packages/utils/src/udt.ts` - DELETED: removed ~406 lines of UdtHandler, UdtManager, ErrorTransactionInsufficientCoin, UdtCell, findUdts, addUdts, isUdtSymbol
+- `packages/utils/src/index.ts` - Removed "export * from ./udt.js" barrel export
+- `packages/sdk/src/constants.ts` - IckbUdt import, code OutPoint constants, IckbUdt construction with typeScriptFrom, LogicManager/OwnedOwnerManager 3-param construction
+
+## Decisions Made
+- Used IckbUdt.typeScriptFrom to compute type script dynamically from raw UDT and Logic scripts, rather than passing d.udt.script directly (which would bypass the correct args computation)
+- Devnet fallback uses cellDeps[0].outPoint for code OutPoints -- pragmatic since devnet deps are typically depType: code
+- Network string captured before d gets reassigned via `const network = typeof d === "string" ? d : undefined` for clean conditional logic
+
+## Deviations from Plan
+
+None - plan executed exactly as written.
+
+## Issues Encountered
+None
+
+## User Setup Required
+None - no external service configuration required.
+
+## Next Phase Readiness
+- Phase 5 complete: IckbUdt replaces all UDT infrastructure, full stack compiles
+- All deprecated CCC API calls (udtBalanceFrom, getInputsUdtBalance, etc.) eliminated
+- ScriptDeps, ExchangeRatio, ValueComponents preserved for downstream consumers
+- Ready for Phase 6 (SDK refactor) or PR preparation
+
+## Self-Check: PASSED
+
+All files exist, all commits verified.
+
+---
+*Phase: 05-ickb-core-udt-refactor*
+*Completed: 2026-02-26*
diff --git a/.planning/phases/05-ickb-core-udt-refactor/05-CONTEXT.md b/.planning/phases/05-ickb-core-udt-refactor/05-CONTEXT.md
new file mode 100644
index 0000000..f970388
--- /dev/null
+++ b/.planning/phases/05-ickb-core-udt-refactor/05-CONTEXT.md
@@ -0,0 +1,84 @@
+# Phase 5: @ickb/core UDT Refactor - Context
+
+**Gathered:** 2026-02-26
+**Status:** Ready for planning
+
+
+## Phase Boundary
+
+Implement `IckbUdt extends udt.Udt` in `@ickb/core` with `infoFrom` override for multi-representation balance (xUDT + receipts + deposits). Delete `UdtHandler` interface, `UdtManager` class, `ErrorTransactionInsufficientCoin`, `UdtCell`, `findUdts`, `addUdts` from `@ickb/utils`. Remove `udtHandler` parameter from `LogicManager` and `OwnedOwnerManager` (same pattern as Phase 4 OrderManager fix). Replace deprecated CCC API calls in `@ickb/core` and `@ickb/utils`. Update SDK to construct and use `IckbUdt`.
+
+
+
+
+## Implementation Decisions
+
+### UDT completion & state compression
+- Drop `compressState` feature entirely — CCC's `completeInputsByBalance` handles completion
+- Callers use `ickbUdt.completeInputsByBalance(tx, signer)` directly — no convenience wrapper
+- Destructure return as needed: `const { tx } = await ickbUdt.completeInputsByBalance(...)` — `addedCount` available but not required
+- Trust CCC fully for completion — `infoFrom` provides accurate cell valuations, CCC handles dual-constraint (balance + capacity) optimization
+- Stick to `completeInputsByBalance` only — `completeInputsAll` and `completeByTransfer` are inherited but not documented for iCKB callers
+
+### CellDeps strategy
+- IckbUdt overrides `addCellDeps` to add both xUDT code dep AND iCKB Logic code dep (individual `depType: "code"` deps, not dep group)
+- Constructor takes `code: OutPointLike` (xUDT script code cell) via base class + `logicCode: OutPointLike` (iCKB Logic script code cell) as new param
+- Individual code cell OutPoints sourced from `forks/contracts/` (mainnet + testnet deployments)
+- Only IckbUdt switches to code deps pattern in Phase 5; other managers (DaoManager, LogicManager, OrderManager, OwnedOwnerManager) keep `CellDep[]` for now
+- Mixed patterns (code deps + dep groups) coexist temporarily — `tx.addCellDeps` deduplicates
+
+### Cell discovery (findUdts)
+- Delete `findUdts`, `addUdts`, `UdtCell` interface, `isUdtSymbol` — all internal to `UdtManager`, no external consumers
+- CCC's `completeInputs` (used internally by `completeInputsByBalance`) handles cell discovery via `Udt.filter`
+- CCC's `isUdt()` length check (>= 16 bytes) is equivalent to current `>= 34` hex chars — no iCKB-specific reason for old threshold
+
+### Error reporting
+- Accept CCC's `ErrorUdtInsufficientCoin` from `completeInputsByBalance` — callers (SDK, UI) format error messages themselves
+- Delete `ErrorTransactionInsufficientCoin` class from `@ickb/utils`
+- Plain `Error` throws for header-not-found in `infoFrom` (exceptional path — CCC cache should provide headers)
+- Phase 5 handles SDK error handling updates (not deferred to Phase 6)
+
+### calculateScript → typeScriptFrom
+- Renamed to `IckbUdt.typeScriptFrom(udt, ickbLogic)` — static method, CCC-aligned naming
+- Keep current parameter types: `(udt: ccc.Script, ickbLogic: ccc.Script): ccc.Script`
+- Computes the `script` param for IckbUdt constructor (token identity via args)
+
+### Manager dependency chain
+- LogicManager: remove `udtHandler: UdtHandler` constructor param, remove `tx.addCellDeps(this.udtHandler.cellDeps)` calls (2 sites) — UDT cellDeps are caller responsibility
+- OwnedOwnerManager: same treatment — remove `udtHandler` param, remove cellDeps calls (2 sites)
+- This matches Phase 4's OrderManager pattern exactly
+- With all three managers cleaned, `UdtHandler` interface has zero consumers → delete from `@ickb/utils`
+- `ScriptDeps` interface: researcher should check if any consumers remain after `UdtHandler` deletion
+
+### Claude's Discretion
+- Constructor parameter for `IckbUdt`: whether to take `CellDep[]` or single `CellDep` for the dep group — Claude picks cleanest pattern
+- Internal organization of the `infoFrom` override code
+- How to structure the `@ckb-ccc/udt` dependency addition to `@ickb/core` package.json
+- Exact migration of SDK `IckbUdtManager` construction to `IckbUdt` construction
+
+
+
+
+## Specific Ideas
+
+- CCC Udt's `completeInputsByBalance` capacity handling is more sophisticated than current `completeUdt` — caps capacity at tx fee to avoid over-providing from UDT cells
+- CCC's `ErrorUdtInsufficientCoin` has a `reason` field for custom messages — could be used later for iCKB-specific formatting if needed (note for researcher)
+- Researcher should check for catch blocks referencing the old `ErrorTransactionInsufficientCoin` class across SDK and apps
+- Matching bot scenario: `infoFrom` may be called frequently during order matching. CCC `Client.cache` handles header fetch dedup, but researcher should evaluate if additional caching is needed for high-frequency scenarios
+- Individual code cell OutPoints (replacing dep group) can be found in `forks/contracts/` for both mainnet and testnet
+
+
+
+
+## Deferred Ideas
+
+- **ValueComponents redesign**: `udtValue` field name is ambiguous in multi-UDT context (which UDT?). CCC's `UdtInfo` scoped to specific Udt instance is cleaner. Evaluate renaming/replacing across all packages in a future phase
+- **All managers to code deps**: Switch DaoManager, LogicManager, OrderManager, OwnedOwnerManager from `CellDep[]` to individual code OutPoints (CCC pattern). Phase 5 only migrates IckbUdt
+- **infoFrom caching for matching bot**: If matching bot performance becomes a bottleneck, add cell→UdtInfo result caching in IckbUdt to avoid recomputation across trial transactions
+
+
+
+---
+
+*Phase: 05-ickb-core-udt-refactor*
+*Context gathered: 2026-02-26*
diff --git a/.planning/phases/05-ickb-core-udt-refactor/05-RESEARCH.md b/.planning/phases/05-ickb-core-udt-refactor/05-RESEARCH.md
new file mode 100644
index 0000000..f12f1b8
--- /dev/null
+++ b/.planning/phases/05-ickb-core-udt-refactor/05-RESEARCH.md
@@ -0,0 +1,589 @@
+# Phase 5: @ickb/core UDT Refactor - Research
+
+**Researched:** 2026-02-26
+**Domain:** CCC UDT subclassing, iCKB multi-representation balance, code deps migration
+**Confidence:** HIGH
+
+## Summary
+
+Phase 5 replaces the custom `IckbUdtManager` (extending `UdtManager`) in `@ickb/core` with `IckbUdt` (extending `udt.Udt` from `@ckb-ccc/udt`). The core challenge is overriding `infoFrom()` to account for iCKB's three on-chain value representations: xUDT cells (positive balance), receipt cells (positive, input only), and deposit cells (negative, input only). The CCC `Udt` class provides all required override points and a complete input-completion pipeline (`completeInputsByBalance`) that consumes the output of `infoFrom`.
+
+Simultaneously, the phase removes `udtHandler` from `LogicManager` and `OwnedOwnerManager` (matching Phase 4's `OrderManager` pattern), deletes the entire UDT infrastructure from `@ickb/utils` (`UdtHandler`, `UdtManager`, `ErrorTransactionInsufficientCoin`, `UdtCell`, `findUdts`, `addUdts`, `isUdtSymbol`), and migrates the SDK from `IckbUdtManager` to `IckbUdt`. The dep group pattern is replaced with individual code deps (xUDT OutPoint + iCKB Logic OutPoint) for `IckbUdt` only.
+
+All required code cell OutPoints are available in `forks/contracts/scripts/deployment/` for both mainnet and testnet. The `@ckb-ccc/udt` package is already available via the local CCC fork build (workspace-linked by `.pnpmfile.cjs`).
+
+**Primary recommendation:** Implement `IckbUdt extends udt.Udt` with `infoFrom` override as the sole customization point. Use the CCC `completeInputsByBalance` pipeline for balance completion. Delete all UDT infrastructure from `@ickb/utils` since `IckbUdt` replaces it entirely.
+
+
+## User Constraints (from CONTEXT.md)
+
+### Locked Decisions
+- Drop `compressState` feature entirely -- CCC's `completeInputsByBalance` handles completion
+- Callers use `ickbUdt.completeInputsByBalance(tx, signer)` directly -- no convenience wrapper
+- Destructure return as needed: `const { tx } = await ickbUdt.completeInputsByBalance(...)` -- `addedCount` available but not required
+- Trust CCC fully for completion -- `infoFrom` provides accurate cell valuations, CCC handles dual-constraint (balance + capacity) optimization
+- Stick to `completeInputsByBalance` only -- `completeInputsAll` and `completeByTransfer` are inherited but not documented for iCKB callers
+- IckbUdt overrides `addCellDeps` to add both xUDT code dep AND iCKB Logic code dep (individual `depType: "code"` deps, not dep group)
+- Constructor takes `code: OutPointLike` (xUDT script code cell) via base class + `logicCode: OutPointLike` (iCKB Logic script code cell) as new param
+- Individual code cell OutPoints sourced from `forks/contracts/` (mainnet + testnet deployments)
+- Only IckbUdt switches to code deps pattern in Phase 5; other managers (DaoManager, LogicManager, OrderManager, OwnedOwnerManager) keep `CellDep[]` for now
+- Mixed patterns (code deps + dep groups) coexist temporarily -- `tx.addCellDeps` deduplicates
+- Delete `findUdts`, `addUdts`, `UdtCell` interface, `isUdtSymbol` -- all internal to `UdtManager`, no external consumers
+- CCC's `completeInputs` (used internally by `completeInputsByBalance`) handles cell discovery via `Udt.filter`
+- CCC's `isUdt()` length check (>= 16 bytes) is equivalent to current `>= 34` hex chars -- no iCKB-specific reason for old threshold
+- Accept CCC's `ErrorUdtInsufficientCoin` from `completeInputsByBalance` -- callers (SDK, UI) format error messages themselves
+- Delete `ErrorTransactionInsufficientCoin` class from `@ickb/utils`
+- Plain `Error` throws for header-not-found in `infoFrom` (exceptional path -- CCC cache should provide headers)
+- Phase 5 handles SDK error handling updates (not deferred to Phase 6)
+- Renamed to `IckbUdt.typeScriptFrom(udt, ickbLogic)` -- static method, CCC-aligned naming
+- Keep current parameter types: `(udt: ccc.Script, ickbLogic: ccc.Script): ccc.Script`
+- Computes the `script` param for IckbUdt constructor (token identity via args)
+- LogicManager: remove `udtHandler: UdtHandler` constructor param, remove `tx.addCellDeps(this.udtHandler.cellDeps)` calls (2 sites) -- UDT cellDeps are caller responsibility
+- OwnedOwnerManager: same treatment -- remove `udtHandler` param, remove cellDeps calls (2 sites)
+- This matches Phase 4's OrderManager pattern exactly
+- With all three managers cleaned, `UdtHandler` interface has zero consumers -> delete from `@ickb/utils`
+- `ScriptDeps` interface: researcher should check if any consumers remain after `UdtHandler` deletion
+
+### Claude's Discretion
+- Constructor parameter for `IckbUdt`: whether to take `CellDep[]` or single `CellDep` for the dep group -- Claude picks cleanest pattern
+- Internal organization of the `infoFrom` override code
+- How to structure the `@ckb-ccc/udt` dependency addition to `@ickb/core` package.json
+- Exact migration of SDK `IckbUdtManager` construction to `IckbUdt` construction
+
+### Deferred Ideas (OUT OF SCOPE)
+- **ValueComponents redesign**: `udtValue` field name is ambiguous in multi-UDT context (which UDT?). CCC's `UdtInfo` scoped to specific Udt instance is cleaner. Evaluate renaming/replacing across all packages in a future phase
+- **All managers to code deps**: Switch DaoManager, LogicManager, OrderManager, OwnedOwnerManager from `CellDep[]` to individual code OutPoints (CCC pattern). Phase 5 only migrates IckbUdt
+- **infoFrom caching for matching bot**: If matching bot performance becomes a bottleneck, add cell->UdtInfo result caching in IckbUdt to avoid recomputation across trial transactions
+
+
+
+## Phase Requirements
+
+| ID | Description | Research Support |
+|----|-------------|-----------------|
+| SMTX-05 | UDT handler registration replaced by direct Udt instance usage | LogicManager/OwnedOwnerManager `udtHandler` removed (4 sites); `UdtHandler` interface deleted; callers pass `ickbUdt.script` to managers and call `ickbUdt.addCellDeps(tx)` externally |
+| SMTX-07 | IckbUdtManager multi-representation balance logic preserved | `IckbUdt.infoFrom()` override handles xUDT cells (positive), receipt cells (positive, input only), deposit cells (negative, input only); conservation law preserved via accurate sign conventions |
+| SMTX-10 | Deprecated CCC API calls replaced | `ccc.udtBalanceFrom()` calls (3 in `UdtManager`) eliminated by deleting `UdtManager`; `IckbUdtManager.getInputsUdtBalance()` replaced by `IckbUdt.infoFrom()` override; no deprecated APIs remain |
+| UDT-04 | IckbUdt extends udt.Udt implemented | `IckbUdt` class with `infoFrom` override, `addCellDeps` override (individual code deps), `typeScriptFrom` static method, LogicManager/OwnedOwnerManager `udtHandler` removed |
+
+
+## Standard Stack
+
+### Core
+| Library | Version | Purpose | Why Standard |
+|---------|---------|---------|--------------|
+| `@ckb-ccc/udt` | workspace (local CCC fork) | Base `Udt` class for subclassing | CCC's official UDT abstraction; `infoFrom` is the override point for multi-representation balance |
+| `@ckb-ccc/core` | catalog: ^1.12.2 | CKB core types, Transaction, Client | Already used across all packages |
+| `@ckb-ccc/ssri` | workspace (local CCC fork) | `ssri.Trait` base class (parent of `Udt`) | Transitive dependency; `Udt extends ssri.Trait` provides `code: OutPoint` |
+
+### Supporting
+| Library | Version | Purpose | When to Use |
+|---------|---------|---------|-------------|
+| `@ickb/dao` | workspace:* | `DaoManager.isDeposit()` for deposit cell identification | Already a dependency of `@ickb/core`; used in `infoFrom` for deposit cell detection |
+| `@ickb/utils` | workspace:* | `ExchangeRatio`, `ValueComponents`, `ScriptDeps`, utility functions | Already a dependency; `ScriptDeps` still used by managers after `UdtHandler` deletion |
+
+### Alternatives Considered
+| Instead of | Could Use | Tradeoff |
+|------------|-----------|----------|
+| Subclass `Udt` | Keep `UdtManager` | Loses CCC's completion pipeline, keeps duplicated code |
+| Override `infoFrom` only | Override `getInputsInfo`/`getOutputsInfo` | `infoFrom` is simpler -- single override handles both inputs and outputs |
+| Dep group for IckbUdt | Individual code deps | Dep groups semantically imply "all cells in group needed"; CCC author prefers individual code deps |
+
+**Installation:**
+Add `@ckb-ccc/udt` to `@ickb/core/package.json` dependencies. With the local CCC fork build active, `.pnpmfile.cjs` automatically rewires to workspace:*:
+```json
+{
+ "dependencies": {
+ "@ckb-ccc/core": "catalog:",
+ "@ckb-ccc/udt": "catalog:",
+ "@ickb/dao": "workspace:*",
+ "@ickb/utils": "workspace:*"
+ }
+}
+```
+
+**Note on catalog:** `@ckb-ccc/udt` is NOT currently in the pnpm-workspace.yaml catalog. It needs to be added OR use `"workspace:*"` directly since the `.pnpmfile.cjs` hook rewires it anyway. Recommendation: add `"@ckb-ccc/udt": "^1.12.2"` to the catalog for consistency with `@ckb-ccc/core`, though the pnpmfile hook will override it to `workspace:*` when the fork is present.
+
+## Architecture Patterns
+
+### IckbUdt Class Structure
+```
+@ickb/core/src/udt.ts
+├── IckbUdt extends udt.Udt
+│ ├── constructor(code, script, logicCode, logicScript, daoManager)
+│ ├── static typeScriptFrom(udt, ickbLogic): Script
+│ ├── override infoFrom(client, cells, acc?): Promise
+│ └── override addCellDeps(txLike): Transaction
+├── ickbValue(capacity, header): FixedPoint (unchanged)
+├── convert(isCkb2Udt, amount, rate, ...): FixedPoint (unchanged)
+├── ickbExchangeRatio(header, ...): ExchangeRatio (unchanged)
+└── constants (AR_0, ICKB_DEPOSIT_CAP, etc.) (unchanged)
+```
+
+### Pattern 1: infoFrom Override for Multi-Representation Balance
+**What:** Override `infoFrom()` to value three cell types: xUDT (positive), receipts (positive, input only), deposits (negative, input only).
+**When to use:** Called by CCC's `getInputsInfo()` and `getOutputsInfo()` which pass resolved cells.
+**Key insight:** `infoFrom` receives `CellAnyLike` which may or may not have `outPoint`. Input cells (from `getInputsInfo`) have `outPoint` (needed for header fetches). Output cells (from `getOutputsInfo`) do not have `outPoint` -- receipt and deposit cells should only appear as inputs, so the absence of `outPoint` naturally excludes them from output-side processing.
+
+```typescript
+// Source: Verified from CCC Udt source (forks/ccc/packages/udt/src/udt/index.ts)
+import { udt } from "@ckb-ccc/udt";
+
+export class IckbUdt extends udt.Udt {
+ constructor(
+ code: ccc.OutPointLike, // xUDT code cell OutPoint (via base Trait)
+ script: ccc.ScriptLike, // iCKB UDT type script (via base Udt)
+ public readonly logicCode: ccc.OutPoint, // iCKB Logic code cell OutPoint
+ public readonly logicScript: ccc.Script, // iCKB Logic script
+ public readonly daoManager: DaoManager, // for isDeposit check
+ ) {
+ super(code, script);
+ }
+
+ async infoFrom(
+ client: ccc.Client,
+ cells: ccc.CellAnyLike | ccc.CellAnyLike[],
+ acc?: udt.UdtInfoLike,
+ ): Promise {
+ const info = udt.UdtInfo.from(acc).clone();
+
+ for (const cellLike of [cells].flat()) {
+ const cell = ccc.CellAny.from(cellLike);
+
+ // Standard xUDT cell -- delegate to base class pattern
+ if (this.isUdt(cell)) {
+ info.addAssign({
+ balance: udt.Udt.balanceFromUnsafe(cell.outputData),
+ capacity: cell.cellOutput.capacity,
+ count: 1,
+ });
+ continue;
+ }
+
+ // Receipt and deposit cells need outPoint for header fetch.
+ // Output cells (no outPoint) are skipped -- correct by design.
+ if (!cell.outPoint) {
+ continue;
+ }
+
+ const { type, lock } = cell.cellOutput;
+
+ // Receipt cell: type === logicScript
+ if (type && this.logicScript.eq(type)) {
+ const txWithHeader = await client.getTransactionWithHeader(
+ cell.outPoint.txHash,
+ );
+ if (!txWithHeader?.header) {
+ throw new Error("Header not found for txHash");
+ }
+ const { depositQuantity, depositAmount } =
+ ReceiptData.decode(cell.outputData);
+ info.addAssign({
+ balance: ickbValue(depositAmount, txWithHeader.header) *
+ depositQuantity,
+ capacity: cell.cellOutput.capacity,
+ count: 1,
+ });
+ continue;
+ }
+
+ // Deposit cell: lock === logicScript AND isDeposit
+ if (this.logicScript.eq(lock) && this.daoManager.isDeposit(cell)) {
+ const txWithHeader = await client.getTransactionWithHeader(
+ cell.outPoint.txHash,
+ );
+ if (!txWithHeader?.header) {
+ throw new Error("Header not found for txHash");
+ }
+ info.addAssign({
+ balance: -ickbValue(cell.capacityFree, txWithHeader.header),
+ capacity: cell.cellOutput.capacity,
+ count: 1,
+ });
+ continue;
+ }
+ }
+
+ return info;
+ }
+
+ override addCellDeps(txLike: ccc.TransactionLike): ccc.Transaction {
+ const tx = ccc.Transaction.from(txLike);
+ // xUDT code dep (from base class's this.code)
+ tx.addCellDeps({ outPoint: this.code, depType: "code" });
+ // iCKB Logic code dep
+ tx.addCellDeps({ outPoint: this.logicCode, depType: "code" });
+ return tx;
+ }
+
+ static typeScriptFrom(udt: ccc.Script, ickbLogic: ccc.Script): ccc.Script {
+ const { codeHash, hashType } = udt;
+ return new ccc.Script(
+ codeHash,
+ hashType,
+ [ickbLogic.hash(), "00000080"].join("") as ccc.Hex,
+ );
+ }
+}
+```
+
+### Pattern 2: Manager udtHandler Removal (Phase 4 Pattern)
+**What:** Remove `udtHandler: UdtHandler` constructor parameter and `tx.addCellDeps(this.udtHandler.cellDeps)` calls from `LogicManager` and `OwnedOwnerManager`.
+**When to use:** Follows Phase 4's `OrderManager` pattern exactly.
+**Example (LogicManager):**
+```typescript
+// BEFORE:
+constructor(
+ public readonly script: ccc.Script,
+ public readonly cellDeps: ccc.CellDep[],
+ public readonly daoManager: DaoManager,
+ public readonly udtHandler: UdtHandler, // REMOVE
+) {}
+
+// AFTER:
+constructor(
+ public readonly script: ccc.Script,
+ public readonly cellDeps: ccc.CellDep[],
+ public readonly daoManager: DaoManager,
+) {}
+
+// BEFORE (in deposit, completeDeposit methods):
+tx.addCellDeps(this.udtHandler.cellDeps); // REMOVE (4 sites total: 2 in LogicManager, 2 in OwnedOwnerManager)
+
+// Caller responsibility (SDK or app code):
+tx = ickbUdt.addCellDeps(tx); // Caller adds UDT cellDeps before or after calling manager methods
+```
+
+### Pattern 3: SDK Construction Migration
+**What:** Replace `IckbUdtManager` construction with `IckbUdt` in `getConfig()`.
+**Key change:** `getConfig` needs xUDT code OutPoint and iCKB Logic code OutPoint (not dep group).
+
+```typescript
+// BEFORE:
+const ickbUdt = new IckbUdtManager(
+ d.udt.script, // UDT type script
+ d.udt.cellDeps, // dep group CellDep[]
+ d.logic.script, // logic script
+ dao, // DaoManager
+);
+
+// AFTER:
+const ickbUdt = new IckbUdt(
+ udtCode, // xUDT code cell OutPoint
+ d.udt.script, // UDT type script (computed via typeScriptFrom)
+ logicCode, // iCKB Logic code cell OutPoint
+ d.logic.script, // logic script
+ dao, // DaoManager
+);
+```
+
+### Anti-Patterns to Avoid
+- **Overriding `getInputsInfo`/`getOutputsInfo` instead of `infoFrom`:** The Phase 3 decision (03-02) explicitly chose `infoFrom` as the sole override point. `getInputsInfo` and `getOutputsInfo` both delegate to `infoFrom` already.
+- **Adding CCC `isUdt()` workaround:** CCC's `isUdt()` checks `>= 16 bytes` data length, which is equivalent to the old `>= 34` hex chars check. No iCKB-specific override needed.
+- **Wrapping `completeInputsByBalance`:** The decision explicitly says "no convenience wrapper." Callers use `ickbUdt.completeInputsByBalance(tx, signer)` directly.
+- **Keeping `UdtHandler` for backward compatibility:** The interface has zero consumers after LogicManager/OwnedOwnerManager cleanup. Delete it cleanly.
+
+## Don't Hand-Roll
+
+| Problem | Don't Build | Use Instead | Why |
+|---------|-------------|-------------|-----|
+| UDT cell discovery | Custom `findUdts` generator | `Udt.completeInputs` (via `filter`) | CCC handles dedup, pagination, signer address resolution |
+| Balance completion pipeline | Custom `completeUdt` | `Udt.completeInputsByBalance` | CCC handles dual-constraint (balance + capacity) optimization, change output, error throwing |
+| UDT balance extraction | `ccc.udtBalanceFrom()` | `Udt.balanceFromUnsafe()` | `udtBalanceFrom` is deprecated; `balanceFromUnsafe` is the replacement |
+| Insufficient coin error | Custom `ErrorTransactionInsufficientCoin` | CCC's `ErrorUdtInsufficientCoin` | CCC throws it from `completeInputsByBalance`; has `amount`, `type`, `reason` fields |
+
+**Key insight:** The entire `UdtManager` class (cell finding, balance calculation, input completion, change output) is replaced by CCC's `Udt` class. The only custom code needed is the `infoFrom` override for multi-representation balance.
+
+## Common Pitfalls
+
+### Pitfall 1: Receipt/Deposit Cells in Outputs
+**What goes wrong:** `infoFrom` accidentally counts receipt or deposit cells in transaction outputs, breaking the conservation law.
+**Why it happens:** `getOutputsInfo` also calls `infoFrom`. If `infoFrom` doesn't distinguish inputs from outputs, receipt/deposit cells could be double-counted.
+**How to avoid:** Check for `cell.outPoint` existence. Input cells have `outPoint` (resolved by `CellInput.getCell()`). Output cells created by `Array.from(tx.outputCells)` do NOT have `outPoint`. Receipt and deposit cells should only appear as inputs, so checking `outPoint` naturally excludes them from output processing.
+**Warning signs:** Balance reported by `getOutputsInfo` includes non-xUDT cell values.
+
+### Pitfall 2: DaoManager.isDeposit Requires Full Cell
+**What goes wrong:** `daoManager.isDeposit(cell)` called on a `CellAny` that hasn't been fully resolved.
+**Why it happens:** `CellAny.from(cellLike)` preserves the `outPoint`, `cellOutput`, and `outputData` from the input. `isDeposit` checks `outputData` for the 8-zero-byte DAO deposit marker.
+**How to avoid:** `isDeposit` only checks `outputData` format (8 zero bytes = deposit), not headers. It works on any `CellAny`-compatible object as long as `outputData` is present. The `CellAny.from()` factory preserves `outputData` from the `CellAnyLike`, so this works correctly. Verify by checking `DaoManager.isDeposit` signature accepts `CellAny` (or a compatible type).
+**Warning signs:** `isDeposit` returns false for valid deposit cells because `outputData` is missing.
+
+### Pitfall 3: ScriptDeps Interface Consumers After Deletion
+**What goes wrong:** `ScriptDeps` interface deleted along with `UdtHandler`, breaking downstream imports.
+**Why it happens:** `ScriptDeps` is used by `LogicManager`, `OwnedOwnerManager`, `OrderManager`, `DaoManager`, and `getConfig()` (SDK constants). It has many consumers beyond `UdtHandler`.
+**How to avoid:** `ScriptDeps` MUST be preserved. Only `UdtHandler` extends it and is deleted. `ScriptDeps` remains in `@ickb/utils/utils.ts` and continues to be imported by all manager classes.
+**Warning signs:** Compilation errors in packages importing `ScriptDeps`.
+
+### Pitfall 4: Catalog Entry for @ckb-ccc/udt
+**What goes wrong:** `pnpm install` fails because `@ckb-ccc/udt` is not in the catalog and `catalog:` specifier is used.
+**Why it happens:** Currently only `@ckb-ccc/core` is in the pnpm-workspace.yaml catalog. The `.pnpmfile.cjs` hook rewires to `workspace:*` when the fork is present, but catalog: specifier needs a matching entry.
+**How to avoid:** Either add `"@ckb-ccc/udt": "^1.12.2"` to the catalog in `pnpm-workspace.yaml`, OR use `"workspace:*"` directly in `@ickb/core/package.json`. The pnpmfile hook will rewrite either way when forks are present. Using `catalog:` is cleaner for consistency but requires the catalog entry.
+**Warning signs:** `pnpm install` errors about unresolved catalog specifier.
+
+### Pitfall 5: IckbUdt Constructor and ssri.Trait
+**What goes wrong:** `IckbUdt` constructor doesn't properly call `super()` with the right arguments.
+**Why it happens:** `Udt extends ssri.Trait`, and `Trait` expects `(code: OutPointLike, executor?: Executor)`. The `Udt` constructor is `(code: OutPointLike, script: ScriptLike, config?: UdtConfigLike)`. `IckbUdt` must pass `code` (xUDT OutPoint) and `script` (iCKB UDT type script) to `super()`, plus store `logicCode` and other iCKB-specific params.
+**How to avoid:** Keep constructor simple: `super(code, script)` passes xUDT code OutPoint and iCKB type script to base `Udt`. No executor needed (legacy xUDT, not SSRI). Store `logicCode`, `logicScript`, `daoManager` as own properties.
+**Warning signs:** `this.code` doesn't point to xUDT code cell; `this.script` doesn't match iCKB UDT type script.
+
+## Code Examples
+
+### IckbUdt addCellDeps Override
+```typescript
+// Source: Verified pattern from CCC Udt.addCellDeps (forks/ccc/packages/udt/src/udt/index.ts:856-863)
+override addCellDeps(txLike: ccc.TransactionLike): ccc.Transaction {
+ const tx = ccc.Transaction.from(txLike);
+ // xUDT code dep (base class stores this as this.code from ssri.Trait)
+ tx.addCellDeps({ outPoint: this.code, depType: "code" });
+ // iCKB Logic code dep (new param)
+ tx.addCellDeps({ outPoint: this.logicCode, depType: "code" });
+ return tx;
+}
+```
+
+### SDK getConfig Migration
+```typescript
+// Source: Current SDK constants.ts construction + planned migration
+
+// NEW: xUDT and Logic code cell OutPoints (from forks/contracts/scripts/deployment/)
+const MAINNET_XUDT_CODE = { txHash: "0xc07844ce21b38e4b071dd0e1ee3b0e27afd8d7532491327f39b786343f558ab7", index: "0x0" };
+const MAINNET_LOGIC_CODE = { txHash: "0xd7309191381f5a8a2904b8a79958a9be2752dbba6871fa193dab6aeb29dc8f44", index: "0x0" };
+const TESTNET_XUDT_CODE = { txHash: "0xbf6fb538763efec2a70a6a3dcb7242787087e1030c4e7d86585bc63a9d337f5f", index: "0x0" };
+const TESTNET_LOGIC_CODE = { txHash: "0x9ac989b3355764f76cdce02c69dedb819fdfbcbda49a7db1a2c9facdfdb9a7fe", index: "0x0" };
+
+// In getConfig():
+const ickbUdt = new IckbUdt(
+ d === "mainnet" ? MAINNET_XUDT_CODE : TESTNET_XUDT_CODE, // xUDT code OutPoint
+ IckbUdt.typeScriptFrom( // iCKB UDT type script
+ ccc.Script.from(UDT),
+ ccc.Script.from(ICKB_LOGIC),
+ ),
+ d === "mainnet" ? MAINNET_LOGIC_CODE : TESTNET_LOGIC_CODE, // Logic code OutPoint
+ ccc.Script.from(ICKB_LOGIC), // Logic script
+ dao, // DaoManager
+);
+```
+
+### Error Handling Migration in SDK
+```typescript
+// Source: CCC ErrorUdtInsufficientCoin (forks/ccc/packages/udt/src/udt/index.ts:27-83)
+
+// BEFORE (old pattern):
+import { ErrorTransactionInsufficientCoin } from "@ickb/utils";
+try {
+ // ...
+} catch (e) {
+ if (e instanceof ErrorTransactionInsufficientCoin) { ... }
+}
+
+// AFTER (CCC pattern):
+import { ErrorUdtInsufficientCoin } from "@ckb-ccc/udt";
+try {
+ const { tx } = await ickbUdt.completeInputsByBalance(tx, signer);
+} catch (e) {
+ if (e instanceof ErrorUdtInsufficientCoin) {
+ // e.amount: shortfall amount
+ // e.type: UDT type script
+ // e.message: "Insufficient coin, need {amount} extra coin"
+ }
+}
+```
+
+### LogicManager After udtHandler Removal
+```typescript
+// Source: Current logic.ts with modifications applied
+
+export class LogicManager implements ScriptDeps {
+ constructor(
+ public readonly script: ccc.Script,
+ public readonly cellDeps: ccc.CellDep[],
+ public readonly daoManager: DaoManager,
+ // udtHandler REMOVED
+ ) {}
+
+ async deposit(txLike, depositQuantity, depositAmount, lock, client) {
+ let tx = ccc.Transaction.from(txLike);
+ // ...
+ tx.addCellDeps(this.cellDeps);
+ // tx.addCellDeps(this.udtHandler.cellDeps); // REMOVED
+ // ...
+ }
+
+ completeDeposit(txLike, receipts) {
+ const tx = ccc.Transaction.from(txLike);
+ // ...
+ tx.addCellDeps(this.cellDeps);
+ // tx.addCellDeps(this.udtHandler.cellDeps); // REMOVED
+ // ...
+ }
+}
+```
+
+## State of the Art
+
+| Old Approach | Current Approach | When Changed | Impact |
+|--------------|------------------|--------------|--------|
+| `UdtHandler` interface + `UdtManager` class | CCC `udt.Udt` class with `infoFrom` override | Phase 5 | Entire custom UDT infrastructure deleted |
+| `ccc.udtBalanceFrom()` (deprecated) | `udt.Udt.balanceFromUnsafe()` | CCC upstream | Deprecated calls eliminated by deleting `UdtManager` |
+| `ccc.ErrorTransactionInsufficientCoin` (deprecated) | `udt.ErrorUdtInsufficientCoin` | CCC upstream | New error class with `amount`, `type`, `reason` fields |
+| `IckbUdtManager.getInputsUdtBalance()` | `IckbUdt.infoFrom()` (override) | Phase 5 | Single override point handles both inputs and outputs |
+| Dep group `CellDep` for all iCKB scripts | Individual code OutPoints for IckbUdt | Phase 5 | xUDT + Logic code deps added individually |
+| `IckbUdtManager.calculateScript()` | `IckbUdt.typeScriptFrom()` | Phase 5 | Static method renamed for CCC alignment |
+
+**Deprecated/outdated:**
+- `UdtHandler` interface: Replaced by CCC `udt.Udt` type
+- `UdtManager` class: Replaced by CCC `udt.Udt` class (which has same capabilities + completion pipeline)
+- `ErrorTransactionInsufficientCoin`: Replaced by `ErrorUdtInsufficientCoin` from `@ckb-ccc/udt`
+- `UdtCell` interface, `findUdts`, `addUdts`, `isUdtSymbol`: All internal to `UdtManager`, no external consumers
+
+## Detailed Code Analysis
+
+### Files to Modify
+
+| File | Changes | Lines Affected |
+|------|---------|----------------|
+| `packages/core/src/udt.ts` | Replace `IckbUdtManager` with `IckbUdt extends udt.Udt`; rename `calculateScript` to `typeScriptFrom` | ~100 lines (rewrite class, keep `ickbValue`, `convert`, `ickbExchangeRatio`, constants) |
+| `packages/core/src/logic.ts` | Remove `udtHandler` constructor param; remove 2 `tx.addCellDeps(this.udtHandler.cellDeps)` calls; remove `UdtHandler` import | ~6 lines changed |
+| `packages/core/src/owned_owner.ts` | Remove `udtHandler` constructor param; remove 2 `tx.addCellDeps(this.udtHandler.cellDeps)` calls; remove `UdtHandler` import | ~6 lines changed |
+| `packages/core/package.json` | Add `@ckb-ccc/udt` dependency | 1 line |
+| `packages/utils/src/udt.ts` | Delete entire file | ~406 lines deleted |
+| `packages/utils/src/index.ts` | Remove `export * from "./udt.js"` | 1 line |
+| `packages/sdk/src/constants.ts` | Rewrite `getConfig()` to construct `IckbUdt`; add code OutPoint constants; adjust `LogicManager`/`OwnedOwnerManager` construction (no `ickbUdt` arg); pass `ickbUdt.script` to `OrderManager` | ~30 lines |
+| `packages/sdk/src/sdk.ts` | No changes needed -- SDK uses managers, not UdtHandler directly | 0 lines |
+
+### ScriptDeps Consumer Audit
+
+`ScriptDeps` is imported/used in these locations (MUST survive `UdtHandler` deletion):
+
+| File | Usage | Status |
+|------|-------|--------|
+| `packages/utils/src/utils.ts` | Interface definition | Keep (canonical location) |
+| `packages/core/src/logic.ts` | `LogicManager implements ScriptDeps` | Keep |
+| `packages/core/src/owned_owner.ts` | `OwnedOwnerManager implements ScriptDeps` | Keep |
+| `packages/order/src/order.ts` | `OrderManager implements ScriptDeps` | Keep |
+| `packages/dao/src/dao.ts` | `DaoManager implements ScriptDeps` | Keep |
+| `packages/sdk/src/constants.ts` | `getConfig()` param type | Keep |
+| `packages/utils/src/udt.ts` | `UdtHandler extends ScriptDeps`, `UdtManager implements UdtHandler` | Deleted with file |
+
+**Conclusion:** `ScriptDeps` has 6 consumers remaining after `UdtHandler`/`UdtManager` deletion. It MUST be preserved.
+
+### ExchangeRatio Consumer Audit
+
+`ExchangeRatio` from `@ickb/utils` is used in:
+| File | Usage |
+|------|-------|
+| `packages/core/src/udt.ts` | `convert()` function parameter type |
+| `packages/sdk/src/sdk.ts` | Imported from `@ickb/utils` (indirect via `@ickb/core`) |
+
+**Conclusion:** `ExchangeRatio` stays in `@ickb/utils/utils.ts`. Not affected by udt.ts deletion.
+
+### Catch Blocks for ErrorTransactionInsufficientCoin
+
+Searched across all apps and packages:
+- **`packages/`**: Only thrown in `packages/utils/src/udt.ts:258` (inside `UdtManager.completeUdt`). No catch blocks.
+- **`apps/`**: No catch blocks referencing `ErrorTransactionInsufficientCoin`. Zero hits.
+- **Conclusion:** Deleting the class has no impact on error handling. CCC's `ErrorUdtInsufficientCoin` (thrown by `completeInputsByBalance`) is the only error callers will encounter going forward.
+
+### Code Cell OutPoints (from forks/contracts/scripts/deployment/)
+
+| Network | Script | tx_hash | index |
+|---------|--------|---------|-------|
+| Mainnet | xUDT | `0xc07844ce21b38e4b071dd0e1ee3b0e27afd8d7532491327f39b786343f558ab7` | 0 |
+| Mainnet | iCKB Logic | `0xd7309191381f5a8a2904b8a79958a9be2752dbba6871fa193dab6aeb29dc8f44` | 0 |
+| Testnet | xUDT | `0xbf6fb538763efec2a70a6a3dcb7242787087e1030c4e7d86585bc63a9d337f5f` | 0 |
+| Testnet | iCKB Logic | `0x9ac989b3355764f76cdce02c69dedb819fdfbcbda49a7db1a2c9facdfdb9a7fe` | 0 |
+
+Source: `forks/contracts/scripts/deployment/mainnet/deployment.toml` and `forks/contracts/scripts/deployment/testnet/deployment.toml` (HIGH confidence -- direct file examination).
+
+### CCC Udt.infoFrom Signature Details
+
+```typescript
+// From forks/ccc/packages/udt/src/udt/index.ts:624-641
+async infoFrom(
+ _client: ccc.Client, // Client for network requests (subclasses use it)
+ cells: ccc.CellAnyLike | ccc.CellAnyLike[], // Single cell or array
+ acc?: UdtInfoLike, // Optional accumulator for running totals
+): Promise
+```
+
+Key details:
+- `_client` parameter: Base class doesn't use it (local-only operation), but it's available for subclass network requests (header fetches in IckbUdt)
+- `cells` is flattened with `[cells].flat()` -- accepts single cell or array
+- `acc` is optional starting accumulator; `UdtInfo.from(acc).clone()` creates a safe copy
+- Returns `UdtInfo` with `{ balance, capacity, count }` fields (all mutable)
+- `balance` can go negative (deposit cells subtract) -- this is intentional for conservation law accounting
+- CCC's `completeInputsByBalance` uses `infoFrom` as the accumulator in `completeInputs`, checking `info.balance >= 0 && info.capacity >= 0` to stop
+
+### CCC getInputsInfo Flow
+
+```typescript
+// From forks/ccc/packages/udt/src/udt/index.ts:1099-1108
+async getInputsInfo(client, txLike): Promise {
+ const tx = ccc.Transaction.from(txLike);
+ const inputCells = await Promise.all(
+ tx.inputs.map((input) => input.getCell(client)), // Resolves to Cell (has outPoint)
+ );
+ return this.infoFrom(client, inputCells); // Passes Cell[] to infoFrom
+}
+```
+
+`CellInput.getCell(client)` returns a `Cell` (extends `CellAny`, has guaranteed `outPoint`). So when `infoFrom` is called from `getInputsInfo`, all cells have `outPoint`. When called from `getOutputsInfo`, cells come from `tx.outputCells` which creates `CellAny` without `outPoint`.
+
+### DaoManager.isDeposit Compatibility
+
+```typescript
+// DaoManager.isDeposit checks cell.outputData for 8 zero bytes (DAO deposit marker)
+// It accepts ccc.Cell which extends CellAny
+// CellAny.from(cellLike) preserves outputData from CellAnyLike
+// So passing CellAny to isDeposit works IF outputData has 8+ bytes
+```
+
+The `daoManager.isDeposit(cell)` call in `infoFrom` receives a `CellAny`. Need to verify `isDeposit` accepts `CellAny` (not just `Cell`). Checking the `DaoManager.isDeposit` signature:
+
+```typescript
+// From packages/dao/src/dao.ts (Phase 1 migrated signature)
+isDeposit(cell: ccc.Cell): boolean
+```
+
+This takes `ccc.Cell`, not `ccc.CellAny`. Since `Cell extends CellAny`, a `CellAny` is NOT a `Cell`. However, for input cells in `infoFrom`, `getInputsInfo` resolves to `Cell` objects (via `CellInput.getCell()`), so this works for inputs. For output cells, we skip deposit detection (no `outPoint` check gates it). This is correct by design -- deposits only appear as inputs.
+
+## Open Questions
+
+1. **getConfig devnet path**
+ - What we know: `getConfig()` accepts a devnet object with `ScriptDeps` for each script. Phase 5 changes IckbUdt construction from `ScriptDeps` (script + cellDeps) to code OutPoints.
+ - What's unclear: The devnet path provides `{ udt: ScriptDeps, logic: ScriptDeps, ... }`. With `IckbUdt` needing code OutPoints instead of `CellDep[]`, the devnet interface needs adjustment. The `udt` entry would need to provide an OutPoint (for xUDT code cell) instead of `cellDeps`.
+ - Recommendation: For devnet, accept either: (a) a new `codeOutPoint` field in each ScriptDeps-like config, or (b) restructure the devnet config type to match the new needs. Since devnet usage is secondary, keep it minimal -- document the interface change.
+
+2. **pnpm-workspace.yaml catalog entry**
+ - What we know: `@ckb-ccc/core` has `catalog:` entry; `@ckb-ccc/udt` does not.
+ - What's unclear: Whether to add `@ckb-ccc/udt` to catalog or use `workspace:*` directly.
+ - Recommendation: Add `"@ckb-ccc/udt": "^1.12.2"` to catalog alongside `@ckb-ccc/core` for consistency. The pnpmfile hook rewrites to `workspace:*` when forks are present.
+
+## Sources
+
+### Primary (HIGH confidence)
+- CCC `Udt` class source: `/workspaces/stack/forks/ccc/packages/udt/src/udt/index.ts` -- `infoFrom`, `addCellDeps`, `completeInputsByBalance`, `getInputsInfo`, `getOutputsInfo` signatures and implementations verified
+- CCC `ssri.Trait` source: `/workspaces/stack/forks/ccc/packages/ssri/src/trait.ts` -- constructor `(code: OutPointLike, executor?: Executor)` verified
+- CCC `CellAny` class: `/workspaces/stack/forks/ccc/packages/core/src/ckb/transaction.ts:313-432` -- `outPoint` optional, `capacityFree` getter verified
+- CCC `ErrorUdtInsufficientCoin`: `/workspaces/stack/forks/ccc/packages/udt/src/udt/index.ts:27-83` -- `amount`, `type`, `reason` fields verified
+- Current `IckbUdtManager`: `/workspaces/stack/packages/core/src/udt.ts` -- full source examined
+- Current `UdtManager`/`UdtHandler`: `/workspaces/stack/packages/utils/src/udt.ts` -- full source examined
+- Current `LogicManager`: `/workspaces/stack/packages/core/src/logic.ts` -- `udtHandler` usage at 2 sites confirmed
+- Current `OwnedOwnerManager`: `/workspaces/stack/packages/core/src/owned_owner.ts` -- `udtHandler` usage at 2 sites confirmed
+- SDK `getConfig()`: `/workspaces/stack/packages/sdk/src/constants.ts` -- construction flow verified
+- Contract deployments: `/workspaces/stack/forks/contracts/scripts/deployment/mainnet/deployment.toml` and testnet equivalent -- xUDT and Logic code cell OutPoints verified
+
+### Secondary (MEDIUM confidence)
+- Phase 3 decision document: `.planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md` -- `infoFrom` as sole override point, cell discovery boundary
+- Phase 4 context/execution: `.planning/phases/04-deprecated-ccc-api-replacement/` -- OrderManager pattern for udtHandler removal
+- Project STATE.md accumulated decisions -- cross-referenced with source code
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard stack: HIGH -- all libraries verified from local source code; `@ckb-ccc/udt` available via workspace
+- Architecture: HIGH -- `infoFrom` override pattern verified from CCC source; all 4 `udtHandler` removal sites identified and confirmed
+- Pitfalls: HIGH -- `outPoint` availability, `ScriptDeps` consumers, catalog entry all verified against source
+
+**Research date:** 2026-02-26
+**Valid until:** 2026-03-26 (stable -- all source code is local, no external API changes expected)
diff --git a/.planning/phases/05-ickb-core-udt-refactor/05-VERIFICATION.md b/.planning/phases/05-ickb-core-udt-refactor/05-VERIFICATION.md
new file mode 100644
index 0000000..0d863ec
--- /dev/null
+++ b/.planning/phases/05-ickb-core-udt-refactor/05-VERIFICATION.md
@@ -0,0 +1,102 @@
+---
+phase: 05-ickb-core-udt-refactor
+verified: 2026-02-26T17:00:00Z
+status: passed
+score: 8/8 must-haves verified
+re_verification: false
+---
+
+# Phase 5: @ickb/core UDT Refactor Verification Report
+
+**Phase Goal:** Implement `IckbUdt extends udt.Udt` in `@ickb/core`, replacing `IckbUdtManager`. Delete entire UDT infrastructure from `@ickb/utils`. Wire SDK to construct `IckbUdt`.
+**Verified:** 2026-02-26T17:00:00Z
+**Status:** passed
+**Re-verification:** No — initial verification
+
+## Goal Achievement
+
+### Observable Truths
+
+| # | Truth | Status | Evidence |
+|----|-------|--------|----------|
+| 1 | `IckbUdt extends udt.Udt` exists in `@ickb/core` with `infoFrom` override | VERIFIED | `packages/core/src/udt.ts` line 20: `export class IckbUdt extends udt.Udt`, line 84: `override async infoFrom` |
+| 2 | `infoFrom` correctly values xUDT cells (positive), receipt cells (positive, input only), deposit cells (negative, input only) | VERIFIED | xUDT: `udt.Udt.balanceFromUnsafe` (line 97); receipt: `ickbValue(depositAmount, ...) * depositQuantity` (lines 124-128); deposit: `-ickbValue(cell.capacityFree, ...)` (lines 146-149); output cells gated by `!cell.outPoint` check (line 106) |
+| 3 | `IckbUdt.addCellDeps` overridden to add individual code deps | VERIFIED | `packages/core/src/udt.ts` line 168: `override addCellDeps`; adds xUDT code dep (line 171) and Logic code dep (line 173) with `depType: "code"` |
+| 4 | `IckbUdt.typeScriptFrom` static method computes iCKB UDT type script | VERIFIED | `packages/core/src/udt.ts` line 57: `static typeScriptFrom(udt: ccc.Script, ickbLogic: ccc.Script): ccc.Script` |
+| 5 | `LogicManager` and `OwnedOwnerManager` no longer take `udtHandler` parameter; no `tx.addCellDeps(this.udtHandler.cellDeps)` calls remain | VERIFIED | `logic.ts` constructor has 3 params (script, cellDeps, daoManager); `owned_owner.ts` constructor has 3 params; grep confirms zero `udtHandler` references across both files |
+| 6 | `UdtHandler`, `UdtManager`, `ErrorTransactionInsufficientCoin`, `UdtCell`, `findUdts`, `addUdts`, `isUdtSymbol` deleted from `@ickb/utils` | VERIFIED | `packages/utils/src/udt.ts` deleted; `packages/utils/src/index.ts` has no `udt.js` export; grep across all packages confirms zero references |
+| 7 | SDK constructs `IckbUdt` with individual code OutPoints; passes `ickbUdt.script` to `OrderManager` | VERIFIED | `packages/sdk/src/constants.ts` line 3: `import { IckbUdt, ... }`, line 63: `new IckbUdt(...)` with `MAINNET_XUDT_CODE`/`TESTNET_XUDT_CODE` and `MAINNET_LOGIC_CODE`/`TESTNET_LOGIC_CODE`; line 86: `OrderManager(d.order.script, d.order.cellDeps, ickbUdt.script)` |
+| 8 | `pnpm check:full` passes | VERIFIED | Exit code 0; all 5 library packages lint, build, and test clean |
+
+**Score:** 8/8 truths verified
+
+### Required Artifacts
+
+| Artifact | Expected | Status | Details |
+|----------|----------|--------|---------|
+| `packages/core/src/udt.ts` | IckbUdt class extending udt.Udt with infoFrom, addCellDeps, typeScriptFrom | VERIFIED | 249 lines; `class IckbUdt extends udt.Udt` at line 20; all three overrides present |
+| `packages/core/src/logic.ts` | LogicManager without udtHandler parameter | VERIFIED | Constructor at line 28-32: 3 params only; zero `udtHandler` references |
+| `packages/core/src/owned_owner.ts` | OwnedOwnerManager without udtHandler parameter | VERIFIED | Constructor at line 23-27: 3 params only; zero `udtHandler` references |
+| `packages/core/package.json` | `@ckb-ccc/udt` dependency | VERIFIED | Line 57: `"@ckb-ccc/udt": "catalog:"` |
+| `pnpm-workspace.yaml` | Catalog entry for `@ckb-ccc/udt` | VERIFIED | Line 16: `"@ckb-ccc/udt": ^1.12.2` |
+| `packages/utils/src/udt.ts` | Deleted | VERIFIED | File does not exist; confirmed by `test -f` returning false |
+| `packages/utils/src/index.ts` | Barrel export without `udt.js` | VERIFIED | 3 lines only: codec.js, heap.js, utils.js |
+| `packages/sdk/src/constants.ts` | `getConfig()` constructing `IckbUdt` with code OutPoints | VERIFIED | `new IckbUdt(` at line 63; `IckbUdt.typeScriptFrom` at line 69; 4 code OutPoint constants added (lines 168-202) |
+
+### Key Link Verification
+
+| From | To | Via | Status | Details |
+|------|----|-----|--------|---------|
+| `packages/core/src/udt.ts` | `@ckb-ccc/udt` | `import { udt } from '@ckb-ccc/udt'` | WIRED | Line 2: exact import; `udt.Udt`, `udt.UdtInfo`, `udt.UdtInfoLike` all used |
+| `packages/core/src/udt.ts` | `packages/core/src/entities.ts` | `ReceiptData.decode` for receipt cell valuation | WIRED | Line 3: `import { ReceiptData }`, line 122: `ReceiptData.decode(cell.outputData)` |
+| `packages/core/src/udt.ts` | `@ickb/dao` | `daoManager.isDeposit` for deposit cell identification | WIRED | Line 4: `import type { DaoManager }`, line 137: `this.daoManager.isDeposit(cell as ccc.Cell)` |
+| `packages/sdk/src/constants.ts` | `packages/core/src/udt.ts` | `import { IckbUdt }` and constructor call | WIRED | Line 2: `import { IckbUdt, ... } from "@ickb/core"`, line 63: `new IckbUdt(...)` |
+| `packages/sdk/src/constants.ts` | `packages/core/src/udt.ts` | `IckbUdt.typeScriptFrom` for script computation | WIRED | Line 69: `IckbUdt.typeScriptFrom(ccc.Script.from(d.udt.script), ccc.Script.from(d.logic.script))` |
+
+### Requirements Coverage
+
+| Requirement | Source Plan | Description | Status | Evidence |
+|-------------|------------|-------------|--------|----------|
+| SMTX-05 | 05-01, 05-02 | UDT handler registration replaced by direct Udt instance usage | SATISFIED | LogicManager/OwnedOwnerManager no longer take `udtHandler`; UdtHandler/UdtManager/etc deleted from utils; SDK uses `IckbUdt` instance |
+| SMTX-07 | 05-01, 05-02 | IckbUdtManager multi-representation UDT balance logic survives intact | SATISFIED | `IckbUdt.infoFrom` handles xUDT (positive), receipts (positive via `ickbValue`), deposits (negative via `ickbValue`); conservation law encoded in sign conventions |
+| SMTX-10 | 05-01, 05-02 | Deprecated CCC API calls replaced | SATISFIED | grep confirms zero `udtBalanceFrom`, `getInputsUdtBalance`, `getOutputsUdtBalance`, `completeInputsByUdt` across all packages |
+| UDT-04 | 05-01, 05-02 | `IckbUdt extends udt.Udt` with infoFrom, addCellDeps, typeScriptFrom; managers cleaned | SATISFIED | Full implementation verified; `typeScriptFrom` replaces `calculateScript`; all 4 manager `udtHandler.cellDeps` call sites removed |
+
+All 4 requirements from plan frontmatter are satisfied. No orphaned requirements found (no additional Phase 5 requirements in REQUIREMENTS.md traceability table beyond those declared).
+
+### Anti-Patterns Found
+
+No anti-patterns detected in modified files:
+- No TODO/FIXME/HACK/PLACEHOLDER comments
+- No empty return stubs (`return null`, `return {}`, etc.)
+- No console-only handlers
+- All method overrides have substantive implementations
+
+| File | Line | Pattern | Severity | Impact |
+|------|------|---------|----------|--------|
+| — | — | — | — | No issues found |
+
+### Human Verification Required
+
+None required. All success criteria are verifiable through static code analysis and `pnpm check:full`.
+
+The following items are noted as runtime-only and untestable without a live CKB node, but they are architectural/wiring correct:
+
+1. **`infoFrom` header fetch correctness** — The `client.getTransactionWithHeader(cell.outPoint.txHash)` calls fetch headers correctly only when connected to a real CKB RPC. Static analysis confirms the call pattern matches Phase 1's established pattern (replacing `getHeader()`). No human action needed for phase acceptance; this is a runtime concern for Phase 6/7.
+
+### Gaps Summary
+
+No gaps found. All 8 observable truths are verified. The phase goal is fully achieved:
+
+- `IckbUdt extends udt.Udt` is implemented with a substantive `infoFrom` override that handles all three iCKB cell representations
+- `addCellDeps` adds individual code deps (not dep group) as specified
+- `typeScriptFrom` static method correctly computes the UDT type script
+- All `udtHandler` references removed from `LogicManager` and `OwnedOwnerManager`
+- The entire UDT infrastructure (`UdtHandler`, `UdtManager`, `ErrorTransactionInsufficientCoin`, `UdtCell`, `findUdts`, `addUdts`, `isUdtSymbol`) is deleted from `@ickb/utils`
+- SDK wiring is complete: `IckbUdt` constructed with correct code OutPoints for mainnet and testnet; `IckbUdt.typeScriptFrom` used for script computation; `LogicManager` and `OwnedOwnerManager` constructed without `ickbUdt`
+- `pnpm check:full` passes with exit code 0 (both `check:fresh` and `check:ci` stages)
+
+---
+
+_Verified: 2026-02-26T17:00:00Z_
+_Verifier: AI Coworker (gsd-verifier)_
From 64e60977fba842694a448a6881afc72edf660844 Mon Sep 17 00:00:00 2001
From: phroi <90913182+phroi@users.noreply.github.com>
Date: Thu, 26 Feb 2026 22:34:33 +0000
Subject: [PATCH 2/5] deps: add @ckb-ccc/udt to workspace catalog
Add @ckb-ccc/udt as a catalog dependency and wire it into @ickb/core.
This provides the udt.Udt base class that IckbUdt will extend.
---
package.json | 2 +-
packages/core/package.json | 1 +
pnpm-lock.yaml | 163 +++++++++++++++++++------------------
pnpm-workspace.yaml | 1 +
4 files changed, 86 insertions(+), 81 deletions(-)
diff --git a/package.json b/package.json
index 7671183..57eeac7 100644
--- a/package.json
+++ b/package.json
@@ -39,5 +39,5 @@
"typescript-eslint": "^8.56.1",
"vitest": "^3.2.4"
},
- "packageManager": "pnpm@10.30.2+sha512.36cdc707e7b7940a988c9c1ecf88d084f8514b5c3f085f53a2e244c2921d3b2545bc20dd4ebe1fc245feec463bb298aecea7a63ed1f7680b877dc6379d8d0cb4"
+ "packageManager": "pnpm@10.30.3+sha512.c961d1e0a2d8e354ecaa5166b822516668b7f44cb5bd95122d590dd81922f606f5473b6d23ec4a5be05e7fcd18e8488d47d978bbe981872f1145d06e9a740017"
}
diff --git a/packages/core/package.json b/packages/core/package.json
index 4ccd4a3..1d5ae7e 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -53,6 +53,7 @@
},
"dependencies": {
"@ckb-ccc/core": "catalog:",
+ "@ckb-ccc/udt": "catalog:",
"@ickb/dao": "workspace:*",
"@ickb/utils": "workspace:*"
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1ba306a..384969f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -18,7 +18,7 @@ importers:
devDependencies:
'@anthropic-ai/claude-code':
specifier: latest
- version: 2.1.53
+ version: 2.1.58
'@changesets/changelog-github':
specifier: ^0.5.2
version: 0.5.2
@@ -30,7 +30,7 @@ importers:
version: 9.39.3
'@typescript/native-preview':
specifier: latest
- version: 7.0.0-dev.20260224.1
+ version: 7.0.0-dev.20260225.1
'@vitest/coverage-v8':
specifier: 3.2.4
version: 3.2.4(vitest@3.2.4(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.2))
@@ -539,7 +539,7 @@ importers:
version: 4.3.0(prettier@3.8.1)(typescript@5.9.3)
tsdown:
specifier: 0.19.0-beta.3
- version: 0.19.0-beta.3(@typescript/native-preview@7.0.0-dev.20260224.1)(synckit@0.11.12)(typescript@5.9.3)
+ version: 0.19.0-beta.3(@typescript/native-preview@7.0.0-dev.20260225.1)(synckit@0.11.12)(typescript@5.9.3)
typescript:
specifier: ^5.9.2
version: 5.9.3
@@ -972,7 +972,7 @@ importers:
version: 4.3.0(prettier@3.8.1)(typescript@5.9.3)
tsdown:
specifier: 0.19.0-beta.3
- version: 0.19.0-beta.3(@typescript/native-preview@7.0.0-dev.20260224.1)(synckit@0.11.12)(typescript@5.9.3)
+ version: 0.19.0-beta.3(@typescript/native-preview@7.0.0-dev.20260225.1)(synckit@0.11.12)(typescript@5.9.3)
typescript:
specifier: ^5.9.2
version: 5.9.3
@@ -1145,6 +1145,9 @@ importers:
'@ckb-ccc/core':
specifier: workspace:*
version: link:../../forks/ccc/packages/core
+ '@ckb-ccc/udt':
+ specifier: workspace:*
+ version: link:../../forks/ccc/packages/udt
'@ickb/dao':
specifier: workspace:*
version: link:../dao
@@ -1203,8 +1206,8 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
- '@anthropic-ai/claude-code@2.1.53':
- resolution: {integrity: sha512-iKzS7+ktmxKoGXMyrahsMHpwwI36mIv674exgN7MdUySOFmo0MY4ahVxTMuZQkSXJVgKCcU2OpeSeH8Rz6WDzw==}
+ '@anthropic-ai/claude-code@2.1.58':
+ resolution: {integrity: sha512-NWTMNg6jIVtzUKkYj3qAqHXsD1clgVtKgJGmf1p2IXc5jhP1jOlCezSRvztuDH5omabVfkcrFVIMkZDX+Oe+6g==}
engines: {node: '>=18.0.0'}
hasBin: true
@@ -2265,17 +2268,17 @@ packages:
cpu: [x64]
os: [win32]
- '@shikijs/engine-oniguruma@3.22.0':
- resolution: {integrity: sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==}
+ '@shikijs/engine-oniguruma@3.23.0':
+ resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==}
- '@shikijs/langs@3.22.0':
- resolution: {integrity: sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==}
+ '@shikijs/langs@3.23.0':
+ resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==}
- '@shikijs/themes@3.22.0':
- resolution: {integrity: sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==}
+ '@shikijs/themes@3.23.0':
+ resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==}
- '@shikijs/types@3.22.0':
- resolution: {integrity: sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==}
+ '@shikijs/types@3.23.0':
+ resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==}
'@shikijs/vscode-textmate@10.0.2':
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
@@ -2512,43 +2515,43 @@ packages:
resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260224.1':
- resolution: {integrity: sha512-9VHXRhB7sM5DFqdlKaeDww8vuklgfzhYCjBazLCEnuFvb4J+rJ1DodLykc2bL+6kE8k6sdhYi3x8ipfbjtO44g==}
+ '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260225.1':
+ resolution: {integrity: sha512-3qSsqv7FmM4z09wEpEXdhmgMfiJF/OMOZa41AdgMsXTTRpX2/38hDg2KGhi3fc24M2T3MnLPLTqw6HyTOBaV1Q==}
cpu: [arm64]
os: [darwin]
- '@typescript/native-preview-darwin-x64@7.0.0-dev.20260224.1':
- resolution: {integrity: sha512-uCHipPRcIhHnvb7lAM29MQ1QT9pZ+uirqtH630aOMFm8VG3j8mkxVM9iGRLx829n38DMSDLjc3joCrQO3+sDcQ==}
+ '@typescript/native-preview-darwin-x64@7.0.0-dev.20260225.1':
+ resolution: {integrity: sha512-F8ZCCX2UESHcbxvnkd1Dn5PTnOOgpGddFHYgn4usyWRMzNZLPP+YjyGALZe9zdR/D8L0uraND0Haok+TPq8xYg==}
cpu: [x64]
os: [darwin]
- '@typescript/native-preview-linux-arm64@7.0.0-dev.20260224.1':
- resolution: {integrity: sha512-yFEEq6hD2R70+lTogb211sPdCwz3H5hpYh0+YuKVMPsKo0oM8/jMvgjj2pyutmj/uCKLdbcJ9HP2vJ/13Szbcg==}
+ '@typescript/native-preview-linux-arm64@7.0.0-dev.20260225.1':
+ resolution: {integrity: sha512-Up8Z/QNcwce5C4rWnbLNW5w7lRARdyKZcNbB1NMnaswaGOBdeDmdP0wbVsOgJMoDp6vnun+EkvrSft8hWLLhIg==}
cpu: [arm64]
os: [linux]
- '@typescript/native-preview-linux-arm@7.0.0-dev.20260224.1':
- resolution: {integrity: sha512-cEWSRQ8b+CXdMJvoG18IjNTvBo+qT22B5imqm6nAssMpyHHQb62PvZGnrA8mPRQNPzLpa5F956j8GwAjyP8hBQ==}
+ '@typescript/native-preview-linux-arm@7.0.0-dev.20260225.1':
+ resolution: {integrity: sha512-Iu5rnCmqwGIMUu//BXkl9VQaxAAsqVvFhU4mJoNexNkMxPqVcu9quqYAouY7tN/95WcKzUsPpyRfkThdbNFO/g==}
cpu: [arm]
os: [linux]
- '@typescript/native-preview-linux-x64@7.0.0-dev.20260224.1':
- resolution: {integrity: sha512-zGz5kVcCeBRheQwA4jVTAxtbLsBsTkp9AEvWK5AlyCs1rQCUQobBhtx37X4VEmxn4ekIDMxYgaZdlZb7/PGp8w==}
+ '@typescript/native-preview-linux-x64@7.0.0-dev.20260225.1':
+ resolution: {integrity: sha512-WWjIfHCWlcriempYYc/sPJ3HFt6znNZKp60nvDNih0+wmxNqEfT5Yzu5zAY0awIe7XLelFSY+bolkpzMYVWEIQ==}
cpu: [x64]
os: [linux]
- '@typescript/native-preview-win32-arm64@7.0.0-dev.20260224.1':
- resolution: {integrity: sha512-A0f9ZDQqKvGk/an59HuAJuzoI/wMyrgTd69oX9gFCx7+5E/ajSdgv0Eg1Fco+nyLfT/UVM0CV3ERyWrKzx277w==}
+ '@typescript/native-preview-win32-arm64@7.0.0-dev.20260225.1':
+ resolution: {integrity: sha512-lmfQO+HdmPMk0dtPoNo8dZereTUYNQuapsAI7nFHCP8F25I8eGKKXY2nD1R8W1hp/LmVtske1pqKFNN6IOCt5g==}
cpu: [arm64]
os: [win32]
- '@typescript/native-preview-win32-x64@7.0.0-dev.20260224.1':
- resolution: {integrity: sha512-Se9JrcMdVLeDYMLn+CKEV3qy1yiildb5N23USGvnC9siNFalz8tVgd589dhRP+ywDhXnbIsZiFKDrZF/7B4wSQ==}
+ '@typescript/native-preview-win32-x64@7.0.0-dev.20260225.1':
+ resolution: {integrity: sha512-e4eJyzR9ne0XreqYgQNqfX7SNuaePxggnUtVrLERgBv25QKwdQl72GnSXDhdxZHzrb97YwumiXWMQQJj9h8NCg==}
cpu: [x64]
os: [win32]
- '@typescript/native-preview@7.0.0-dev.20260224.1':
- resolution: {integrity: sha512-PU0zBXLvz6RKxbIubT66RCnJXgScdDIhfmNMkvRhOnX/C4SZom5TFSn7BEHC3w8JPj7OSz5OYoubtV1Haty2GA==}
+ '@typescript/native-preview@7.0.0-dev.20260225.1':
+ resolution: {integrity: sha512-mUf1aON+eZLupLorX4214n4W6uWIz/lvNv81ErzjJylD/GyJPEJkvDLmgIK3bbvLpMwTRWdVJLhpLCah5Qe8iA==}
hasBin: true
'@vitejs/plugin-basic-ssl@1.2.0':
@@ -3576,15 +3579,15 @@ packages:
minimalistic-crypto-utils@1.0.1:
resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==}
- minimatch@10.2.2:
- resolution: {integrity: sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==}
+ minimatch@10.2.4:
+ resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
engines: {node: 18 || 20 || >=22}
- minimatch@3.1.3:
- resolution: {integrity: sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==}
+ minimatch@3.1.5:
+ resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
- minimatch@9.0.6:
- resolution: {integrity: sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==}
+ minimatch@9.0.8:
+ resolution: {integrity: sha512-reYkDYtj/b19TeqbNZCV4q9t+Yxylf/rYBsLb42SXJatTv4/ylq5lEiAmhA/IToxO7NI2UzNMghHoHuaqDkAjw==}
engines: {node: '>=16 || 14 >=14.17'}
minipass@7.1.3:
@@ -4385,7 +4388,7 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
- '@anthropic-ai/claude-code@2.1.53':
+ '@anthropic-ai/claude-code@2.1.58':
optionalDependencies:
'@img/sharp-darwin-arm64': 0.34.5
'@img/sharp-darwin-x64': 0.34.5
@@ -5068,7 +5071,7 @@ snapshots:
dependencies:
'@eslint/object-schema': 2.1.7
debug: 4.4.3
- minimatch: 3.1.3
+ minimatch: 3.1.5
transitivePeerDependencies:
- supports-color
@@ -5089,7 +5092,7 @@ snapshots:
ignore: 5.3.2
import-fresh: 3.3.1
js-yaml: 4.1.1
- minimatch: 3.1.3
+ minimatch: 3.1.5
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
@@ -5105,10 +5108,10 @@ snapshots:
'@gerrit0/mini-shiki@3.22.0':
dependencies:
- '@shikijs/engine-oniguruma': 3.22.0
- '@shikijs/langs': 3.22.0
- '@shikijs/themes': 3.22.0
- '@shikijs/types': 3.22.0
+ '@shikijs/engine-oniguruma': 3.23.0
+ '@shikijs/langs': 3.23.0
+ '@shikijs/themes': 3.23.0
+ '@shikijs/types': 3.23.0
'@shikijs/vscode-textmate': 10.0.2
'@humanfs/core@0.19.1': {}
@@ -5519,20 +5522,20 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.59.0':
optional: true
- '@shikijs/engine-oniguruma@3.22.0':
+ '@shikijs/engine-oniguruma@3.23.0':
dependencies:
- '@shikijs/types': 3.22.0
+ '@shikijs/types': 3.23.0
'@shikijs/vscode-textmate': 10.0.2
- '@shikijs/langs@3.22.0':
+ '@shikijs/langs@3.23.0':
dependencies:
- '@shikijs/types': 3.22.0
+ '@shikijs/types': 3.23.0
- '@shikijs/themes@3.22.0':
+ '@shikijs/themes@3.23.0':
dependencies:
- '@shikijs/types': 3.22.0
+ '@shikijs/types': 3.23.0
- '@shikijs/types@3.22.0':
+ '@shikijs/types@3.23.0':
dependencies:
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
@@ -5762,7 +5765,7 @@ snapshots:
'@typescript-eslint/types': 8.56.1
'@typescript-eslint/visitor-keys': 8.56.1
debug: 4.4.3
- minimatch: 10.2.2
+ minimatch: 10.2.4
semver: 7.7.4
tinyglobby: 0.2.15
ts-api-utils: 2.4.0(typescript@5.9.3)
@@ -5786,36 +5789,36 @@ snapshots:
'@typescript-eslint/types': 8.56.1
eslint-visitor-keys: 5.0.1
- '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260224.1':
+ '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260225.1':
optional: true
- '@typescript/native-preview-darwin-x64@7.0.0-dev.20260224.1':
+ '@typescript/native-preview-darwin-x64@7.0.0-dev.20260225.1':
optional: true
- '@typescript/native-preview-linux-arm64@7.0.0-dev.20260224.1':
+ '@typescript/native-preview-linux-arm64@7.0.0-dev.20260225.1':
optional: true
- '@typescript/native-preview-linux-arm@7.0.0-dev.20260224.1':
+ '@typescript/native-preview-linux-arm@7.0.0-dev.20260225.1':
optional: true
- '@typescript/native-preview-linux-x64@7.0.0-dev.20260224.1':
+ '@typescript/native-preview-linux-x64@7.0.0-dev.20260225.1':
optional: true
- '@typescript/native-preview-win32-arm64@7.0.0-dev.20260224.1':
+ '@typescript/native-preview-win32-arm64@7.0.0-dev.20260225.1':
optional: true
- '@typescript/native-preview-win32-x64@7.0.0-dev.20260224.1':
+ '@typescript/native-preview-win32-x64@7.0.0-dev.20260225.1':
optional: true
- '@typescript/native-preview@7.0.0-dev.20260224.1':
+ '@typescript/native-preview@7.0.0-dev.20260225.1':
optionalDependencies:
- '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260224.1
- '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260224.1
- '@typescript/native-preview-linux-arm': 7.0.0-dev.20260224.1
- '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260224.1
- '@typescript/native-preview-linux-x64': 7.0.0-dev.20260224.1
- '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260224.1
- '@typescript/native-preview-win32-x64': 7.0.0-dev.20260224.1
+ '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260225.1
+ '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260225.1
+ '@typescript/native-preview-linux-arm': 7.0.0-dev.20260225.1
+ '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260225.1
+ '@typescript/native-preview-linux-x64': 7.0.0-dev.20260225.1
+ '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260225.1
+ '@typescript/native-preview-win32-x64': 7.0.0-dev.20260225.1
'@vitejs/plugin-basic-ssl@1.2.0(vite@6.4.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.2))':
dependencies:
@@ -6104,7 +6107,7 @@ snapshots:
copyfiles@2.4.1:
dependencies:
glob: 7.2.3
- minimatch: 3.1.3
+ minimatch: 3.1.5
mkdirp: 1.0.4
noms: 0.0.0
through2: 2.0.5
@@ -6328,7 +6331,7 @@ snapshots:
is-glob: 4.0.3
json-stable-stringify-without-jsonify: 1.0.1
lodash.merge: 4.6.2
- minimatch: 3.1.3
+ minimatch: 3.1.5
natural-compare: 1.4.0
optionator: 0.9.4
optionalDependencies:
@@ -6502,14 +6505,14 @@ snapshots:
dependencies:
foreground-child: 3.3.1
jackspeak: 3.4.3
- minimatch: 9.0.6
+ minimatch: 9.0.8
minipass: 7.1.3
package-json-from-dist: 1.0.1
path-scurry: 1.11.1
glob@13.0.6:
dependencies:
- minimatch: 10.2.2
+ minimatch: 10.2.4
minipass: 7.1.3
path-scurry: 2.0.2
@@ -6518,7 +6521,7 @@ snapshots:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
- minimatch: 3.1.3
+ minimatch: 3.1.5
once: 1.4.0
path-is-absolute: 1.0.1
@@ -6838,15 +6841,15 @@ snapshots:
minimalistic-crypto-utils@1.0.1: {}
- minimatch@10.2.2:
+ minimatch@10.2.4:
dependencies:
brace-expansion: 5.0.3
- minimatch@3.1.3:
+ minimatch@3.1.5:
dependencies:
brace-expansion: 1.1.12
- minimatch@9.0.6:
+ minimatch@9.0.8:
dependencies:
brace-expansion: 5.0.3
@@ -7047,7 +7050,7 @@ snapshots:
glob: 13.0.6
package-json-from-dist: 1.0.1
- rolldown-plugin-dts@0.20.0(@typescript/native-preview@7.0.0-dev.20260224.1)(rolldown@1.0.0-beta.58)(typescript@5.9.3):
+ rolldown-plugin-dts@0.20.0(@typescript/native-preview@7.0.0-dev.20260225.1)(rolldown@1.0.0-beta.58)(typescript@5.9.3):
dependencies:
'@babel/generator': 7.29.1
'@babel/parser': 7.29.0
@@ -7059,7 +7062,7 @@ snapshots:
obug: 2.1.1
rolldown: 1.0.0-beta.58
optionalDependencies:
- '@typescript/native-preview': 7.0.0-dev.20260224.1
+ '@typescript/native-preview': 7.0.0-dev.20260225.1
typescript: 5.9.3
transitivePeerDependencies:
- oxc-resolver
@@ -7230,7 +7233,7 @@ snapshots:
dependencies:
'@istanbuljs/schema': 0.1.3
glob: 10.5.0
- minimatch: 10.2.2
+ minimatch: 10.2.4
through2@2.0.5:
dependencies:
@@ -7266,7 +7269,7 @@ snapshots:
dependencies:
typescript: 5.9.3
- tsdown@0.19.0-beta.3(@typescript/native-preview@7.0.0-dev.20260224.1)(synckit@0.11.12)(typescript@5.9.3):
+ tsdown@0.19.0-beta.3(@typescript/native-preview@7.0.0-dev.20260225.1)(synckit@0.11.12)(typescript@5.9.3):
dependencies:
ansis: 4.2.0
cac: 6.7.14
@@ -7277,7 +7280,7 @@ snapshots:
obug: 2.1.1
picomatch: 4.0.3
rolldown: 1.0.0-beta.58
- rolldown-plugin-dts: 0.20.0(@typescript/native-preview@7.0.0-dev.20260224.1)(rolldown@1.0.0-beta.58)(typescript@5.9.3)
+ rolldown-plugin-dts: 0.20.0(@typescript/native-preview@7.0.0-dev.20260225.1)(rolldown@1.0.0-beta.58)(typescript@5.9.3)
semver: 7.7.4
tinyexec: 1.0.2
tinyglobby: 0.2.15
@@ -7311,7 +7314,7 @@ snapshots:
'@gerrit0/mini-shiki': 3.22.0
lunr: 2.3.9
markdown-it: 14.1.1
- minimatch: 9.0.6
+ minimatch: 9.0.8
typescript: 5.9.3
yaml: 2.8.2
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index d8a154b..f1b0c9a 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -13,6 +13,7 @@ packages:
catalog:
"@ckb-ccc/core": ^1.12.2
+ "@ckb-ccc/udt": ^1.12.2
"@types/node": ^24.8.1
minimumReleaseAge: 1440
From 4d8ef7e23c158ace09dd43f35f604bd74c935382 Mon Sep 17 00:00:00 2001
From: phroi <90913182+phroi@users.noreply.github.com>
Date: Thu, 26 Feb 2026 22:34:47 +0000
Subject: [PATCH 3/5] refactor(core): implement IckbUdt extending CCC udt.Udt
Rewrite IckbUdtManager as IckbUdt, extending the CCC udt.Udt base class
instead of the homegrown UdtManager. The new class:
- Accepts code OutPoints (xUDT, Logic) instead of pre-built CellDep arrays
- Overrides infoFrom() to value iCKB's three cell representations
(xUDT cells, receipt cells, deposit cells)
- Overrides addCellDeps() to add individual code deps
- Exposes typeScriptFrom() static for computing the UDT type script
Also widen DaoManager.isDeposit() to accept CellAny (needed by
IckbUdt.infoFrom which works with CellAny, not just Cell).
---
packages/core/src/udt.ts | 243 ++++++++++++++++++++++-----------------
packages/dao/src/dao.ts | 2 +-
2 files changed, 139 insertions(+), 106 deletions(-)
diff --git a/packages/core/src/udt.ts b/packages/core/src/udt.ts
index 276fcb2..591d278 100644
--- a/packages/core/src/udt.ts
+++ b/packages/core/src/udt.ts
@@ -1,50 +1,60 @@
import { ccc } from "@ckb-ccc/core";
+import { udt } from "@ckb-ccc/udt";
import { ReceiptData } from "./entities.js";
import type { DaoManager } from "@ickb/dao";
-import { UdtManager, type ExchangeRatio, type UdtHandler } from "@ickb/utils";
+import type { ExchangeRatio } from "@ickb/utils";
/**
- * IckbUdtManager is a class that implements the UdtHandler interface.
- * It is responsible for handling UDT (User Defined Token) operations related to iCKB.
+ * IckbUdt extends CCC's Udt class to provide accurate multi-representation
+ * balance for iCKB tokens. The iCKB conservation law is:
+ * Input UDT + Input Receipts = Output UDT + Input Deposits
+ *
+ * `infoFrom` values three cell types:
+ * - xUDT cells: positive balance (standard UDT)
+ * - Receipt cells: positive balance (input only, valued via ickbValue)
+ * - Deposit cells: negative balance (input only, withdrawal reduces UDT supply)
+ *
+ * Output cells without outPoint are naturally excluded from receipt/deposit
+ * processing, since only input cells (resolved by CellInput.getCell()) have outPoint.
*/
-export class IckbUdtManager extends UdtManager implements UdtHandler {
+export class IckbUdt extends udt.Udt {
+ public readonly logicCode: ccc.OutPoint;
+ public readonly logicScript: ccc.Script;
+ public readonly daoManager: DaoManager;
+
/**
- * Creates an instance of IckbUdtManager.
- * @param script - The script associated with the UDT.
- * @param cellDeps - An array of cell dependencies.
+ * Creates an instance of IckbUdt.
+ *
+ * @param code - The xUDT code cell OutPoint (passed to base Udt/Trait).
+ * @param script - The iCKB UDT type script (token identity via args).
+ * @param logicCode - The iCKB Logic code cell OutPoint.
* @param logicScript - The iCKB Logic script.
- * @param daoManager - The DAO manager instance.
+ * @param daoManager - The DAO manager instance for deposit cell identification.
*/
constructor(
- script: ccc.Script,
- cellDeps: ccc.CellDep[],
- public readonly logicScript: ccc.Script,
- public readonly daoManager: DaoManager,
+ code: ccc.OutPointLike,
+ script: ccc.ScriptLike,
+ logicCode: ccc.OutPointLike,
+ logicScript: ccc.ScriptLike,
+ daoManager: DaoManager,
) {
- super(script, cellDeps, "iCKB", "iCKB", 8);
+ super(code, script);
+ this.logicCode = ccc.OutPoint.from(logicCode);
+ this.logicScript = ccc.Script.from(logicScript);
+ this.daoManager = daoManager;
}
/**
- * Calculates and returns the iCKB UDT script by combining the existing UDT script
- * with the iCKB logic script’s hash.
- *
- * This method takes the raw UDT script (user-defined token) and the iCKB logic script,
- * then recalculates the UDT script’s `args` field by concatenating:
- * 1. The iCKB logic script hash
- * 2. A fixed 4-byte little-endian length postfix (`"00000080"`)
+ * Computes the iCKB UDT type script from raw UDT and Logic scripts.
*
- * This is used to ensure that the UDT cell carries the correct iCKB lock logic
- * within its arguments.
+ * Concatenates the iCKB logic script hash with a fixed 4-byte LE length
+ * postfix ("00000080") to form the UDT type script args.
*
- * @param udt - The original UDT (user-defined token) script whose codeHash and hashType
- * will be reused for the new script.
- * @param ickbLogic - The iCKB logic script whose `hash()` will be prepended
- * to the UDT args.
- * @returns A new `ccc.Script` instance with:
- * - `codeHash` and `hashType` copied from the `udt` parameter
- * - `args` set to the concatenation of `ickbLogic.hash()` and `"00000080"`
+ * @param udt - The raw xUDT script (codeHash and hashType reused).
+ * @param ickbLogic - The iCKB logic script (hash used for args).
+ * @returns A new Script with the computed args.
*/
- static calculateScript(udt: ccc.Script, ickbLogic: ccc.Script): ccc.Script {
+ static typeScriptFrom(udt: ccc.Script, ickbLogic: ccc.Script): ccc.Script {
const { codeHash, hashType } = udt;
return new ccc.Script(
codeHash,
@@ -54,90 +64,113 @@ export class IckbUdtManager extends UdtManager implements UdtHandler {
}
/**
- * Asynchronously retrieves the iCKB UDT input balance (token amount and capacity)
- * for a given transaction.
+ * Computes UDT balance info for iCKB's three cell representations.
+ *
+ * For each cell:
+ * - xUDT cell (type === this.script, data >= 16 bytes): adds positive balance
+ * - Receipt cell (type === logicScript, has outPoint): adds positive balance
+ * via ickbValue of deposit amount * quantity
+ * - Deposit cell (lock === logicScript, isDeposit, has outPoint): adds negative
+ * balance via ickbValue of free capacity (withdrawal reduces UDT supply)
*
- * @param client - The CKB client to query cell data.
- * @param txLike - The transaction whose inputs are to be balanced.
- * @returns A promise resolving to a tuple:
- * - [0]: Total iCKB UDT amount in inputs (as `ccc.FixedPoint`).
- * - [1]: Total capacity in UDT-related inputs (as `ccc.FixedPoint`).
+ * Cells without outPoint (output cells from getOutputsInfo) skip receipt/deposit
+ * processing -- correct by design since these only appear as inputs.
+ *
+ * @param client - CKB client for header fetches (receipt/deposit valuation).
+ * @param cells - Cell or array of cells to evaluate.
+ * @param acc - Optional accumulator for running totals.
+ * @returns UdtInfo with balance, capacity, and count.
*/
- override async getInputsUdtBalance(
+ override async infoFrom(
client: ccc.Client,
- txLike: ccc.TransactionLike,
- ): Promise<[ccc.FixedPoint, ccc.FixedPoint]> {
- const tx = ccc.Transaction.from(txLike);
- return ccc.reduceAsync(
- tx.inputs,
- async (acc, input) => {
- // Get all cell info
- await input.completeExtraInfos(client);
- const { previousOutput: outPoint, cellOutput, outputData } = input;
-
- // Input is not well defined
- if (!cellOutput || !outputData) {
- throw new Error("Unable to complete input");
- }
-
- const { type, lock } = cellOutput;
-
- if (!type) {
- return acc;
+ cells: ccc.CellAnyLike | ccc.CellAnyLike[],
+ acc?: udt.UdtInfoLike,
+ ): Promise {
+ const info = udt.UdtInfo.from(acc).clone();
+
+ for (const cellLike of [cells].flat()) {
+ const cell = ccc.CellAny.from(cellLike);
+
+ // Standard xUDT cell -- delegate to base class pattern
+ if (this.isUdt(cell)) {
+ info.addAssign({
+ balance: udt.Udt.balanceFromUnsafe(cell.outputData),
+ capacity: cell.cellOutput.capacity,
+ count: 1,
+ });
+ continue;
+ }
+
+ // Receipt and deposit cells need outPoint for header fetch.
+ // Output cells (no outPoint) are skipped -- correct by design.
+ if (!cell.outPoint) {
+ continue;
+ }
+
+ const { type, lock } = cell.cellOutput;
+
+ // Receipt cell: type === logicScript
+ if (type && this.logicScript.eq(type)) {
+ const txWithHeader = await client.getTransactionWithHeader(
+ cell.outPoint.txHash,
+ );
+ if (!txWithHeader?.header) {
+ throw new Error("Header not found for txHash");
}
- const cell = new ccc.Cell(outPoint, cellOutput, outputData);
-
- const [udtValue, capacity] = acc;
-
- // An iCKB UDT Cell
- if (this.isUdt(cell)) {
- return [
- udtValue + ccc.numFromBytes(ccc.bytesFrom(outputData).slice(0, 16)),
- capacity + cellOutput.capacity,
- ];
+ const { depositQuantity, depositAmount } =
+ ReceiptData.decode(cell.outputData);
+ info.addAssign({
+ balance: ickbValue(depositAmount, txWithHeader.header) *
+ depositQuantity,
+ capacity: cell.cellOutput.capacity,
+ count: 1,
+ });
+ continue;
+ }
+
+ // Deposit cell: lock === logicScript AND isDeposit
+ // Output cells are gated by the !cell.outPoint check above and never reach here.
+ if (
+ this.logicScript.eq(lock) &&
+ this.daoManager.isDeposit(cell)
+ ) {
+ const txWithHeader = await client.getTransactionWithHeader(
+ cell.outPoint.txHash,
+ );
+ if (!txWithHeader?.header) {
+ throw new Error("Header not found for txHash");
}
- // An iCKB Receipt
- if (this.logicScript.eq(type)) {
- // Get header of Receipt cell
- const txWithHeader = await client.getTransactionWithHeader(
- outPoint.txHash,
- );
- if (!txWithHeader?.header) {
- throw new Error("Header not found for txHash");
- }
-
- const { depositQuantity, depositAmount } =
- ReceiptData.decode(outputData);
-
- return [
- udtValue +
- ickbValue(depositAmount, txWithHeader.header) * depositQuantity,
- capacity + cellOutput.capacity,
- ];
- }
+ info.addAssign({
+ balance: -ickbValue(cell.capacityFree, txWithHeader.header),
+ capacity: cell.cellOutput.capacity,
+ count: 1,
+ });
+ continue;
+ }
+ }
- // An iCKB Deposit for which the withdrawal is being requested
- if (this.logicScript.eq(lock) && this.daoManager.isDeposit(cell)) {
- // Get header of Deposit cell
- const txWithHeader = await client.getTransactionWithHeader(
- outPoint.txHash,
- );
- if (!txWithHeader?.header) {
- throw new Error("Header not found for txHash");
- }
-
- return [
- udtValue - ickbValue(cell.capacityFree, txWithHeader.header),
- capacity + cellOutput.capacity,
- ];
- }
+ return info;
+ }
- return acc;
- },
- [0n, 0n],
- );
+ /**
+ * Adds iCKB-specific cell dependencies to a transaction.
+ *
+ * Adds individual code deps (not dep group) for:
+ * - xUDT code cell (this.code from ssri.Trait)
+ * - iCKB Logic code cell (this.logicCode)
+ *
+ * @param txLike - The transaction to add cell deps to.
+ * @returns The transaction with cell deps added.
+ */
+ override addCellDeps(txLike: ccc.TransactionLike): ccc.Transaction {
+ const tx = ccc.Transaction.from(txLike);
+ // xUDT code dep
+ tx.addCellDeps({ outPoint: this.code, depType: "code" });
+ // iCKB Logic code dep
+ tx.addCellDeps({ outPoint: this.logicCode, depType: "code" });
+ return tx;
}
}
diff --git a/packages/dao/src/dao.ts b/packages/dao/src/dao.ts
index 23382f2..d01b9d6 100644
--- a/packages/dao/src/dao.ts
+++ b/packages/dao/src/dao.ts
@@ -27,7 +27,7 @@ export class DaoManager implements ScriptDeps {
* @param cell - The cell to check.
* @returns True if the cell is a deposit, otherwise false.
*/
- isDeposit(cell: ccc.Cell): boolean {
+ isDeposit(cell: ccc.CellAny): boolean {
const {
cellOutput: { type },
outputData,
From 08012132c0c4874215adba3e2117d1e7b804bd76 Mon Sep 17 00:00:00 2001
From: phroi <90913182+phroi@users.noreply.github.com>
Date: Thu, 26 Feb 2026 22:34:58 +0000
Subject: [PATCH 4/5] refactor: migrate from UdtHandler to IckbUdt across
packages
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Remove udtHandler parameter from LogicManager and OwnedOwnerManager
constructors; callers now manage UDT cellDeps via IckbUdt.addCellDeps()
- Replace UdtHandler with plain udtScript in OrderManager
- Wire getConfig() to construct IckbUdt with code OutPoints,
computing the UDT type script dynamically via IckbUdt.typeScriptFrom()
- Delete UDT infrastructure from @ickb/utils (UdtHandler, UdtManager,
UdtCell, ErrorTransactionInsufficientCoin) — all superseded by CCC
---
packages/core/src/logic.ts | 11 +-
packages/core/src/owned_owner.ts | 11 +-
packages/order/src/order.ts | 26 +-
packages/sdk/src/constants.ts | 99 ++++++--
packages/utils/src/index.ts | 1 -
packages/utils/src/udt.ts | 405 -------------------------------
6 files changed, 110 insertions(+), 443 deletions(-)
delete mode 100644 packages/utils/src/udt.ts
diff --git a/packages/core/src/logic.ts b/packages/core/src/logic.ts
index 0311b3a..a8ef205 100644
--- a/packages/core/src/logic.ts
+++ b/packages/core/src/logic.ts
@@ -3,7 +3,6 @@ import { DaoManager } from "@ickb/dao";
import {
defaultFindCellsLimit,
type ScriptDeps,
- type UdtHandler,
unique,
} from "@ickb/utils";
import {
@@ -25,13 +24,11 @@ export class LogicManager implements ScriptDeps {
* @param script - The script associated with the manager.
* @param cellDeps - The cell dependencies for the manager.
* @param daoManager - The DAO manager for handling deposits and receipts.
- * @param udtHandler - The handler for User Defined Tokens (UDTs).
*/
constructor(
public readonly script: ccc.Script,
public readonly cellDeps: ccc.CellDep[],
public readonly daoManager: DaoManager,
- public readonly udtHandler: UdtHandler,
) {}
/**
@@ -63,6 +60,9 @@ export class LogicManager implements ScriptDeps {
* @param depositQuantity - The quantity of deposits.
* @param depositAmount - The amount of each deposit.
* @param lock - The lock script for the output receipt cell.
+ *
+ * @remarks Caller must ensure UDT cellDeps are added to the transaction
+ * (e.g., via ickbUdt.addCellDeps(tx)).
*/
async deposit(
txLike: ccc.TransactionLike,
@@ -85,7 +85,6 @@ export class LogicManager implements ScriptDeps {
}
tx.addCellDeps(this.cellDeps);
- tx.addCellDeps(this.udtHandler.cellDeps);
const capacities = Array.from(
{ length: depositQuantity },
@@ -111,6 +110,9 @@ export class LogicManager implements ScriptDeps {
*
* @param txLike - The transaction to add the receipts to.
* @param receipts - The receipts to add to the transaction.
+ *
+ * @remarks Caller must ensure UDT cellDeps are added to the transaction
+ * (e.g., via ickbUdt.addCellDeps(tx)).
*/
completeDeposit(
txLike: ccc.TransactionLike,
@@ -122,7 +124,6 @@ export class LogicManager implements ScriptDeps {
}
tx.addCellDeps(this.cellDeps);
- tx.addCellDeps(this.udtHandler.cellDeps);
for (const r of receipts) {
const hash = r.header.header.hash;
diff --git a/packages/core/src/owned_owner.ts b/packages/core/src/owned_owner.ts
index ff63e45..154c3a3 100644
--- a/packages/core/src/owned_owner.ts
+++ b/packages/core/src/owned_owner.ts
@@ -3,7 +3,6 @@ import {
defaultFindCellsLimit,
unique,
type ScriptDeps,
- type UdtHandler,
} from "@ickb/utils";
import { daoCellFrom, DaoManager } from "@ickb/dao";
import { OwnerData } from "./entities.js";
@@ -20,13 +19,11 @@ export class OwnedOwnerManager implements ScriptDeps {
* @param script - The script associated with the OwnedOwner script.
* @param cellDeps - The cell dependencies for the OwnedOwner script.
* @param daoManager - The DAO manager for handling withdrawal requests.
- * @param udtHandler - The handler for User Defined Tokens (UDTs).
*/
constructor(
public readonly script: ccc.Script,
public readonly cellDeps: ccc.CellDep[],
public readonly daoManager: DaoManager,
- public readonly udtHandler: UdtHandler,
) {}
/**
@@ -64,6 +61,9 @@ export class OwnedOwnerManager implements ScriptDeps {
* @param options - Optional parameters for the withdrawal request.
* @param options.isReadyOnly - Whether to only process ready deposits (default: false).
* @returns void
+ *
+ * @remarks Caller must ensure UDT cellDeps are added to the transaction
+ * (e.g., via ickbUdt.addCellDeps(tx)).
*/
async requestWithdrawal(
txLike: ccc.TransactionLike,
@@ -92,7 +92,6 @@ export class OwnedOwnerManager implements ScriptDeps {
options,
);
tx.addCellDeps(this.cellDeps);
- tx.addCellDeps(this.udtHandler.cellDeps);
const outputData = OwnerData.encode({ ownedDistance: -deposits.length });
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -118,6 +117,9 @@ export class OwnedOwnerManager implements ScriptDeps {
* @param options - Optional parameters for the withdrawal process.
* @param options.isReadyOnly - Whether to only process ready withdrawal groups (default: false).
* @returns void
+ *
+ * @remarks Caller must ensure UDT cellDeps are added to the transaction
+ * (e.g., via ickbUdt.addCellDeps(tx)).
*/
async withdraw(
txLike: ccc.TransactionLike,
@@ -137,7 +139,6 @@ export class OwnedOwnerManager implements ScriptDeps {
}
tx.addCellDeps(this.cellDeps);
- tx.addCellDeps(this.udtHandler.cellDeps);
const requests = withdrawalGroups.map((g) => g.owned);
tx = await this.daoManager.withdraw(tx, requests, client);
diff --git a/packages/order/src/order.ts b/packages/order/src/order.ts
index c1463ea..525c72a 100644
--- a/packages/order/src/order.ts
+++ b/packages/order/src/order.ts
@@ -4,7 +4,6 @@ import {
defaultFindCellsLimit,
type ExchangeRatio,
type ScriptDeps,
- type UdtHandler,
type ValueComponents,
} from "@ickb/utils";
import { Info, OrderData, Ratio, Relative, type InfoLike } from "./entities.js";
@@ -19,12 +18,12 @@ export class OrderManager implements ScriptDeps {
*
* @param script - The order script.
* @param cellDeps - The cell dependencies for the order.
- * @param udtHandler - The handler for UDT (User Defined Token).
+ * @param udtScript - The UDT (User Defined Token) type script.
*/
constructor(
public readonly script: ccc.Script,
public readonly cellDeps: ccc.CellDep[],
- public readonly udtHandler: UdtHandler,
+ public readonly udtScript: ccc.Script,
) {}
/**
@@ -39,7 +38,7 @@ export class OrderManager implements ScriptDeps {
isOrder(cell: ccc.Cell): boolean {
return (
cell.cellOutput.lock.eq(this.script) &&
- Boolean(cell.cellOutput.type?.eq(this.udtHandler.script))
+ Boolean(cell.cellOutput.type?.eq(this.udtScript))
);
}
@@ -156,10 +155,12 @@ export class OrderManager implements ScriptDeps {
*
* The method performs the following:
* - Creates order data using the provided amounts and order information.
- * - Adds required cell dependencies and UDT handlers to the transaction.
+ * - Adds required cell dependencies to the transaction.
* - Appends the order cell to the outputs with the UDT data and adjusts the CKB capacity.
* - Appends a corresponding master cell immediately after the order cell.
*
+ * @remarks Caller must ensure UDT cellDeps are added to the transaction (e.g., via CCC Udt balance completion).
+ *
* @param txLike - The transaction to which the order will be added.
* @param lock - The lock script for the master cell.
* @param info - The information related to the order, usually calculated using OrderManager.convert.
@@ -187,13 +188,12 @@ export class OrderManager implements ScriptDeps {
});
tx.addCellDeps(this.cellDeps);
- tx.addCellDeps(this.udtHandler.cellDeps);
// Append order cell to Outputs
const position = tx.addOutput(
{
lock: this.script,
- type: this.udtHandler.script,
+ type: this.udtScript,
},
data.toBytes(),
);
@@ -215,6 +215,8 @@ export class OrderManager implements ScriptDeps {
* - Adds the original order as an input.
* - Creates an updated output with adjusted CKB capacity and UDT data.
*
+ * @remarks Caller must ensure UDT cellDeps are added to the transaction (e.g., via CCC Udt balance completion).
+ *
* @param txLike - The transaction to which the matches will be added.
* @param match - The match object containing partial matches.
*/
@@ -226,14 +228,13 @@ export class OrderManager implements ScriptDeps {
}
tx.addCellDeps(this.cellDeps);
- tx.addCellDeps(this.udtHandler.cellDeps);
for (const { order, ckbOut, udtOut } of partials) {
tx.addInput(order.cell);
tx.addOutput(
{
lock: this.script,
- type: this.udtHandler.script,
+ type: this.udtScript,
capacity: ckbOut,
},
OrderData.from({
@@ -495,6 +496,8 @@ export class OrderManager implements ScriptDeps {
*
* @param txLike - The transaction to which the groups will be added.
* @param groups - The array of OrderGroup instances to melt.
+ * @remarks Caller must ensure UDT cellDeps are added to the transaction (e.g., via CCC Udt balance completion).
+ *
* @param options - Optional parameters:
* @param options.isFulfilledOnly - If true, only groups with fulfilled orders will be melted.
*
@@ -516,7 +519,6 @@ export class OrderManager implements ScriptDeps {
return tx;
}
tx.addCellDeps(this.cellDeps);
- tx.addCellDeps(this.udtHandler.cellDeps);
for (const group of groups) {
tx.addInput(group.order.cell);
@@ -615,7 +617,7 @@ export class OrderManager implements ScriptDeps {
* Finds simple orders on the blockchain.
*
* Queries cells whose lock script equals the order script and whose type script
- * matches the UDT handler's script, returning only valid {@link OrderCell} instances.
+ * matches the UDT type script, returning only valid {@link OrderCell} instances.
*
* @param client – The client used to interact with the blockchain.
* @param limit – Maximum cells to scan per findCells batch.
@@ -632,7 +634,7 @@ export class OrderManager implements ScriptDeps {
script: this.script,
scriptType: "lock",
filter: {
- script: this.udtHandler.script,
+ script: this.udtScript,
},
scriptSearchMode: "exact",
withData: true,
diff --git a/packages/sdk/src/constants.ts b/packages/sdk/src/constants.ts
index d2f271c..ff9c522 100644
--- a/packages/sdk/src/constants.ts
+++ b/packages/sdk/src/constants.ts
@@ -1,5 +1,5 @@
import { ccc } from "@ckb-ccc/core";
-import { IckbUdtManager, LogicManager, OwnedOwnerManager } from "@ickb/core";
+import { IckbUdt, LogicManager, OwnedOwnerManager } from "@ickb/core";
import { DaoManager } from "@ickb/dao";
import { OrderManager } from "@ickb/order";
import { unique, type ScriptDeps } from "@ickb/utils";
@@ -34,13 +34,16 @@ export function getConfig(
): {
managers: {
dao: DaoManager;
- ickbUdt: IckbUdtManager;
+ ickbUdt: IckbUdt;
logic: LogicManager;
ownedOwner: OwnedOwnerManager;
order: OrderManager;
};
bots: ccc.Script[];
} {
+ // Capture network before d gets reassigned to ScriptDeps.
+ const network = typeof d === "string" ? d : undefined;
+
// If deps is provided as a network string, use the pre-defined constants.
if (d === "mainnet" || d === "testnet") {
bots = bots.concat(
@@ -57,25 +60,49 @@ export function getConfig(
}
const dao = new DaoManager(d.dao.script, d.dao.cellDeps);
- const ickbUdt = new IckbUdtManager(
- d.udt.script,
- d.udt.cellDeps,
- d.logic.script,
- dao,
- );
- const logic = new LogicManager(
+
+ // xUDT code cell OutPoint: use known constants for mainnet/testnet,
+ // fall back to first cellDep's outPoint for devnet.
+ const xudtCode = network
+ ? network === "mainnet"
+ ? MAINNET_XUDT_CODE
+ : TESTNET_XUDT_CODE
+ : d.udt.cellDeps[0]?.outPoint;
+ if (!xudtCode) {
+ throw new Error(
+ "devnet config missing xUDT code cell outPoint in udt.cellDeps",
+ );
+ }
+
+ // iCKB Logic code cell OutPoint: same pattern as above.
+ const logicCode = network
+ ? network === "mainnet"
+ ? MAINNET_LOGIC_CODE
+ : TESTNET_LOGIC_CODE
+ : d.logic.cellDeps[0]?.outPoint;
+ if (!logicCode) {
+ throw new Error(
+ "devnet config missing Logic code cell outPoint in logic.cellDeps",
+ );
+ }
+
+ const ickbUdt = new IckbUdt(
+ xudtCode,
+ IckbUdt.typeScriptFrom(
+ ccc.Script.from(d.udt.script),
+ ccc.Script.from(d.logic.script),
+ ),
+ logicCode,
d.logic.script,
- d.logic.cellDeps,
dao,
- ickbUdt,
);
+ const logic = new LogicManager(d.logic.script, d.logic.cellDeps, dao);
const ownedOwner = new OwnedOwnerManager(
d.ownedOwner.script,
d.ownedOwner.cellDeps,
dao,
- ickbUdt,
);
- const order = new OrderManager(d.order.script, d.order.cellDeps, ickbUdt);
+ const order = new OrderManager(d.order.script, d.order.cellDeps, ickbUdt.script);
return {
managers: {
@@ -114,13 +141,15 @@ const DAO = {
};
/**
- * UDT (User Defined Token) lock script information used for onchain validation.
+ * Raw xUDT type script (codeHash + hashType only).
+ * The iCKB UDT type script args are computed dynamically by
+ * IckbUdt.typeScriptFrom() from ICKB_LOGIC.
*/
const UDT = {
codeHash:
"0x50bd8d6680b8b9cf98b73f3c08faf8b2a21914311954118ad6609be6e78a1b95",
hashType: "data1",
- args: "0xb73b6ab39d79390c6de90a09c96b290c331baf1798ed6f97aed02590929734e800000080",
+ args: "0x",
};
/**
@@ -153,6 +182,46 @@ const ORDER = {
args: "0x",
};
+/**
+ * Mainnet xUDT code cell OutPoint.
+ * Source: forks/contracts/scripts/deployment/mainnet/deployment.toml
+ */
+const MAINNET_XUDT_CODE = {
+ txHash:
+ "0xc07844ce21b38e4b071dd0e1ee3b0e27afd8d7532491327f39b786343f558ab7",
+ index: "0x0",
+};
+
+/**
+ * Mainnet iCKB Logic code cell OutPoint.
+ * Source: forks/contracts/scripts/deployment/mainnet/deployment.toml
+ */
+const MAINNET_LOGIC_CODE = {
+ txHash:
+ "0xd7309191381f5a8a2904b8a79958a9be2752dbba6871fa193dab6aeb29dc8f44",
+ index: "0x0",
+};
+
+/**
+ * Testnet xUDT code cell OutPoint.
+ * Source: forks/contracts/scripts/deployment/testnet/deployment.toml
+ */
+const TESTNET_XUDT_CODE = {
+ txHash:
+ "0xbf6fb538763efec2a70a6a3dcb7242787087e1030c4e7d86585bc63a9d337f5f",
+ index: "0x0",
+};
+
+/**
+ * Testnet iCKB Logic code cell OutPoint.
+ * Source: forks/contracts/scripts/deployment/testnet/deployment.toml
+ */
+const TESTNET_LOGIC_CODE = {
+ txHash:
+ "0x9ac989b3355764f76cdce02c69dedb819fdfbcbda49a7db1a2c9facdfdb9a7fe",
+ index: "0x0",
+};
+
/**
* Mainnet dependency group cell dep.
*/
diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts
index eb415f1..ffa8b89 100644
--- a/packages/utils/src/index.ts
+++ b/packages/utils/src/index.ts
@@ -1,4 +1,3 @@
export * from "./codec.js";
export * from "./heap.js";
-export * from "./udt.js";
export * from "./utils.js";
diff --git a/packages/utils/src/udt.ts b/packages/utils/src/udt.ts
deleted file mode 100644
index e799067..0000000
--- a/packages/utils/src/udt.ts
+++ /dev/null
@@ -1,405 +0,0 @@
-import { ccc, mol } from "@ckb-ccc/core";
-import {
- defaultFindCellsLimit,
- unique,
- type ScriptDeps,
- type ValueComponents,
-} from "./utils.js";
-
-/**
- * Interface representing a handler for User Defined Tokens (UDTs).
- * Extends the ScriptDeps interface to include `script` and `cellDeps`.
- */
-export interface UdtHandler extends ScriptDeps {
- /**
- * Asynchronously retrieves the UDT input balance (token amount and capacity)
- * for a given transaction.
- *
- * @param client - The CKB client to query cell data.
- * @param txLike - The transaction whose inputs are to be balanced.
- * @returns A promise resolving to a tuple:
- * - [0]: Total UDT amount in inputs (as `ccc.FixedPoint`).
- * - [1]: Total capacity in UDT inputs (as `ccc.FixedPoint`).
- */
- getInputsUdtBalance(
- client: ccc.Client,
- txLike: ccc.TransactionLike,
- ): Promise<[ccc.FixedPoint, ccc.FixedPoint]>;
-
- /**
- * Retrieves the UDT output balance (token amount and capacity)
- * for a given transaction.
- *
- * @param txLike - The transaction whose outputs are to be balanced.
- * @returns A tuple:
- * - [0]: Total UDT amount in outputs (as `ccc.FixedPoint`).
- * - [1]: Total capacity in UDT outputs (as `ccc.FixedPoint`).
- */
- getOutputsUdtBalance(
- txLike: ccc.TransactionLike,
- ): [ccc.FixedPoint, ccc.FixedPoint];
-
- /**
- * Completes a transaction by adding UDT inputs and/or UDT change outputs as needed.
- *
- * @param signer - Signer providing client access and account scripts.
- * @param txLike - The transaction to adjust.
- * @param options.shouldAddInputs - Whether to add inputs if insufficient. Defaults to `true`.
- * @param options.compressState - Whether to collect all UDT cells to compress state rent. Defaults to `false`.
- * @returns A promise resolving to a tuple:
- * - [0]: The (possibly mutated) transaction.
- * - [1]: Number of UDT inputs added.
- * - [2]: `true` if a UDT change output was appended; otherwise `false`.
- */
- completeUdt(
- signer: ccc.Signer,
- txLike: ccc.TransactionLike,
- options?: {
- shouldAddInputs?: boolean;
- compressState?: boolean;
- },
- ): Promise<[ccc.Transaction, number, boolean]>;
-
- /** The canonical name of the UDT. */
- name: string;
-
- /** The ticker or symbol of the UDT. */
- symbol: string;
-
- /** The number of decimal places of precision for the UDT. */
- decimals: number;
-}
-
-/**
- * Error thrown when a transaction has insufficient UDT balance.
- * Extends the CKB core `ErrorTransactionInsufficientCoin` by including token symbol
- * and decimal precision in the error message.
- *
- * UDT Handler implementer should use this error class where appropriate.
- */
-export class ErrorTransactionInsufficientCoin extends ccc.ErrorTransactionInsufficientCoin {
- /**
- * @param amount - The additional amount required (in fixed-point).
- * @param type - The UDT type script.
- * @param symbol - Token symbol (e.g., "USDT").
- * @param decimals - Decimal precision of the token.
- */
- constructor(
- amount: ccc.Num,
- type: ccc.Script,
- public readonly symbol: string,
- public readonly decimals: number,
- ) {
- super(amount, type);
- this.message = `Insufficient coin, need ${ccc.fixedPointToString(
- amount,
- decimals,
- )} extra ${symbol} coin`;
- }
-}
-
-/**
- * UdtManager implements UdtHandler for managing UDT operations:
- * - Detecting UDT cells
- * - Computing input/output balances
- * - Adding inputs or change outputs to meet desired UDT amounts
- */
-export class UdtManager implements UdtHandler {
- /**
- * @param script - The UDT type script.
- * @param cellDeps - Cell dependencies required to use the UDT script.
- * @param name - The token's full name.
- * @param symbol - The token's symbol or ticker.
- * @param decimals - Decimal precision for token amounts.
- */
- constructor(
- public readonly script: ccc.Script,
- public readonly cellDeps: ccc.CellDep[],
- public readonly name: string,
- public readonly symbol: string,
- public readonly decimals: number,
- ) {}
-
- /**
- * Determines whether a cell contains this UDT.
- *
- * @param cell - The cell to inspect.
- * @returns `true` if the cell's type script equals `this.script` and
- * its data length indicates a UDT; otherwise `false`.
- */
- isUdt(cell: ccc.Cell): boolean {
- return (
- Boolean(cell.cellOutput.type?.eq(this.script)) &&
- cell.outputData.length >= 34
- );
- }
-
- /**
- * Asynchronously retrieves the UDT input balance (token amount and capacity)
- * for a given transaction.
- *
- * @param client - The CKB client to query cell data.
- * @param txLike - The transaction whose inputs are to be balanced.
- * @returns A promise resolving to a tuple:
- * - [0]: Total UDT amount in inputs (as `ccc.FixedPoint`).
- * - [1]: Total capacity in UDT inputs (as `ccc.FixedPoint`).
- */
- getInputsUdtBalance(
- client: ccc.Client,
- txLike: ccc.TransactionLike,
- ): Promise<[ccc.FixedPoint, ccc.FixedPoint]> {
- const tx = ccc.Transaction.from(txLike);
- return ccc.reduceAsync(
- tx.inputs,
- async (acc, input) => {
- // Get all cell info
- await input.completeExtraInfos(client);
- const { previousOutput: outPoint, cellOutput, outputData } = input;
-
- // Input is not well defined
- if (!cellOutput || !outputData) {
- throw new Error("Unable to complete input");
- }
-
- // Input is not an UDT
- const cell = new ccc.Cell(outPoint, cellOutput, outputData);
- if (!this.isUdt(cell)) {
- return acc;
- }
-
- // Input is an UDT
- const [udtValue, capacity] = acc;
- return [
- // eslint-disable-next-line @typescript-eslint/no-deprecated
- udtValue + ccc.udtBalanceFrom(outputData),
- capacity + cell.cellOutput.capacity,
- ];
- },
- [0n, 0n],
- );
- }
-
- /**
- * Retrieves the UDT output balance (token amount and capacity)
- * for a given transaction.
- *
- * @param txLike - The transaction whose outputs are to be balanced.
- * @returns A tuple:
- * - [0]: Total UDT amount in outputs (as `ccc.FixedPoint`).
- * - [1]: Total capacity in UDT outputs (as `ccc.FixedPoint`).
- */
- getOutputsUdtBalance(
- txLike: ccc.TransactionLike,
- ): [ccc.FixedPoint, ccc.FixedPoint] {
- const tx = ccc.Transaction.from(txLike);
- return tx.outputs.reduce(
- (acc, output, i) => {
- if (!output.type?.eq(this.script)) {
- return acc;
- }
-
- // Input is an UDT
- const [udtValue, capacity] = acc;
- return [
- // eslint-disable-next-line @typescript-eslint/no-deprecated
- udtValue + ccc.udtBalanceFrom(tx.outputsData[i] ?? "0x"),
- capacity + output.capacity,
- ];
- },
- [0n, 0n],
- );
- }
-
- /**
- * Completes a transaction by adding UDT inputs and/or UDT change outputs as needed.
- *
- * @param signer - Signer providing client access and account scripts.
- * @param txLike - The transaction to adjust.
- * @param options.shouldAddInputs - Whether to add inputs if insufficient. Defaults to `true`.
- * @param options.compressState - Whether to collect all UDT cells to compress state rent. Defaults to `false`.
- * @returns A promise resolving to a tuple:
- * - [0]: The (possibly mutated) transaction.
- * - [1]: Number of UDT inputs added.
- * - [2]: `true` if a UDT change output was appended; otherwise `false`.
- */
- async completeUdt(
- signer: ccc.Signer,
- txLike: ccc.TransactionLike,
- options?: {
- shouldAddInputs?: boolean;
- compressState?: boolean;
- },
- ): Promise<[ccc.Transaction, number, boolean]> {
- const tx = ccc.Transaction.from(txLike);
- const client = signer.client;
- let [inUdt, inCapacity] = await this.getInputsUdtBalance(client, tx);
- const [outUdt, outCapacity] = this.getOutputsUdtBalance(tx);
- let inAdded = 0;
- if (
- (inUdt < outUdt || inCapacity < outCapacity) &&
- (options?.shouldAddInputs ?? true)
- ) {
- const compressState = options?.compressState ?? false;
- const locks = (await signer.getAddressObjs()).map((a) => a.script);
- const udts = [];
- for await (const cell of this.findUdts(client, locks)) {
- udts.push(cell);
- inUdt += cell.udtValue;
- inCapacity += cell.ckbValue;
- if (!compressState && inUdt >= outUdt && inCapacity >= outCapacity) {
- break;
- }
- }
- this.addUdts(tx, udts);
- inAdded = udts.length;
- }
-
- if (inUdt < outUdt) {
- throw new ErrorTransactionInsufficientCoin(
- outUdt - inUdt,
- this.script,
- this.symbol,
- this.decimals,
- );
- }
-
- if (inUdt === outUdt) {
- return [tx, inAdded, false];
- }
-
- tx.addOutput(
- {
- lock: (await signer.getRecommendedAddressObj()).script,
- type: this.script,
- },
- mol.Uint128LE.encode(inUdt - outUdt),
- );
-
- return [tx, inAdded, true];
- }
-
- /**
- * Adds UDT cells to a transaction.
- * @param txLike - The transaction to which UDT cells will be added.
- * @param udts - An array of UDT cells to add.
- * @returns The transaction with UDT cells added.
- */
- addUdts(txLike: ccc.TransactionLike, udts: UdtCell[]): ccc.Transaction {
- const tx = ccc.Transaction.from(txLike);
- if (udts.length === 0) {
- return tx;
- }
-
- tx.addCellDeps(this.cellDeps);
-
- for (const { cell } of udts) {
- tx.addInput(cell);
- }
- return tx;
- }
-
- /**
- * Async generator that finds and yields UDT (User‐Defined Token) cells matching the given lock scripts.
- *
- * @param client
- * A CKB client instance providing:
- * - `findCells(query, order, limit)` for cached searches
- * - `findCellsOnChain(query, order, limit)` for on-chain searches
- *
- * @param locks
- * An array of lock scripts. Only cells whose `cellOutput.lock` matches one of these
- * scripts exactly will be considered.
- *
- * @param options
- * Optional parameters to control the search behavior:
- * - `onChain?: boolean`
- * If `true`, queries the chain directly via `findCellsOnChain`.
- * Otherwise, uses local cache via `findCells`. Default: `false`.
- * - `limit?: number`
- * Maximum number of cells to fetch per lock script in each batch.
- * Defaults to `defaultFindCellsLimit` (400).
- *
- * @yields
- * {@link UdtCell} objects for each valid UDT cell found.
- *
- * @remarks
- * - Deduplicates `locks` via `unique(locks)` to avoid redundant queries.
- * - Applies an RPC filter:
- * • `script: this.script` (the UDT type script)
- * - Skips any cell that:
- * 1. Does not pass `this.isUdt(cell)`
- * 2. Whose lock script does not exactly match the queried `lock`
- * - Each yielded `UdtCell` contains:
- * • `cell`: original cell data with status
- * • `ckbValue`: capacity in shannons
- * • `udtValue`: token amount parsed via `ccc.udtBalanceFrom(cell.outputData)`
- * • a hidden `[isUdtSymbol]: true` marker
- */
- async *findUdts(
- client: ccc.Client,
- locks: ccc.Script[],
- options?: {
- /**
- * If true, fetch cells directly from the chain RPC. Otherwise, use cached results.
- * @default false
- */
- onChain?: boolean;
- /**
- * Batch size per lock script. Defaults to {@link defaultFindCellsLimit}.
- */
- limit?: number;
- },
- ): AsyncGenerator {
- const limit = options?.limit ?? defaultFindCellsLimit;
- for (const lock of unique(locks)) {
- const findCellsArgs = [
- {
- script: lock,
- scriptType: "lock",
- filter: {
- script: this.script,
- },
- scriptSearchMode: "exact",
- withData: true,
- },
- "asc",
- limit,
- ] as const;
-
- for await (const cell of options?.onChain
- ? client.findCellsOnChain(...findCellsArgs)
- : client.findCells(...findCellsArgs)) {
- if (!this.isUdt(cell) || !cell.cellOutput.lock.eq(lock)) {
- continue;
- }
-
- yield {
- cell,
- ckbValue: cell.cellOutput.capacity,
- // eslint-disable-next-line @typescript-eslint/no-deprecated
- udtValue: ccc.udtBalanceFrom(cell.outputData),
- [isUdtSymbol]: true,
- };
- }
- }
- }
-}
-
-/**
- * Interface representing a UdtCell Cell.
- */
-export interface UdtCell extends ValueComponents {
- /**
- * The underlying cell associated with the UDT Cell.
- */
- cell: ccc.Cell;
-
- /**
- * A symbol property indicating that this cell is a UDT Cell.
- * This property is always set to true.
- */
- [isUdtSymbol]: true;
-}
-
-// Symbol to represent the isUdt property of UDT Cells
-const isUdtSymbol = Symbol("isUdt");
From 0ee8e2ab757170644455971120a64605e9173918 Mon Sep 17 00:00:00 2001
From: phroi <90913182+phroi@users.noreply.github.com>
Date: Thu, 26 Feb 2026 22:35:05 +0000
Subject: [PATCH 5/5] chore: add changeset for CCC UDT adoption
---
.changeset/adopt-ccc-udt.md | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
create mode 100644 .changeset/adopt-ccc-udt.md
diff --git a/.changeset/adopt-ccc-udt.md b/.changeset/adopt-ccc-udt.md
new file mode 100644
index 0000000..afa1c66
--- /dev/null
+++ b/.changeset/adopt-ccc-udt.md
@@ -0,0 +1,17 @@
+---
+"@ickb/core": major
+"@ickb/dao": minor
+"@ickb/order": major
+"@ickb/utils": major
+"@ickb/sdk": major
+---
+
+Adopt CCC udt.Udt as IckbUdt base class, replacing homegrown UDT infrastructure
+
+- Rewrite `IckbUdtManager` as `IckbUdt` extending CCC's `udt.Udt` base class
+- Accept code OutPoints instead of pre-built CellDep arrays
+- Override `infoFrom()` to value iCKB's three cell representations (xUDT, receipt, deposit)
+- Remove `udtHandler` parameter from `LogicManager` and `OwnedOwnerManager`
+- Replace `UdtHandler` with plain `udtScript` in `OrderManager`
+- Delete `UdtHandler`, `UdtManager`, `UdtCell`, `ErrorTransactionInsufficientCoin` from `@ickb/utils`
+- Widen `DaoManager.isDeposit()` to accept `CellAny`