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`
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)_
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/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/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,
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");
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