From 5f5651895370b2988bfb8084e8a9379dc2887a3f Mon Sep 17 00:00:00 2001 From: phroi <90913182+phroi@users.noreply.github.com> Date: Wed, 25 Feb 2026 02:08:11 +0000 Subject: [PATCH 1/3] docs(phase-03): CCC Udt integration investigation Decision: subclass CCC Udt as IckbUdt, overriding infoFrom() for multi-representation balance (xUDT + receipts + deposits). Header access via client.getTransactionWithHeader(). Conservation law enforced on-chain, not in infoFrom. --- .planning/REQUIREMENTS.md | 16 +- .planning/ROADMAP.md | 18 +- .planning/STATE.md | 46 +- .../03-01-INVESTIGATION.md | 717 ++++++++++++++++++ .../03-01-PLAN.md | 136 ++++ .../03-01-SUMMARY.md | 109 +++ .../03-02-PLAN.md | 212 ++++++ .../03-02-SUMMARY.md | 117 +++ .../03-CONTEXT.md | 75 ++ .../03-DECISION.md | 481 ++++++++++++ .../03-RESEARCH.md | 440 +++++++++++ .../03-VERIFICATION.md | 111 +++ 12 files changed, 2444 insertions(+), 34 deletions(-) create mode 100644 .planning/phases/03-ccc-udt-integration-investigation/03-01-INVESTIGATION.md create mode 100644 .planning/phases/03-ccc-udt-integration-investigation/03-01-PLAN.md create mode 100644 .planning/phases/03-ccc-udt-integration-investigation/03-01-SUMMARY.md create mode 100644 .planning/phases/03-ccc-udt-integration-investigation/03-02-PLAN.md create mode 100644 .planning/phases/03-ccc-udt-integration-investigation/03-02-SUMMARY.md create mode 100644 .planning/phases/03-ccc-udt-integration-investigation/03-CONTEXT.md create mode 100644 .planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md create mode 100644 .planning/phases/03-ccc-udt-integration-investigation/03-RESEARCH.md create mode 100644 .planning/phases/03-ccc-udt-integration-investigation/03-VERIFICATION.md diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 7496e34..d78278c 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -30,9 +30,9 @@ Requirements for initial milestone. Each maps to roadmap phases. ### CCC Udt Integration -- [ ] **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 -- [ ] **UDT-02**: Header access pattern for receipt value calculation is designed -- determine whether `client.getCellWithHeader()`, `client.getHeaderByTxHash()`, or direct CCC client calls are used within the Udt override (`getHeader()` utility removed in Phase 1) -- [ ] **UDT-03**: Decision documented: subclass CCC `Udt` vs. keep custom `UdtHandler` interface vs. hybrid approach +- [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 @@ -86,7 +86,7 @@ Which phases cover which requirements. Updated during roadmap creation. | 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-06 | Phase 1 | Complete | DAO check contributed to CCC core via ccc-dev/ (01-01) | +| SMTX-06 | Phase 1 | Complete | DAO check contributed to CCC core via ccc-fork/ (01-01) | | SMTX-07 | Phase 5 | Pending | | | SMTX-08 | Phase 6 | Pending | | | SMTX-09 | Phase 7 | Pending | | @@ -96,9 +96,9 @@ Which phases cover which requirements. Updated during roadmap creation. | DEDUP-03 | Phase 2 | Complete | isHex() deleted, only used internally by deleted hexFrom() (02-01) | | DEDUP-04 | Phase 2 | Complete | hexFrom() call sites use entity.toHex() or ccc.hexFrom(), local deleted (02-01) | | DEDUP-05 | Phase 2 | Complete | All 8 iCKB-unique utilities preserved unchanged (02-01) | -| UDT-01 | Phase 3 | Pending | | -| UDT-02 | Phase 3 | Pending | | -| UDT-03 | Phase 3 | Pending | | +| 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 | | @@ -109,4 +109,4 @@ Which phases cover which requirements. Updated during roadmap creation. --- *Requirements defined: 2026-02-21* -*Last updated: 2026-02-23 after 02-01 execution (DEDUP-01 through DEDUP-05 completed; Phase 2 complete)* +*Last updated: 2026-02-24 after Phase 3 completion (UDT-01 through UDT-03 completed; Phase 3 complete)* diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 58d73f4..e6887c6 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -14,7 +14,7 @@ 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 -- [ ] **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 +- [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 - [ ] **Phase 6: SDK Completion Pipeline** - Wire IckbSdk facade to CCC-native fee completion; verify estimate() and maturity() work end-to-end @@ -30,7 +30,7 @@ Decimal phases appear between their surrounding integers in numeric order. 1. `SmartTransaction` class and `CapacityManager` class no longer exist in `@ickb/utils` source or exports 2. `UdtHandler` interface and `UdtManager` class remain in `@ickb/utils` with method signatures updated from `SmartTransaction` to `ccc.TransactionLike` (full replacement deferred to Phase 3+) 3. `getHeader()` function and `HeaderKey` type are removed from `@ickb/utils`; all call sites across dao/core/sdk inline CCC client calls (`client.getTransactionWithHeader()`, `client.getHeaderByNumber()`); `SmartTransaction.addHeaders()` call sites in DaoManager/LogicManager push to `tx.headerDeps` directly - 4. A 64-output NervosDAO limit check exists in CCC core (via `ccc-dev/`): `completeFee()` safety net, standalone async utility, and `ErrorNervosDaoOutputLimit` error class; all 6+ scattered checks across dao/core packages are replaced with calls to this CCC utility + 4. A 64-output NervosDAO limit check exists in CCC core (via `ccc-fork/`): `completeFee()` safety net, standalone async utility, and `ErrorNervosDaoOutputLimit` error class; all 6+ scattered checks across dao/core packages are replaced with calls to this CCC utility 5. ALL manager method signatures across ALL 5 library packages accept `ccc.TransactionLike` instead of `SmartTransaction`, following CCC's convention (TransactionLike input, Transaction output with `Transaction.from()` conversion at entry point) 6. `pnpm check:full` passes after each feature-slice removal step — no intermediate broken states **Plans**: 3 plans @@ -60,14 +60,14 @@ Plans: **Depends on**: Nothing (can proceed in parallel with Phases 1-2; design investigation, not code changes) **Requirements**: UDT-01, UDT-02, UDT-03 **Success Criteria** (what must be TRUE): - 1. A written feasibility assessment exists answering: can `IckbUdt extends udt.Udt` override `getInputsInfo()`/`getOutputsInfo()` to account for receipt cells and deposit cells alongside xUDT cells, without breaking CCC's internal method chains - 2. The header access pattern for receipt value calculation is designed and documented -- specifying whether `client.getCellWithHeader()`, `client.getHeaderByTxHash()`, or direct CCC client calls are used within the Udt override (note: `getHeader()` was removed in Phase 1) + 1. A written feasibility assessment exists answering: can `IckbUdt extends udt.Udt` override `infoFrom()` (or `getInputsInfo()`/`getOutputsInfo()`) to account for receipt cells and deposit cells alongside xUDT cells, without breaking CCC's internal method chains + 2. The header access pattern for receipt value calculation is designed and documented -- specifying whether `client.getCellWithHeader()`, `client.getTransactionWithHeader()`, or direct CCC client calls are used within the Udt override (note: `getHeader()` was removed in Phase 1) 3. A decision document exists with one of three outcomes: (a) subclass CCC Udt, (b) keep custom interface, (c) hybrid approach -- with rationale for the chosen path -**Plans**: TBD +**Plans**: 2 plans Plans: -- [ ] 03-01: TBD -- [ ] 03-02: TBD +- [x] 03-01-PLAN.md — Trace CCC Udt internals end-to-end, verify infoFrom override feasibility, resolve open questions +- [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) @@ -89,7 +89,7 @@ Plans: **Requirements**: SMTX-05, SMTX-07, SMTX-10, UDT-04, UDT-05 **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 `getInputsInfo()`/`getOutputsInfo()` that account for xUDT cells, receipt cells, and deposit cells + 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` @@ -139,7 +139,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 | 0/2 | Not started | - | +| 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 | - | | 6. SDK Completion Pipeline | 0/2 | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 7bde1b0..d35dd07 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -5,23 +5,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 2: CCC Utility Adoption +**Current focus:** Phase 4: Deprecated CCC API Replacement ## Current Position -Phase: 2 of 7 (CCC Utility Adoption) -Plan: 1 of 1 in current phase (phase complete) -Status: Phase 02 complete, ready for phase 03 -Last activity: 2026-02-23 -- Plan 02-01 executed (local utility functions replaced with CCC equivalents) +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) -Progress: [███░░░░░░░] 25% +Progress: [████░░░░░░] 43% ## Performance Metrics **Velocity:** -- Total plans completed: 4 -- Average duration: ~15min -- Total execution time: 1.0 hours +- Total plans completed: 6 +- Average duration: ~12min +- Total execution time: 1.2 hours **By Phase:** @@ -29,9 +29,10 @@ Progress: [███░░░░░░░] 25% |-------|-------|-------|----------| | 01 | 3/3 | 52min | 17min | | 02 | 1/1 | 7min | 7min | +| 03 | 2/2 | 9min | 4.5min | **Recent Trend:** -- Last 5 plans: 01-01 (~30min), 01-02 (~6min), 01-03 (~16min), 02-01 (~7min) +- Last 5 plans: 01-03 (~16min), 02-01 (~7min), 03-01 (~5min), 03-02 (~4min) - Trend: accelerating *Updated after each plan completion* @@ -46,13 +47,13 @@ Recent decisions affecting current work: - [Roadmap]: Phase 1 uses feature-slice approach -- each removal chased across all packages, build stays green after every step. SMTX-01 (all signatures to TransactionLike) completed in Phase 1, not Phase 5. - [Roadmap]: UDT investigation (Phase 3) is a design phase that produces a decision document; its outcome determines UdtHandler/UdtManager replacement pattern used in Phases 4-5 - [Roadmap]: Phases 4-5 reduced in scope: Phase 4 focuses on deprecated API replacement + UDT pattern finalization in dao/order; Phase 5 focuses on IckbUdt implementation + conservation law in core -- [Phase 1 Context]: DAO 64-output limit check contributed to CCC core via ccc-dev/, CCC PR submitted during Phase 1 +- [Phase 1 Context]: DAO 64-output limit check contributed to CCC core via ccc-fork/, CCC PR submitted during Phase 1 - [Phase 1 Context]: getHeader()/HeaderKey removed entirely -- inline CCC client calls at read-only call sites; addHeaders() call sites in DaoManager/LogicManager push to tx.headerDeps directly - [Phase 1 Context]: Script comparison must always use full Script.eq(), never just codeHash comparison -- [01-01]: Added ccc-dev local patch mechanism (pins/local/*.patch) for deterministic replay of CCC modifications +- [01-01]: Added ccc-fork local patch mechanism for deterministic replay of CCC modifications (now multi-file format: manifest + res-N.resolution + local-*.patch) - [01-01]: DaoManager.requestWithdrawal/withdraw client parameter placed before optional options for cleaner API - [01-01]: assertDaoOutputLimit uses early return when outputs <= 64 for zero-cost common case -- [01-02]: Moved getHeader/HeaderKey to transaction.ts as non-exported internals (SmartTransaction still uses internally until Plan 03 deletion) +- [01-02]: Moved getHeader/HeaderKey to transaction.ts as non-exported internals (deleted alongside SmartTransaction in 01-03) - [01-02]: TransactionHeader moved to utils.ts as canonical location for downstream consumers - [01-02]: Inlined CCC client calls use explicit null checks with descriptive error messages - [01-03]: All manager methods accept ccc.TransactionLike and return ccc.Transaction (TransactionLike pattern) @@ -61,6 +62,14 @@ Recent decisions affecting current work: - [01-03]: SDK getCkb() uses direct client.findCellsOnChain instead of CapacityManager - [02-01]: Used Math.max() over Number(ccc.numMax()) for number-typed contexts to avoid unnecessary number→bigint→number round-trips - [02-01]: Used entity.toHex() for Entity args, ccc.hexFrom() for BytesLike args -- matching CCC's type-safe separation +- [03-01]: infoFrom is the sole override point for IckbUdt -- no need to override getInputsInfo/getOutputsInfo +- [03-01]: No upstream CCC changes required for IckbUdt subclass -- all override points are public with appropriate signatures +- [03-01]: Caller responsibility for receipt/deposit cell discovery (not IckbUdt's filter) -- LogicManager/OwnedOwnerManager handle this +- [03-01]: Accurate balance reporting only -- conservation law enforcement is separate from infoFrom +- [03-02]: Decision: subclass CCC Udt (option a) -- IckbUdt extends udt.Udt with infoFrom override +- [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 ### Pending Todos @@ -69,10 +78,13 @@ None yet. ### Blockers/Concerns - Resolved: CCC's `Transaction.getInputsCapacity()` handles DAO profit natively via `getInputsCapacityExtra()` -> `CellInput.getExtraCapacity()` -> `Cell.getDaoProfit()` (verified in STACK.md from CCC source). No standalone utility needed. -- Research gap: CCC Udt `getInputsInfo()` signature needs verification for header fetching context -- must confirm during Phase 3 investigation. +- Resolved: CCC Udt `getInputsInfo()` resolves inputs to `Cell` objects (which have `outPoint`) before passing to `infoFrom()`. `infoFrom()`'s `CellAnyLike` parameter has `outPoint?: OutPointLike | null` — optional, not absent. Input cells have outPoint (for header fetches), output cells don't. Both `infoFrom` and `getInputsInfo/getOutputsInfo` are viable override points for IckbUdt (verified during Phase 3 discuss-phase). +- Resolved: STACK.md research correction applied — `client.getHeaderByTxHash()` (non-existent) replaced with `client.getTransactionWithHeader()` in STACK.md, ROADMAP.md Phase 3 success criterion #2, and REQUIREMENTS.md UDT-02. +- Resolved: PR #328 stance updated during Phase 3 context — user decision is to design around PR #328 as target architecture (overrides research recommendation to "not wait for #328"). PR #328 is now integrated into `ccc-fork/ccc` via pins; FeePayer classes available at `ccc-fork/ccc/packages/core/src/signer/feePayer/`. The separate `reference/ccc-fee-payer` clone is no longer needed. +- Resolved: `CellAny` has `capacityFree` getter (CCC transaction.ts:404-405) — 03-RESEARCH.md corrected (previously claimed `CellAny` lacked it). ## Session Continuity -Last session: 2026-02-23 -Stopped at: Completed 02-01-PLAN.md (Phase 02 complete) -Resume file: .planning/phases/02-ccc-utility-adoption/02-01-SUMMARY.md +Last session: 2026-02-24 +Stopped at: Completed 03-02-PLAN.md (Phase 3 complete) +Resume file: Phase 4 planning needed diff --git a/.planning/phases/03-ccc-udt-integration-investigation/03-01-INVESTIGATION.md b/.planning/phases/03-ccc-udt-integration-investigation/03-01-INVESTIGATION.md new file mode 100644 index 0000000..b9859f9 --- /dev/null +++ b/.planning/phases/03-ccc-udt-integration-investigation/03-01-INVESTIGATION.md @@ -0,0 +1,717 @@ +# Phase 3 Plan 1: CCC Udt Integration Investigation + +**Investigated:** 2026-02-24 +**Source base:** ccc-fork/ccc (local fork with PR #328 integrated) +**Purpose:** Trace CCC Udt class internals end-to-end, verify infoFrom override feasibility, resolve all open questions from 03-RESEARCH.md + +## CCC Udt Method Chain Trace + +### Udt Constructor + +**File:** `ccc-fork/ccc/packages/udt/src/udt/index.ts:412-425` + +```typescript +constructor( + code: ccc.OutPointLike, + script: ccc.ScriptLike, + config?: UdtConfigLike | null, +) { + super(code, config?.executor); + this.script = ccc.Script.from(script); + this.filter = ccc.ClientIndexerSearchKeyFilter.from( + config?.filter ?? { + script: this.script, + outputDataLenRange: [16, "0xffffffff"], + }, + ); +} +``` + +**Key findings:** +- `this.script` is set from the `script` parameter -- this is the xUDT type script +- `this.filter` defaults to matching cells by `this.script` type with minimum 16-byte output data +- The filter only matches standard xUDT cells -- receipt and deposit cells have different type/lock scripts and will NOT be found by this filter +- `super(code, config?.executor)` passes to `ssri.Trait` -- stores `code` (OutPoint) and optional `executor` + +### infoFrom (Override Target) + +**File:** `ccc-fork/ccc/packages/udt/src/udt/index.ts:624-641` + +```typescript +async infoFrom( + _client: ccc.Client, + cells: ccc.CellAnyLike | ccc.CellAnyLike[], + acc?: UdtInfoLike, +): Promise { + return [cells].flat().reduce((acc, cellLike) => { + const cell = ccc.CellAny.from(cellLike); + if (!this.isUdt(cell)) { + return acc; + } + + return acc.addAssign({ + balance: Udt.balanceFromUnsafe(cell.outputData), + capacity: cell.cellOutput.capacity, + count: 1, + }); + }, UdtInfo.from(acc).clone()); +} +``` + +**Key findings:** +- Signature: `(client: ccc.Client, cells: ccc.CellAnyLike | ccc.CellAnyLike[], acc?: UdtInfoLike) => Promise` +- `_client` is unused in the base implementation but available for override (iCKB needs it for header fetches) +- Accepts single cell or array, flattened with `[cells].flat()` +- Each cell is converted to `CellAny` via `ccc.CellAny.from(cellLike)` +- `acc` parameter enables accumulation across multiple `infoFrom` calls +- Base implementation only counts cells where `this.isUdt(cell)` returns true +- Override can add custom logic for receipt and deposit cells while preserving the `UdtInfo` accumulator pattern +- Return type is `Promise` -- async allows network calls in override + +### isUdt + +**File:** `ccc-fork/ccc/packages/udt/src/udt/index.ts:1063-1069` + +```typescript +isUdt(cellLike: ccc.CellAnyLike) { + const cell = ccc.CellAny.from(cellLike); + return ( + (cell.cellOutput.type?.eq(this.script) ?? false) && + ccc.bytesFrom(cell.outputData).length >= 16 + ); +} +``` + +**Key findings:** +- Accepts `CellAnyLike` (not just `Cell`) -- works on both input and output cells +- Uses `Script.eq()` for full script comparison (codeHash + hashType + args) -- matches CLAUDE.md guidance +- Checks output data length >= 16 bytes (UDT balance is stored as 128-bit LE integer) +- **Comparison with `UdtManager.isUdt()`** (`packages/utils/src/udt.ts:132-137`): `UdtManager.isUdt()` accepts `ccc.Cell` and checks `cell.outputData.length >= 34` (hex string: `"0x" + 32 hex chars = 34 chars`). Both are checking for 16 bytes minimum -- `UdtManager` uses hex string length, CCC `Udt` uses byte array length. Functionally equivalent. + +### balanceFromUnsafe + +**File:** `ccc-fork/ccc/packages/udt/src/udt/index.ts:590-593` + +```typescript +static balanceFromUnsafe(outputData: ccc.HexLike): ccc.Num { + const data = ccc.bytesFrom(outputData).slice(0, 16); + return data.length < 16 ? ccc.Zero : ccc.numFromBytes(data); +} +``` + +**Key findings:** +- Static method -- extracts UDT balance from first 16 bytes of output data +- Returns `ccc.Zero` if data is shorter than 16 bytes (safe default) +- Equivalent to the deprecated `ccc.udtBalanceFrom()` and the `ccc.numFromBytes(ccc.bytesFrom(outputData).slice(0, 16))` pattern used in `IckbUdtManager` + +### getInputsInfo + +**File:** `ccc-fork/ccc/packages/udt/src/udt/index.ts:1099-1108` + +```typescript +async getInputsInfo( + client: ccc.Client, + txLike: ccc.TransactionLike, +): Promise { + const tx = ccc.Transaction.from(txLike); + const inputCells = await Promise.all( + tx.inputs.map((input) => input.getCell(client)), + ); + return this.infoFrom(client, inputCells); +} +``` + +**Key findings:** +- Resolves all transaction inputs in parallel via `input.getCell(client)` +- `CellInput.getCell()` (transaction.ts:861-872) calls `completeExtraInfos(client)` then returns `Cell.from({ outPoint: this.previousOutput, cellOutput, outputData })` +- The returned `Cell` objects **always have `outPoint` set** (from `this.previousOutput`) +- These `Cell` objects are passed to `infoFrom` as `CellAnyLike[]` +- Since `Cell extends CellAny`, and `Cell` always has `outPoint`, input cells in `infoFrom` will always have `outPoint !== undefined` + +### getOutputsInfo + +**File:** `ccc-fork/ccc/packages/udt/src/udt/index.ts:1178-1184` + +```typescript +async getOutputsInfo( + client: ccc.Client, + txLike: ccc.TransactionLike, +): Promise { + const tx = ccc.Transaction.from(txLike); + return this.infoFrom(client, Array.from(tx.outputCells)); +} +``` + +**Key findings:** +- Uses `tx.outputCells` getter (transaction.ts:1715-1728) +- `outputCells` yields `CellAny.from({ cellOutput: outputs[i], outputData: outputsData[i] ?? "0x" })` +- No `outPoint` is passed to `CellAny.from()`, so `outPoint` is `undefined` on output cells +- Output cells passed to `infoFrom` will have `outPoint === undefined` + +### completeInputsByBalance + +**File:** `ccc-fork/ccc/packages/udt/src/udt/index.ts:1394-1446` + +```typescript +async completeInputsByBalance( + txLike: ccc.TransactionLike, + from: ccc.Signer, + balanceTweak?: ccc.NumLike, + capacityTweak?: ccc.NumLike, +): Promise<{ addedCount: number; tx: ccc.Transaction }> { + const tx = ccc.Transaction.from(txLike); + const { balance: inBalance, capacity: inCapacity } = + await this.getInputsInfo(from.client, tx); + const { balance: outBalance, capacity: outCapacity } = + await this.getOutputsInfo(from.client, tx); + + const balanceBurned = inBalance - outBalance - ccc.numFrom(balanceTweak ?? 0); + const capacityBurned = + ccc.numMin(inCapacity - outCapacity, await tx.getFee(from.client)) - + ccc.numFrom(capacityTweak ?? 0); + + if (balanceBurned >= ccc.Zero && capacityBurned >= ccc.Zero) { + return { addedCount: 0, tx }; + } + + const { tx: txRes, addedCount, accumulated } = await this.completeInputs( + tx, from, + async (acc, cell) => { + const info = await this.infoFrom(from.client, cell, acc); + return info.balance >= ccc.Zero && info.capacity >= ccc.Zero + ? undefined : info; + }, + { balance: balanceBurned, capacity: capacityBurned }, + ); + + if (accumulated === undefined || accumulated.balance >= ccc.Zero) { + return { tx: txRes, addedCount }; + } + + throw new ErrorUdtInsufficientCoin({ amount: -accumulated.balance, type: this.script }); +} +``` + +**Key findings:** +- Full chain: `getInputsInfo` -> `infoFrom` (for existing inputs) and `getOutputsInfo` -> `infoFrom` (for outputs) +- Calculates balance and capacity deficit +- Early exit if both constraints satisfied (no new inputs needed) +- Uses `completeInputs` with accumulator that calls `infoFrom` per new cell found +- The accumulator in `completeInputs` receives `Cell` objects (from signer's `findCellsOnChain`), which always have `outPoint` +- `infoFrom` is called with these individual `Cell` objects during completion -- override automatically participates +- Throws `ErrorUdtInsufficientCoin` if insufficient balance (not capacity -- capacity is a soft constraint) + +### completeInputs (Low-Level) + +**File:** `ccc-fork/ccc/packages/udt/src/udt/index.ts:1309-1331` + +```typescript +async completeInputs( + txLike: ccc.TransactionLike, + from: ccc.Signer, + accumulator: (acc: T, v: ccc.Cell, ...) => Promise | T | undefined, + init: T, +): Promise<{ tx: ccc.Transaction; addedCount: number; accumulated?: T }> { + const tx = ccc.Transaction.from(txLike); + const res = await tx.completeInputs(from, this.filter, accumulator, init); + return { ...res, tx }; +} +``` + +**Key finding:** Delegates to `tx.completeInputs(from, this.filter, ...)` which uses the Udt's `filter` to find cells via the signer. The `filter` only matches xUDT cells. Receipt and deposit cells must be pre-added to the transaction by the caller. + +## CellAny vs Cell: outPoint and capacityFree + +### CellAnyLike type + +**File:** `ccc-fork/ccc/packages/core/src/ckb/transaction.ts:313-318` + +```typescript +export type CellAnyLike = { + outPoint?: OutPointLike | null; + previousOutput?: OutPointLike | null; + cellOutput: CellOutputLike; + outputData?: HexLike | null; +}; +``` + +- `outPoint` is `OutPointLike | null | undefined` -- explicitly optional + +### CellAny class + +**File:** `ccc-fork/ccc/packages/core/src/ckb/transaction.ts:331-457` + +```typescript +export class CellAny { + public outPoint: OutPoint | undefined; // line 332 + + constructor( + public cellOutput: CellOutput, + public outputData: Hex, + outPoint?: OutPoint, // line 346: optional + ) { + this.outPoint = outPoint; // line 347 + } +} +``` + +- `outPoint` is `OutPoint | undefined` at the class level +- Constructor parameter `outPoint` is optional -- defaults to `undefined` + +### CellAny.from() factory + +**File:** `ccc-fork/ccc/packages/core/src/ckb/transaction.ts:374-386` + +```typescript +static from(cell: CellAnyLike): CellAny { + if (cell instanceof CellAny) { return cell; } + const outputData = hexFrom(cell.outputData ?? "0x"); + return new CellAny( + CellOutput.from(cell.cellOutput, outputData), + outputData, + apply(OutPoint.from, cell.outPoint ?? cell.previousOutput), + ); +} +``` + +- Uses `apply(OutPoint.from, cell.outPoint ?? cell.previousOutput)` -- if both are null/undefined, `outPoint` is `undefined` + +### CellAny.capacityFree + +**File:** `ccc-fork/ccc/packages/core/src/ckb/transaction.ts:404-405` + +```typescript +get capacityFree() { + return this.cellOutput.capacity - fixedPointFrom(this.occupiedSize); +} +``` + +**Confirmed:** `capacityFree` is a getter on `CellAny` -- available on ALL cells, both input and output. No need to construct `Cell` to access `capacityFree`. The getter computes `capacity - fixedPointFrom(occupiedSize)` where `occupiedSize` is `cellOutput.occupiedSize + bytesFrom(outputData).byteLength`. + +### Cell class (extends CellAny) + +**File:** `ccc-fork/ccc/packages/core/src/ckb/transaction.ts:488-503` + +```typescript +export class Cell extends CellAny { + constructor( + public outPoint: OutPoint, // line 498: NOT optional + cellOutput: CellOutput, + outputData: Hex, + ) { + super(cellOutput, outputData, outPoint); + } +} +``` + +- `Cell.outPoint` is `OutPoint` (non-optional) -- always present +- `Cell extends CellAny` -- a `Cell` is a `CellAny` with guaranteed `outPoint` + +### CellInput.getCell() + +**File:** `ccc-fork/ccc/packages/core/src/ckb/transaction.ts:861-872` + +```typescript +async getCell(client: Client): Promise { + await this.completeExtraInfos(client); + if (!this.cellOutput || !this.outputData) { + throw new Error("Unable to complete input"); + } + return Cell.from({ + outPoint: this.previousOutput, + cellOutput: this.cellOutput, + outputData: this.outputData, + }); +} +``` + +**Confirmed:** Returns `Cell.from(...)` with `outPoint: this.previousOutput`. Since `CellInput.previousOutput` is always an `OutPoint`, the returned `Cell` always has `outPoint` set. + +### tx.outputCells getter + +**File:** `ccc-fork/ccc/packages/core/src/ckb/transaction.ts:1715-1728` + +```typescript +get outputCells(): Iterable { + const { outputs, outputsData } = this; + function* generator(): Generator { + for (let i = 0; i < outputs.length; i++) { + yield CellAny.from({ + cellOutput: outputs[i], + outputData: outputsData[i] ?? "0x", + }); + } + } + return generator(); +} +``` + +**Confirmed:** Output cells are created via `CellAny.from({ cellOutput, outputData })` -- no `outPoint` is passed, so `outPoint` is `undefined`. + +### Summary: outPoint as Input/Output Discriminator + +| Source | Type | outPoint | +|--------|------|----------| +| `getInputsInfo` -> `input.getCell(client)` | `Cell` | Always `OutPoint` | +| `getOutputsInfo` -> `tx.outputCells` | `CellAny` | Always `undefined` | +| `completeInputs` accumulator (new cells found) | `Cell` | Always `OutPoint` | + +**Verdict:** Checking `cell.outPoint` in `infoFrom` reliably distinguishes input cells from output cells. This is structural, not accidental -- `Cell` requires `outPoint`, and output generators never provide one. + +## UdtInfo Migration Mapping + +### UdtInfo structure + +**File:** `ccc-fork/ccc/packages/udt/src/udt/index.ts:218-292` + +```typescript +export class UdtInfo { + constructor( + public balance: ccc.Num, // UDT balance + public capacity: ccc.Num, // total CKB capacity of UDT cells + public count: number, // number of UDT cells + ) {} + + addAssign(infoLike: UdtInfoLike) { + const info = UdtInfo.from(infoLike); + this.balance += info.balance; + this.capacity += info.capacity; + this.count += info.count; + return this; + } +} +``` + +### Migration from [FixedPoint, FixedPoint] + +Current `IckbUdtManager.getInputsUdtBalance()` returns `[ccc.FixedPoint, ccc.FixedPoint]`: +- `[0]`: Total UDT balance (iCKB amount) -- maps to `UdtInfo.balance` +- `[1]`: Total CKB capacity -- maps to `UdtInfo.capacity` + +CCC `UdtInfo` adds `count` (number of cells) which has no equivalent in the current code. This is purely additive -- the override just tracks it alongside balance and capacity. + +| Current (IckbUdtManager) | CCC (UdtInfo) | Notes | +|---------------------------|---------------|-------| +| `acc[0]` (udtValue) | `info.balance` | Same semantics: total iCKB amount | +| `acc[1]` (capacity) | `info.capacity` | Same semantics: total CKB capacity | +| N/A | `info.count` | New field: count of cells contributing | +| `[0n, 0n]` initial | `UdtInfo.from(acc).clone()` | UdtInfo.from(undefined) defaults to `{balance: 0n, capacity: 0n, count: 0}` | + +### addAssign for accumulation + +Current code uses `return [udtValue + amount, capacity + cellCapacity]` tuple pattern. +CCC uses `acc.addAssign({ balance, capacity, count: 1 })` method pattern. + +Key difference: `addAssign` mutates in place and returns `this`. The override must use `addAssign` for each cell type (xUDT, receipt, deposit) to maintain compatibility with the accumulator pattern in `completeInputsByBalance`. + +## Header Access Verification + +### client.getTransactionWithHeader() + +**File:** `ccc-fork/ccc/packages/core/src/client/client.ts:631-661` + +```typescript +async getTransactionWithHeader( + txHashLike: HexLike, +): Promise< + | { transaction: ClientTransactionResponse; header?: ClientBlockHeader } + | undefined +> { + const txHash = hexFrom(txHashLike); + const tx = await this.cache.getTransactionResponse(txHash); + if (tx?.blockHash) { + const header = await this.getHeaderByHash(tx.blockHash); + if (header && this.cache.hasHeaderConfirmed(header)) { + return { transaction: tx, header }; + } + } + + const res = await this.getTransactionNoCache(txHash); + if (!res) { return; } + + await this.cache.recordTransactionResponses(res); + return { + transaction: res, + header: res.blockHash + ? await this.getHeaderByHash(res.blockHash) + : undefined, + }; +} +``` + +**Confirmed:** +- Returns `{ transaction: ClientTransactionResponse, header?: ClientBlockHeader } | undefined` +- `header` is a `ClientBlockHeader` which contains `dao` field with `ar` (accumulate rate) needed for `ickbValue()` +- First checks cache: `this.cache.getTransactionResponse(txHash)` -- if cached and header confirmed, returns immediately +- Falls back to network fetch: `this.getTransactionNoCache(txHash)`, then caches the response +- Subsequent calls for the same txHash are served from cache -- no redundant network requests + +### client.getCellWithHeader() + +**File:** `ccc-fork/ccc/packages/core/src/client/client.ts:212-234` + +```typescript +async getCellWithHeader( + outPointLike: OutPointLike, +): Promise<{ cell: Cell; header?: ClientBlockHeader } | undefined> { + const outPoint = OutPoint.from(outPointLike); + const res = await this.getTransactionWithHeader(outPoint.txHash); + // ... extracts cell from transaction output ... + return { cell, header }; +} +``` + +**Confirmed:** `getCellWithHeader` is a convenience wrapper around `getTransactionWithHeader`. Either can be used in `infoFrom` -- `getTransactionWithHeader` is more direct since we already have `outPoint.txHash`. + +### Caching behavior + +The `Client.cache` is checked first in `getTransactionWithHeader`. The `cache.recordTransactionResponses()` call ensures that fetched transactions are cached. This means: +- First call for a txHash: network fetch + cache store +- Subsequent calls: cache hit, no network +- Multiple receipt/deposit cells from the same transaction share the same cached header + +This is transparent to the `infoFrom` override -- just call `client.getTransactionWithHeader()` and the cache handles the rest. + +## PR #328 Compatibility + +### FeePayer abstract class + +**File:** `ccc-fork/ccc/packages/core/src/signer/feePayer/feePayer.ts:14-72` + +```typescript +export abstract class FeePayer { + constructor(protected client_: Client) {} + + abstract completeTxFee( + txLike: TransactionLike, + options?: FeeRateOptionsLike, + ): Promise; + + abstract completeInputs( + tx: Transaction, + filter: ClientCollectableSearchKeyFilterLike, + accumulator: (acc: T, v: Cell, ...) => Promise | T | undefined, + init: T, + ): Promise<{ addedCount: number; accumulated?: T }>; + + async prepareTransaction(tx: TransactionLike): Promise { + return Transaction.from(tx); + } +} +``` + +### Transaction.completeByFeePayer() + +**File:** `ccc-fork/ccc/packages/core/src/ckb/transaction.ts:2264-2275` + +```typescript +async completeByFeePayer(...feePayers: FeePayer[]): Promise { + let tx = this.clone(); + for (const feePayer of feePayers) { + tx = await feePayer.prepareTransaction(tx); + } + for (const feePayer of feePayers) { + await feePayer.completeTxFee(tx); + } + this.copy(tx); +} +``` + +### Compatibility Assessment + +The `infoFrom` override operates at a level below the completion plumbing. The call chain is: + +1. `completeInputsByBalance` -> `getInputsInfo` -> `infoFrom` (for existing inputs) +2. `completeInputsByBalance` -> `getOutputsInfo` -> `infoFrom` (for outputs) +3. `completeInputsByBalance` -> `completeInputs` -> `tx.completeInputs(from, this.filter, ...)` -> `from.completeInputs(...)` which goes to Signer or FeePayer +4. Within the accumulator: `infoFrom` is called per new cell found + +The FeePayer change affects step 3: how `completeInputs` finds and adds cells. It does NOT affect: +- How `getInputsInfo` resolves inputs to cells (still `input.getCell(client)`) +- How `getOutputsInfo` iterates outputs (still `tx.outputCells`) +- How `infoFrom` processes cells (per-cell logic, unchanged) +- The `infoFrom` signature or semantics + +**Verdict: Fully compatible.** The `infoFrom` override works with both: +- Current architecture: `from.completeInputs(this, filter, accumulator, init)` via Signer +- PR #328 architecture: `feePayer.completeInputs(tx, filter, accumulator, init)` via FeePayer + +The override point is insulated from the completion routing layer. + +## Open Questions Resolved + +### 1. Receipt/Deposit Cell Discovery in completeInputsByBalance + +**Question:** Should `IckbUdt` override `completeInputsByBalance` to also search for receipt/deposit cells? + +**Answer: No -- caller responsibility is confirmed correct.** + +**Evidence:** `Udt.completeInputs` (line 1325) delegates to `tx.completeInputs(from, this.filter, accumulator, init)`. The `this.filter` is hardcoded at construction to match only xUDT cells by type script. There is no multi-filter mechanism in `completeInputs`. + +The current architecture already handles this correctly: +- `LogicManager.completeDeposit()` adds receipt/deposit cells to the transaction +- `OwnedOwnerManager.requestWithdrawal()` adds deposit cells +- `IckbUdt.infoFrom()` then accurately VALUES these cells when `getInputsInfo`/`getOutputsInfo` processes them + +Overriding `completeInputsByBalance` to perform multiple filter searches would: +1. Require reimplementing the dual-constraint optimization logic (balance + capacity deficit) +2. Fight CCC's single-filter design pattern +3. Duplicate cell discovery logic that already exists in `LogicManager` and `OwnedOwnerManager` + +**Recommendation confirmed:** `IckbUdt` overrides only `infoFrom` for accurate balance calculation. Cell discovery is the caller's responsibility. + +### 2. capacityFree on CellAny vs Cell + +**Question:** Does `CellAny` have `capacityFree`? + +**Answer: Yes -- confirmed at transaction.ts:404-405.** + +```typescript +// CellAny class, line 404-405 +get capacityFree() { + return this.cellOutput.capacity - fixedPointFrom(this.occupiedSize); +} +``` + +`CellAny.occupiedSize` (line 394-396) = `this.cellOutput.occupiedSize + bytesFrom(this.outputData).byteLength` + +Since `Cell extends CellAny`, both classes have `capacityFree`. No need to construct `Cell` for capacity computation. + +However, `DaoManager.isDeposit()` (`packages/dao/src/dao.ts:30`) requires `ccc.Cell` (not `CellAny`): + +```typescript +isDeposit(cell: ccc.Cell): boolean { + const { cellOutput: { type }, outputData } = cell; + return outputData === DaoManager.depositData() && type?.eq(this.script) === true; +} +``` + +The `ccc.Cell` requirement is a type constraint -- `isDeposit` only reads `cellOutput` and `outputData`, not `outPoint`. But since `Cell.from()` requires `outPoint`, and deposit cells in `infoFrom` always have `outPoint` (they are input cells), constructing `Cell.from({ outPoint: cell.outPoint, cellOutput: cell.cellOutput, outputData: cell.outputData })` is safe and straightforward. + +### 3. PR #328 FeePayer Integration + +**Question:** Does `IckbUdt` need special handling for the FeePayer transition? + +**Answer: No -- confirmed by code trace above** (see "PR #328 Compatibility" section). + +The `infoFrom` override operates below the completion routing layer. Whether cells arrive via `Signer.completeInputs` or `FeePayer.completeInputs`, they flow through the same `getInputsInfo` -> `infoFrom` chain. + +### 4. Conservation Law Enforcement in IckbUdt + +**Question:** Should IckbUdt enforce the conservation law at build time? + +**Answer: Accurate balance reporting is sufficient; enforcement is out of scope for the Udt subclass.** + +**Evidence:** The conservation law is `Input UDT + Input Receipts = Output UDT + Input Deposits`. With `infoFrom` correctly valuing all three cell types: + +- `getInputsInfo` returns: xUDT balance + receipt value - deposit value (for inputs) +- `getOutputsInfo` returns: xUDT balance (for outputs, since receipt/deposit outputs don't carry iCKB value) +- `getBalanceBurned` (inherited, line 1257-1266) = inputs - outputs + +If the conservation law holds, `getBalanceBurned` returns 0 (or the intended burn amount). Callers can check this before submitting. + +Embedding enforcement in `infoFrom` would: +1. Conflate balance calculation with validation +2. Prevent legitimate partial-construction scenarios where the transaction is not yet balanced +3. Break the `completeInputsByBalance` loop which expects `infoFrom` to report current state, not validate final state + +**Recommendation confirmed:** Start with accurate balance reporting only. Validation can be added as a separate method (e.g., `assertConservationLaw(client, tx)`) if needed later. + +## IckbUdtManager -> IckbUdt Override Mapping + +### Line-by-line mapping + +**Current:** `IckbUdtManager.getInputsUdtBalance()` at `packages/core/src/udt.ts:66-141` +**Target:** `IckbUdt.infoFrom()` override + +| Current Code (IckbUdtManager) | infoFrom Override | Notes | +|-------------------------------|-------------------|-------| +| `const tx = ccc.Transaction.from(txLike)` | N/A -- `infoFrom` receives cells directly, not a transaction | Transaction handling is in `getInputsInfo`/`getOutputsInfo` | +| `ccc.reduceAsync(tx.inputs, async (acc, input) => { ... }, [0n, 0n])` | `for (const cellLike of [cells].flat()) { ... }` on `UdtInfo` accumulator | Iteration pattern differs -- `infoFrom` gets pre-resolved cells | +| `await input.completeExtraInfos(client)` | N/A -- cells are already resolved when `infoFrom` is called | `getInputsInfo` handles resolution via `input.getCell(client)` | +| `const { previousOutput: outPoint, cellOutput, outputData } = input` | `const cell = ccc.CellAny.from(cellLike)` then `cell.outPoint`, `cell.cellOutput`, `cell.outputData` | Property access pattern changes | +| `if (!cellOutput \|\| !outputData) throw ...` | N/A -- `CellAny.from()` always produces valid `cellOutput` and `outputData` (defaults to `"0x"`) | Error case eliminated by type system | +| `if (!type) return acc` | Handled by `this.isUdt(cell)` returning false for typeless cells | Implicit in isUdt check | +| `const cell = new ccc.Cell(outPoint, cellOutput, outputData)` | `const cell = ccc.CellAny.from(cellLike)` -- for `isDeposit`, construct `Cell.from(...)` when needed | Only need `Cell` for `isDeposit()` call | +| `if (this.isUdt(cell)) { return [udtValue + numFromBytes(...), capacity + ...] }` | `if (this.isUdt(cell)) { info.addAssign({ balance: Udt.balanceFromUnsafe(...), capacity: ..., count: 1 }) }` | Use `balanceFromUnsafe` instead of manual `numFromBytes(bytesFrom(outputData).slice(0, 16))` | +| `if (this.logicScript.eq(type)) { // receipt ... }` | Same logic: check `cell.cellOutput.type?.eq(this.logicScript)` | Receipt detection unchanged | +| `client.getTransactionWithHeader(outPoint.txHash)` | `client.getTransactionWithHeader(cell.outPoint!.txHash)` | `outPoint` available since receipt cells are inputs (verified above) | +| `ReceiptData.decode(outputData)` | `ReceiptData.decode(cell.outputData)` | Direct access to `outputData` | +| `ickbValue(depositAmount, header) * depositQuantity` | Same computation | `ickbValue` function unchanged | +| `return [udtValue + receiptValue, capacity + ...]` | `info.addAssign({ balance: receiptValue, capacity: ..., count: 1 })` | Use addAssign pattern | +| `if (this.logicScript.eq(lock) && this.daoManager.isDeposit(cell))` | Check `cell.cellOutput.lock.eq(this.logicScript)` then construct `Cell.from(...)` for `isDeposit` | Need Cell construction for `isDeposit` | +| `ickbValue(cell.capacityFree, header)` | `ickbValue(cell.capacityFree, header)` -- `capacityFree` available on `CellAny` | Direct access, no Cell needed for capacity | +| `return [udtValue - depositValue, capacity + ...]` | `info.addAssign({ balance: -depositValue, capacity: ..., count: 1 })` | Negative balance via addAssign | +| Output cells: handled by separate `getOutputsUdtBalance` | Output cells flow through same `infoFrom` -- only `isUdt` check matches (no receipt/deposit for outputs) | Unified by `outPoint` check: `if (!cell.outPoint) continue` for receipt/deposit logic | + +### Key behavioral difference: Outputs + +Current `IckbUdtManager` has separate methods: +- `getInputsUdtBalance()`: processes xUDT + receipt + deposit inputs +- No explicit output override -- `UdtManager.getOutputsUdtBalance()` handles standard xUDT outputs + +New `IckbUdt.infoFrom()` handles BOTH inputs and outputs in a single method. For output cells (no `outPoint`), only the `isUdt` check applies -- receipt/deposit output cells are skipped because: +1. `cell.outPoint` is `undefined` for outputs +2. Receipt/deposit logic is gated behind `if (!cell.outPoint) continue` +3. The base `isUdt` check catches standard xUDT output cells + +This is correct because: +- iCKB receipt outputs are newly created receipts (by `LogicManager.deposit`), not value carriers for balance +- iCKB deposit outputs have DAO type script, not iCKB UDT type script -- `isUdt` returns false +- Only standard xUDT outputs carry iCKB value in the output direction + +### Constructor migration + +| Current (IckbUdtManager) | Target (IckbUdt) | Notes | +|--------------------------|-------------------|-------| +| `constructor(script, cellDeps, logicScript, daoManager)` | `constructor(code, script, logicScript, daoManager, config?)` | New `code` param (OutPoint for cell deps), `config` optional | +| `super(script, cellDeps, "iCKB", "iCKB", 8)` | `super(code, script, config)` | Base class changes: UdtManager -> Udt | +| `this.logicScript` | `this.logicScript` | Preserved | +| `this.daoManager` | `this.daoManager` | Preserved | +| N/A | `this.script` (from Udt base) | Replaces `UdtManager.script` | +| `this.cellDeps` | `this.code` (OutPoint) + `addCellDeps()` | CCC uses OutPoint for code dep, not explicit CellDep array | +| `this.name`, `this.symbol`, `this.decimals` | Via `udt.name()`, `udt.symbol()`, `udt.decimals()` (SSRI) or custom properties | Metadata access changes | + +## Edge Cases and Risks + +### 1. CellAny.from() coerces both outPoint and previousOutput + +**File:** `transaction.ts:384` +```typescript +apply(OutPoint.from, cell.outPoint ?? cell.previousOutput), +``` + +If a `CellAnyLike` has `previousOutput` but not `outPoint`, the resulting `CellAny` still gets `outPoint` set. This is safe -- both names refer to the same concept. But within `infoFrom`, always check `cell.outPoint` (not `cell.previousOutput`, which doesn't exist on `CellAny`). + +### 2. DaoManager.isDeposit() type requirement + +`DaoManager.isDeposit()` accepts `ccc.Cell`, not `CellAny`. Since deposit cells are only relevant as inputs (where `outPoint` is always present), constructing `Cell.from({ outPoint: cell.outPoint!, cellOutput: cell.cellOutput, outputData: cell.outputData })` is always valid. The non-null assertion on `outPoint` is safe because the deposit check is gated behind `if (!cell.outPoint) continue`. + +### 3. UdtInfo.balance allows negative values + +`UdtInfo.balance` is `ccc.Num` (bigint), not unsigned. The `addAssign` method does `this.balance += info.balance`. For deposit cells, passing `balance: -depositIckbValue` works because bigint addition with negative values is well-defined. This is tested by `completeInputsByBalance` which checks `info.balance >= ccc.Zero` as its termination condition -- negative balance contributions from deposits are correctly accumulated. + +### 4. Cell discovery gap: completeInputsByBalance only finds xUDT cells + +As documented in Open Question #1, `Udt.filter` only matches xUDT cells. If a caller relies solely on `completeInputsByBalance` to provide all iCKB value, receipt and deposit cells will be missed. This is by design -- callers must pre-add receipt/deposit cells (via `LogicManager`/`OwnedOwnerManager`) before calling `completeInputsByBalance`. + +Risk mitigation: Document this clearly in `IckbUdt` API documentation. The `completeInputsByBalance` inherited method correctly accounts for pre-added receipt/deposit inputs via `getInputsInfo` -> `infoFrom`. + +### 5. Async infoFrom and potential performance + +The base `infoFrom` is synchronous (returns `Promise` but internally uses `.reduce()` without `await`). The override will be truly async due to `client.getTransactionWithHeader()` calls. This changes `infoFrom` from O(1) network calls to O(n) where n is the number of receipt/deposit cells in the input. + +Mitigation: +- CCC `Client.cache` ensures each txHash is fetched at most once per session +- Receipt/deposit cells are typically few per transaction (1-5) +- Multiple cells from the same transaction share one cached header fetch +- The async signature is already declared on the base class, so no interface change needed + +### 6. No upstream CCC changes required + +The investigation confirms that `IckbUdt extends udt.Udt` with `infoFrom` override requires ZERO changes to CCC's Udt class. All override points are public methods with appropriate signatures. This eliminates the dealbreaker risk identified in CONTEXT.md. + +--- + +*Investigation: Phase 03-ccc-udt-integration-investigation, Plan 01* +*Completed: 2026-02-24* diff --git a/.planning/phases/03-ccc-udt-integration-investigation/03-01-PLAN.md b/.planning/phases/03-ccc-udt-integration-investigation/03-01-PLAN.md new file mode 100644 index 0000000..edc0fb9 --- /dev/null +++ b/.planning/phases/03-ccc-udt-integration-investigation/03-01-PLAN.md @@ -0,0 +1,136 @@ +--- +phase: 03-ccc-udt-integration-investigation +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - .planning/phases/03-ccc-udt-integration-investigation/03-01-INVESTIGATION.md +autonomous: true +requirements: + - UDT-01 + - UDT-02 + +must_haves: + truths: + - "Every CCC Udt method chain (getInputsInfo -> infoFrom, getOutputsInfo -> infoFrom, completeInputsByBalance -> getInputsInfo/getOutputsInfo) is traced with exact line references showing how cells flow" + - "The outPoint presence/absence mechanism for distinguishing input vs output cells in infoFrom is verified with concrete code evidence from CCC source" + - "The async infoFrom override with header fetches is confirmed compatible with CCC's completion loop" + - "All open questions from 03-RESEARCH.md (filter mismatch, capacityFree on CellAny, PR #328 compatibility, conservation law enforcement) are answered with code evidence" + - "The IckbUdtManager -> IckbUdt migration path is mapped: constructor parameters, method signature changes, return type differences (UdtInfo vs [FixedPoint, FixedPoint])" + artifacts: + - path: ".planning/phases/03-ccc-udt-integration-investigation/03-01-INVESTIGATION.md" + provides: "Detailed source code trace findings with exact line references and code snippets" + contains: "## CCC Udt Method Chain Trace" + key_links: + - from: "ccc-fork/ccc/packages/udt/src/udt/index.ts" + to: "packages/core/src/udt.ts" + via: "infoFrom override replacing getInputsUdtBalance" + pattern: "infoFrom.*CellAnyLike" +--- + + +Trace CCC Udt class internals end-to-end against actual source code and verify the feasibility of the `IckbUdt extends udt.Udt` approach identified in 03-RESEARCH.md. Resolve all open questions. + +Purpose: Provide verified, source-code-backed evidence for the decision document (Plan 02). The research identified `infoFrom` as the optimal override point -- this plan validates that finding by tracing real method chains and checking edge cases. + +Output: `03-01-INVESTIGATION.md` -- structured findings with exact line references, code snippets, and answers to each open question. + + + +@/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/03-ccc-udt-integration-investigation/03-CONTEXT.md +@.planning/phases/03-ccc-udt-integration-investigation/03-RESEARCH.md + + + + + + Task 1: Trace CCC Udt internals and verify override feasibility + .planning/phases/03-ccc-udt-integration-investigation/03-01-INVESTIGATION.md + +Read and trace the following CCC source files end-to-end to verify research findings: + +1. **Udt class and infoFrom** (`ccc-fork/ccc/packages/udt/src/udt/index.ts`): + - Trace `infoFrom()` signature, return type, how it accumulates `UdtInfo` + - Trace `getInputsInfo()` -- how it resolves `CellInput` to `Cell` via `input.getCell(client)`, confirm output `Cell` objects have `outPoint` set + - Trace `getOutputsInfo()` -- how it iterates `tx.outputCells`, confirm yielded `CellAny` objects lack `outPoint` + - Trace `completeInputsByBalance()` -- full chain through `getInputsInfo`/`getOutputsInfo`/`infoFrom`, how balance deficit drives completion + - Check `Udt` constructor -- what `filter` is set to, confirm it only matches xUDT cells + - Check `isUdt()` method -- how it differs from the current `UdtManager.isUdt()` in `@ickb/utils` + - Check `Udt.balanceFromUnsafe()` -- how it reads UDT balance from output data + +2. **CellAny and Cell types** (`ccc-fork/ccc/packages/core/src/ckb/transaction.ts`): + - Verify `CellAny.outPoint` is `OutPoint | undefined` (not nullable) + - Verify `CellAny` vs `Cell` -- confirm `CellAny` has `capacityFree` getter (pre-resolved: transaction.ts:404-405) + - Check `Cell.capacityFree` -- exact computation (capacity - occupiedSize) + - Check `CellInput.getCell(client)` -- confirm returned Cell has outPoint set + - Check `tx.outputCells` getter -- confirm yielded values are `CellAny` without outPoint + +3. **UdtInfo type** (`ccc-fork/ccc/packages/udt/src/udt/index.ts` or related): + - Trace `UdtInfo` fields: balance, capacity, count + - Trace `UdtInfo.addAssign()` method + - Compare with current `IckbUdtManager.getInputsUdtBalance()` return type `[FixedPoint, FixedPoint]` -- map the migration + +4. **Header access** (`ccc-fork/ccc/packages/core/src/client/client.ts`): + - Verify `client.getTransactionWithHeader(txHash)` returns `{ transaction, header? }` where header has `.dao.ar` + - Confirm CCC Client.cache handles repeated calls transparently + +5. **PR #328 compatibility check** (`ccc-fork/ccc/packages/core/src/signer/feePayer/`): + - Check whether PR #328's FeePayer changes affect `infoFrom` or `getInputsInfo`/`getOutputsInfo` method signatures + - Confirm `infoFrom` override is compatible with both current and PR #328 architectures + +6. **Current IckbUdtManager analysis** (`packages/core/src/udt.ts`, `packages/utils/src/udt.ts`): + - Map each line of `IckbUdtManager.getInputsUdtBalance()` to the equivalent `infoFrom` override logic + - Identify the `UdtManager.isUdt()` check -- verify compatibility with CCC `Udt.isUdt()` + - Note: current `getInputsUdtBalance` iterates `tx.inputs` with `completeExtraInfos` -- CCC `getInputsInfo` uses `input.getCell(client)` instead. Verify these are equivalent. + - Identify what `getOutputsUdtBalance()` does for iCKB -- it only counts xUDT output cells (no receipt/deposit outputs carry iCKB value), so the base `infoFrom` logic for outputs should work unchanged + +Write findings as `03-01-INVESTIGATION.md` with these sections: +- **## CCC Udt Method Chain Trace** -- with exact file:line references and key code snippets +- **## CellAny vs Cell: outPoint and capacityFree** -- verified behavior with evidence +- **## UdtInfo Migration Mapping** -- how [FixedPoint, FixedPoint] maps to UdtInfo +- **## Header Access Verification** -- confirmed API and caching +- **## PR #328 Compatibility** -- confirmed or flagged +- **## Open Questions Resolved** -- answers to each open question from 03-RESEARCH.md with code evidence +- **## IckbUdtManager -> IckbUdt Override Mapping** -- line-by-line mapping of current logic to infoFrom override +- **## Edge Cases and Risks** -- any issues discovered during trace + + + test -f .planning/phases/03-ccc-udt-integration-investigation/03-01-INVESTIGATION.md && grep -q "## CCC Udt Method Chain Trace" .planning/phases/03-ccc-udt-integration-investigation/03-01-INVESTIGATION.md && grep -q "## Open Questions Resolved" .planning/phases/03-ccc-udt-integration-investigation/03-01-INVESTIGATION.md && grep -q "## IckbUdtManager -> IckbUdt Override Mapping" .planning/phases/03-ccc-udt-integration-investigation/03-01-INVESTIGATION.md && echo "PASS" + Verify investigation references actual CCC source line numbers and includes code snippets for each finding + + + - Every CCC Udt method chain is traced with file:line references + - outPoint presence/absence mechanism verified with CellAny/Cell source evidence + - capacityFree availability on CellAny answered definitively + - UdtInfo fields mapped to current [FixedPoint, FixedPoint] return type + - PR #328 compatibility confirmed or risks flagged + - All 4 open questions from 03-RESEARCH.md answered with code evidence + - Line-by-line mapping from IckbUdtManager.getInputsUdtBalance to infoFrom override exists + + + + + + +- `03-01-INVESTIGATION.md` exists with all required sections +- Each finding includes a source file path and line number reference +- All open questions from 03-RESEARCH.md are addressed +- The IckbUdtManager -> IckbUdt mapping is complete (no unmapped logic) + + + +The investigation document provides sufficient verified evidence for Plan 02 to write a confident decision document. Every claim from 03-RESEARCH.md is either confirmed or corrected with source code evidence. + + + +After completion, create `.planning/phases/03-ccc-udt-integration-investigation/03-01-SUMMARY.md` + diff --git a/.planning/phases/03-ccc-udt-integration-investigation/03-01-SUMMARY.md b/.planning/phases/03-ccc-udt-integration-investigation/03-01-SUMMARY.md new file mode 100644 index 0000000..410583c --- /dev/null +++ b/.planning/phases/03-ccc-udt-integration-investigation/03-01-SUMMARY.md @@ -0,0 +1,109 @@ +--- +phase: 03-ccc-udt-integration-investigation +plan: 01 +subsystem: udt +tags: [ccc, udt, subclass, infoFrom, override, header-access] + +# Dependency graph +requires: + - phase: 01-ickb-utils-smarttransaction-removal + provides: "IckbUdtManager with TransactionLike pattern, DaoManager with isDeposit()" + - phase: 02-ccc-utility-adoption + provides: "Clean codebase without deprecated CCC APIs" +provides: + - "Verified source-code-backed evidence for IckbUdt extends udt.Udt feasibility" + - "Complete infoFrom override mapping from IckbUdtManager.getInputsUdtBalance()" + - "Confirmed PR #328 compatibility, header access pattern, and outPoint discriminator" +affects: [03-02-decision-document, 04-deprecated-api-replacement, 05-ickb-udt-implementation] + +# Tech tracking +tech-stack: + added: [] + patterns: + - "infoFrom override: per-cell balance calculation with outPoint-based input/output discrimination" + - "UdtInfo accumulator: addAssign pattern replacing tuple accumulation" + +key-files: + created: + - ".planning/phases/03-ccc-udt-integration-investigation/03-01-INVESTIGATION.md" + modified: [] + +key-decisions: + - "infoFrom is the sole override point -- no need to override getInputsInfo/getOutputsInfo" + - "No upstream CCC changes required for IckbUdt subclass" + - "Caller responsibility for receipt/deposit cell discovery (not IckbUdt's filter)" + - "Accurate balance reporting only -- conservation law enforcement is separate" + +patterns-established: + - "outPoint presence/absence as input/output cell discriminator in infoFrom" + - "DaoManager.isDeposit() requires Cell construction from CellAny when outPoint present" + - "UdtInfo.balance supports negative values for deposit cell subtraction" + +requirements-completed: [UDT-01, UDT-02] + +# Metrics +duration: 5min +completed: 2026-02-24 +--- + +# Phase 3 Plan 1: CCC Udt Investigation Summary + +**End-to-end CCC Udt method chain trace confirming infoFrom as optimal override point for IckbUdt subclass -- all open questions resolved with source code evidence, no upstream CCC changes needed** + +## Performance + +- **Duration:** 5 min +- **Started:** 2026-02-24T11:19:15Z +- **Completed:** 2026-02-24T11:23:54Z +- **Tasks:** 1 +- **Files modified:** 1 + +## Accomplishments +- Traced every CCC Udt method chain (infoFrom, getInputsInfo, getOutputsInfo, completeInputsByBalance, completeInputs) with exact file:line references +- Verified outPoint presence/absence as reliable input/output cell discriminator (CellInput.getCell always sets outPoint; tx.outputCells never does) +- Confirmed capacityFree available on CellAny (transaction.ts:404-405) -- no Cell construction needed for deposit cell iCKB value +- Mapped UdtInfo fields to current [FixedPoint, FixedPoint] return type with addAssign migration pattern +- Confirmed PR #328 FeePayer compatibility -- infoFrom operates below the completion routing layer +- Resolved all 4 open questions from 03-RESEARCH.md with code evidence +- Created complete line-by-line mapping from IckbUdtManager.getInputsUdtBalance to infoFrom override + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Trace CCC Udt internals and verify override feasibility** - `b2827e5` (docs) + +## Files Created/Modified +- `.planning/phases/03-ccc-udt-integration-investigation/03-01-INVESTIGATION.md` - Detailed source code trace findings with exact line references, code snippets, migration mapping, and resolved open questions + +## Decisions Made +- **infoFrom is the sole override point:** No need to override getInputsInfo or getOutputsInfo -- infoFrom handles both input and output cells uniformly via outPoint check +- **No upstream CCC changes required:** The Udt class API surface is sufficient for IckbUdt subclassing without modification +- **Caller responsibility for cell discovery:** IckbUdt.filter only matches xUDT cells; receipt/deposit cells must be pre-added by LogicManager/OwnedOwnerManager +- **Accurate balance reporting only:** Conservation law enforcement is separate from infoFrom -- can be added as validation method later + +## Deviations from Plan + +None -- plan executed exactly as written. + +## Issues Encountered +None + +## User Setup Required +None - no external service configuration required. + +## Next Phase Readiness +- Investigation provides complete evidence base for Plan 02 (decision document) +- All source code references verified and documented +- Migration mapping is complete -- no unmapped logic from IckbUdtManager +- Ready to write confident decision document recommending IckbUdt extends udt.Udt + +## Self-Check: PASSED + +- FOUND: `.planning/phases/03-ccc-udt-integration-investigation/03-01-INVESTIGATION.md` +- FOUND: `.planning/phases/03-ccc-udt-integration-investigation/03-01-SUMMARY.md` +- FOUND: commit `b2827e5` + +--- +*Phase: 03-ccc-udt-integration-investigation* +*Completed: 2026-02-24* diff --git a/.planning/phases/03-ccc-udt-integration-investigation/03-02-PLAN.md b/.planning/phases/03-ccc-udt-integration-investigation/03-02-PLAN.md new file mode 100644 index 0000000..2c281d2 --- /dev/null +++ b/.planning/phases/03-ccc-udt-integration-investigation/03-02-PLAN.md @@ -0,0 +1,212 @@ +--- +phase: 03-ccc-udt-integration-investigation +plan: 02 +type: execute +wave: 2 +depends_on: + - "03-01" +files_modified: + - .planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md +autonomous: true +requirements: + - UDT-01 + - UDT-02 + - UDT-03 + +must_haves: + truths: + - "A feasibility assessment exists answering whether IckbUdt extends udt.Udt can override infoFrom to account for receipt cells and deposit cells alongside xUDT cells" + - "The header access pattern for receipt/deposit value calculation is designed with specific API calls and async flow documented" + - "A clear decision is documented with one of three outcomes (subclass, custom, hybrid) and rationale for the chosen path" + - "The conservation law preservation strategy is documented (how sign conventions and cell type handling maintain Input UDT + Input Receipts = Output UDT + Input Deposits)" + - "The cell discovery vs balance calculation boundary is defined (what infoFrom handles vs what callers handle)" + - "The decision document provides sufficient detail for Phase 4 and Phase 5 implementers to proceed without ambiguity" + artifacts: + - path: ".planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md" + provides: "Formal decision document covering UDT-01 (feasibility), UDT-02 (header pattern), UDT-03 (decision)" + contains: "## Decision" + key_links: + - from: ".planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md" + to: ".planning/ROADMAP.md" + via: "Decision outcome determines Phase 4 and Phase 5 approach" + pattern: "Phase [45]" +--- + + +Write the formal decision document synthesizing the investigation findings into actionable outcomes for UDT-01, UDT-02, and UDT-03. This document directly determines how Phases 4 and 5 implement UDT handling. + +Purpose: Close Phase 3's investigation with a clear, justified decision that eliminates ambiguity for downstream phases. + +Output: `03-DECISION.md` -- the formal feasibility assessment, header access pattern design, and decision with rationale. + + + +@/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/03-ccc-udt-integration-investigation/03-CONTEXT.md +@.planning/phases/03-ccc-udt-integration-investigation/03-RESEARCH.md +@.planning/phases/03-ccc-udt-integration-investigation/03-01-SUMMARY.md + + + + + + Task 1: Write feasibility assessment and header access pattern (UDT-01, UDT-02) + .planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md + +Using the verified findings from `03-01-INVESTIGATION.md` (via 03-01-SUMMARY.md), write the first two sections of the decision document: + +**## Feasibility Assessment (UDT-01)** + +Structure as a clear YES/NO/CONDITIONAL answer with supporting evidence: + +1. **Override Point**: Which override point (`infoFrom` vs `getInputsInfo`/`getOutputsInfo`) and why. Reference specific findings from the investigation. + +2. **Three Cell Types in infoFrom**: + - xUDT cells: How `isUdt()` identifies them, how balance is read (reference CCC's `balanceFromUnsafe` vs current `ccc.numFromBytes` approach) + - Receipt cells: How type script matching identifies them, how `ReceiptData.decode` + `ickbValue()` computes value, positive balance contribution + - Deposit cells: How lock script matching + `isDeposit()` identifies them, how `ickbValue(capacityFree)` computes value, NEGATIVE balance contribution (conservation law sign convention) + +3. **Input vs Output Distinction**: How outPoint presence/absence in `infoFrom` cleanly separates input cells (receipt/deposit relevant) from output cells (xUDT only). Reference CellAny investigation findings. + +4. **capacityFree Resolution**: How to access unoccupied capacity for deposit cells within `infoFrom`. Use investigation findings about CellAny vs Cell. + +5. **Completion Pipeline Compatibility**: Confirm that `completeInputsByBalance` chains through `infoFrom` correctly, noting the filter limitation (xUDT only) and the caller-responsibility pattern for receipt/deposit cell discovery. + +6. **Blockers**: List any findings that would make subclassing unviable. If none, state "No blockers identified." + +**## Header Access Pattern (UDT-02)** + +Design the specific pattern: + +1. **API**: `client.getTransactionWithHeader(outPoint.txHash)` -- confirmed from investigation +2. **When**: Only for input cells with outPoint (receipt cells and deposit cells) +3. **What**: Extract `header.dao.ar` for exchange rate computation via `ickbValue()` +4. **Caching**: CCC Client.cache handles repeated calls -- no application-level caching needed +5. **Async flow**: `infoFrom` is already async -- header fetches integrate naturally +6. **Performance**: Few receipt/deposit cells per transaction in practice; caching mitigates repeated fetches in completion loops + +Include a code sketch showing the header fetch pattern within the infoFrom override (referencing the conceptual prototype from 03-RESEARCH.md, updated with any corrections from the investigation). + + + test -f .planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md && grep -q "## Feasibility Assessment" .planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md && grep -q "## Header Access Pattern" .planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md && echo "PASS" + Verify feasibility assessment addresses all sub-questions and header pattern is specific enough for implementation + + + - Feasibility assessment answers YES/NO/CONDITIONAL with evidence + - All three cell types documented with identification and valuation logic + - Input vs output distinction mechanism documented + - capacityFree access resolved + - Header access pattern fully designed with API, timing, caching, async flow + - Code sketch provided + + + + + Task 2: Write decision and implementation guidance (UDT-03) + .planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md + +Append the decision and implementation guidance sections to `03-DECISION.md`: + +**## Decision (UDT-03)** + +State the chosen approach clearly: +- **Chosen**: One of (a) subclass CCC Udt, (b) keep custom UdtHandler interface, (c) hybrid approach +- **Rationale**: Why this approach wins, referencing feasibility assessment findings +- **What it replaces**: Current `UdtHandler` interface + `UdtManager` class in `@ickb/utils` and `IckbUdtManager` in `@ickb/core` +- **What CCC features it gains**: `completeInputsByBalance`, `UdtInfo` accumulator, CCC completion pipeline integration, future upstream improvements +- **What it loses or changes**: Any behavior differences from current implementation + +Per user decisions from CONTEXT.md: +- If subclassing works, that is the preferred path (user is "leaning toward IckbUdt extends udt.Udt") +- If subclassing requires upstream CCC changes, design them generically as "composite UDT" pattern +- Dealbreaker = invasive upstream changes; if blocked, reevaluate WHY and determine minimal CCC changes + +**## Conservation Law Strategy** + +Document how the conservation law `Input UDT + Input Receipts = Output UDT + Input Deposits` is preserved: +- `infoFrom` reports accurate balances with correct sign conventions (deposits negative) +- Enforcement location: on-chain (iCKB Logic script) vs build-time (validation method on IckbUdt) +- Recommendation per CONTEXT.md: start with accurate balance reporting (caller responsibility), add build-time validation later if needed +- Conservation law is NOT embedded in `infoFrom` (balance calculation, not validation) + +**## Cell Discovery vs Balance Calculation Boundary** + +Define the clean separation: +- `infoFrom` responsibility: VALUE cells that are already in the transaction +- Caller responsibility (LogicManager, OwnedOwnerManager): FIND and ADD receipt/deposit cells to the transaction +- `completeInputsByBalance` + `filter`: only finds xUDT cells (this is correct -- receipt/deposit discovery is a separate concern) +- No override of `filter` needed -- callers pre-add special cells, `infoFrom` accurately values them + +**## Implementation Guidance for Phases 4-5** + +Provide actionable guidance: + +Phase 4 (dao/order packages): +- Replace deprecated API calls with `udt.Udt` instance methods +- `udtBalanceFrom` -> `Udt.balanceFromUnsafe()` +- `tx.getInputsUdtBalance()` / `tx.getOutputsUdtBalance()` -> `udt.getInputsInfo()` / `udt.getOutputsInfo()` +- `tx.completeInputsByUdt()` -> `udt.completeInputsByBalance()` +- DaoManager and OrderManager receive a `udt.Udt` instance instead of `UdtHandler` + +Phase 5 (core package): +- Implement `IckbUdt extends udt.Udt` with `infoFrom` override in `packages/core/src/udt.ts` +- Constructor: `code` (OutPoint for xUDT cell dep), `script` (xUDT type script), `logicScript`, `daoManager`, optional `config` +- Override `infoFrom` with three-cell-type logic +- Delete `IckbUdtManager` class from `packages/core/src/udt.ts` +- Delete `UdtHandler` interface and `UdtManager` class from `packages/utils/src/udt.ts` +- Preserve `ickbValue()`, `convert()`, `ickbExchangeRatio()` functions (used by SDK estimate scenarios) +- Update SDK to use `IckbUdt` instance + +**## Upstream CCC Changes** + +Document whether any upstream CCC PRs are needed: +- If `infoFrom` override works without CCC changes: state "No upstream changes required for core feasibility" +- If any issues found: describe minimal, generic changes and assess likely acceptance +- Note: PR #328 (FeePayer) is orthogonal -- `infoFrom` override is compatible with both current and FeePayer architectures + +**## Risks and Mitigations** + +List residual risks with mitigations: +- Filter mismatch for receipt/deposit cell discovery -> mitigation: caller-responsibility pattern +- `DaoManager.isDeposit()` requires `Cell` not `CellAny` -> mitigation: construct `Cell.from(...)` when outPoint present (capacityFree is available on CellAny directly) +- Completion loop performance with header fetches -> mitigation: CCC Client.cache + few cells per tx +- Any other risks identified in the investigation + + + grep -q "## Decision" .planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md && grep -q "## Implementation Guidance" .planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md && grep -q "## Conservation Law Strategy" .planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md && grep -q "## Risks and Mitigations" .planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md && echo "PASS" + Verify decision is clear and unambiguous, implementation guidance is specific enough for Phase 4/5 executors + + + - Decision clearly states chosen approach with rationale + - Conservation law strategy documented + - Cell discovery vs balance calculation boundary defined + - Phase 4 and Phase 5 implementation guidance is specific and actionable + - Upstream CCC changes assessed + - Risks listed with mitigations + - Document is self-contained -- a reader can understand the decision without needing to read the investigation + + + + + + +- `03-DECISION.md` exists with all required sections (Feasibility Assessment, Header Access Pattern, Decision, Conservation Law Strategy, Cell Discovery Boundary, Implementation Guidance, Upstream Changes, Risks) +- The decision is one of the three specified outcomes (a, b, or c) with clear rationale +- Implementation guidance for Phases 4 and 5 is specific enough to plan those phases without revisiting Phase 3 +- All three requirement IDs (UDT-01, UDT-02, UDT-03) have corresponding sections + + + +A Phase 4/5 planner can read `03-DECISION.md` and know exactly: (1) whether to subclass CCC Udt or use a custom approach, (2) how header access works in the override, (3) what classes to create/delete/modify, and (4) how the conservation law is preserved. No re-investigation needed. + + + +After completion, create `.planning/phases/03-ccc-udt-integration-investigation/03-02-SUMMARY.md` + diff --git a/.planning/phases/03-ccc-udt-integration-investigation/03-02-SUMMARY.md b/.planning/phases/03-ccc-udt-integration-investigation/03-02-SUMMARY.md new file mode 100644 index 0000000..62fcdaa --- /dev/null +++ b/.planning/phases/03-ccc-udt-integration-investigation/03-02-SUMMARY.md @@ -0,0 +1,117 @@ +--- +phase: 03-ccc-udt-integration-investigation +plan: 02 +subsystem: udt +tags: [ccc, udt, subclass, infoFrom, decision-document, header-access, conservation-law] + +# Dependency graph +requires: + - phase: 03-ccc-udt-integration-investigation + plan: 01 + provides: "Source code trace evidence for infoFrom override feasibility, resolved open questions" + - phase: 01-ickb-utils-smarttransaction-removal + provides: "IckbUdtManager with TransactionLike pattern, DaoManager with isDeposit()" + - phase: 02-ccc-utility-adoption + provides: "Clean codebase without deprecated CCC APIs" +provides: + - "Formal decision: IckbUdt extends udt.Udt with infoFrom override" + - "Complete implementation guidance for Phases 4 and 5" + - "Conservation law strategy (accurate balance reporting, caller responsibility)" + - "Cell discovery vs balance calculation boundary definition" + - "Deprecated API replacement table for dao/order/core packages" +affects: [04-deprecated-api-replacement, 05-ickb-udt-implementation, 06-sdk-completion] + +# Tech tracking +tech-stack: + added: [] + patterns: + - "infoFrom override: per-cell balance calculation with outPoint-based input/output discrimination" + - "Three cell types: xUDT (positive), receipt (positive, input only), deposit (negative, input only)" + - "Caller-responsibility pattern for receipt/deposit cell discovery" + +key-files: + created: + - ".planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md" + modified: [] + +key-decisions: + - "Subclass CCC Udt: IckbUdt extends udt.Udt with infoFrom override (option a)" + - "No upstream CCC changes required for IckbUdt subclass" + - "Conservation law: accurate balance reporting only, enforcement is on-chain and optionally build-time later" + - "Cell discovery boundary: infoFrom values cells, callers find and add them" + - "UdtHandler interface and UdtManager class to be deleted in Phase 5" + +patterns-established: + - "Deprecated API replacement: udtBalanceFrom -> balanceFromUnsafe, getInputsUdtBalance -> getInputsInfo, etc." + - "DaoManager/OrderManager receive udt.Udt instance instead of UdtHandler" + +requirements-completed: [UDT-01, UDT-02, UDT-03] + +# Metrics +duration: 4min +completed: 2026-02-24 +--- + +# Phase 3 Plan 2: CCC Udt Decision Document Summary + +**Formal decision document choosing IckbUdt extends udt.Udt with infoFrom override -- feasibility confirmed, header access pattern designed, implementation guidance for Phases 4-5 with deprecated API replacement table and conservation law strategy** + +## Performance + +- **Duration:** 4 min +- **Started:** 2026-02-24T11:27:42Z +- **Completed:** 2026-02-24T11:31:27Z +- **Tasks:** 2 +- **Files modified:** 1 + +## Accomplishments +- Wrote complete decision document with all 8 required sections (Feasibility Assessment, Header Access Pattern, Decision, Conservation Law Strategy, Cell Discovery Boundary, Implementation Guidance, Upstream Changes, Risks) +- Decision: (a) subclass CCC Udt -- IckbUdt extends udt.Udt with infoFrom override, no upstream CCC changes needed +- Conservation law strategy: accurate balance reporting with correct sign conventions (deposits negative), enforcement on-chain only, optional build-time validation later +- Phase 4 guidance: deprecated API replacement table mapping old APIs to new Udt instance methods +- Phase 5 guidance: IckbUdt creation in core package, UdtHandler/UdtManager deletion from utils package, SDK update to use IckbUdt instance +- Six risks documented with concrete mitigations (filter mismatch, isDeposit type, header performance, negative balance, output misidentification, return type changes) + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Write feasibility assessment and header access pattern (UDT-01, UDT-02)** - `681248e` (docs) +2. **Task 2: Write decision and implementation guidance (UDT-03)** - `8daffd7` (docs) + +## Files Created/Modified +- `.planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md` - Formal decision document: feasibility YES, header access via getTransactionWithHeader, decision to subclass with infoFrom override, conservation law strategy, cell discovery boundary, implementation guidance for Phases 4-5, upstream assessment, risks and mitigations + +## Decisions Made +- **Subclass CCC Udt (option a):** IckbUdt extends udt.Udt with single infoFrom override handling three cell types (xUDT, receipt, deposit) with outPoint-based input/output discrimination +- **No upstream CCC changes required:** All override points are public with appropriate signatures +- **Conservation law: accurate reporting only:** infoFrom computes balance, does not validate conservation law. On-chain script is authoritative enforcer. Build-time validation can be added later as separate method +- **Cell discovery boundary:** infoFrom values cells already in transaction; LogicManager/OwnedOwnerManager find and add receipt/deposit cells; completeInputsByBalance only finds xUDT cells via filter +- **UdtHandler/UdtManager deletion:** Replaced by udt.Udt type and base class in Phase 5 + +## 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 3 investigation is complete -- decision document provides all information needed for Phases 4-5 +- Phase 4 can proceed: deprecated API replacement table specifies exact old->new API mappings for dao and order packages +- Phase 5 can proceed: IckbUdt class specification (constructor, override, deletions, preservations) is fully documented +- No re-investigation needed -- document is self-contained for downstream phase planners + +## Self-Check: PASSED + +- FOUND: `.planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md` +- FOUND: `.planning/phases/03-ccc-udt-integration-investigation/03-02-SUMMARY.md` +- FOUND: commit `681248e` +- FOUND: commit `8daffd7` + +--- +*Phase: 03-ccc-udt-integration-investigation* +*Completed: 2026-02-24* diff --git a/.planning/phases/03-ccc-udt-integration-investigation/03-CONTEXT.md b/.planning/phases/03-ccc-udt-integration-investigation/03-CONTEXT.md new file mode 100644 index 0000000..b4c09d4 --- /dev/null +++ b/.planning/phases/03-ccc-udt-integration-investigation/03-CONTEXT.md @@ -0,0 +1,75 @@ +# Phase 3: CCC Udt Integration Investigation - Context + +**Gathered:** 2026-02-23 +**Status:** Ready for planning + + +## Phase Boundary + +Assess feasibility of subclassing CCC's `udt.Udt` class for iCKB's multi-representation value (xUDT + receipts + deposits). Design the header access pattern. Document the decision. This feeds directly into Phases 4 and 5 — the decision determines how UdtHandler/UdtManager (which remain in `@ickb/utils` with updated signatures after Phase 1) get replaced. + + + + +## Implementation Decisions + +### Evaluation priorities +- CCC alignment is the primary driver — iCKB should feel native to CCC users and benefit from upstream improvements +- Upstream CCC PRs are explicitly on the table if CCC's Udt class needs small, targeted changes to accommodate iCKB's multi-representation value +- No concern about CCC upgrade risk — if we contribute to CCC's Udt, we co-own the design +- PR #328 (FeePayer abstraction by ashuralyk) is the target architecture — investigation should design around it and identify improvements that would better fit iCKB's needs. Now integrated into `ccc-fork/ccc` (available at `ccc-fork/ccc/packages/core/src/signer/feePayer/`) +- Investigation should cover both cell discovery and balance calculation, not just balance +- Design upstream: if CCC Udt changes are needed, design them generically as a "composite UDT" pattern that benefits other CKB tokens beyond iCKB + +### Subclassing approach +- Leaning toward `IckbUdt extends udt.Udt` — iCKB is fundamentally a UDT, just with extra cell types carrying value +- Two viable override points identified: `getInputsInfo/getOutputsInfo` and `infoFrom` +- `infoFrom` can distinguish between input and output cells by checking outpoint presence (inputs have outpoints, outputs don't) +- Dealbreaker for subclass: if upstream CCC changes needed are too invasive (large, likely-to-be-rejected PRs) +- If subclassing doesn't work, reevaluate WHY it fails and determine what CCC Udt changes would fix it — don't fall back to custom without first trying the upstream path + +### Transaction completion integration +- Standard xUDT token completion must integrate seamlessly (already supported by CCC) +- Accounting for iCKB-specific cells (receipts, deposits) that carry UDT value must also integrate seamlessly into CCC's completion pipeline +- Auto-fetching and auto-adding of receipt/withdrawal-request cells: to be determined — investigate how this fits within PR #328's FeePayer framework (`completeInputs()` with accumulator pattern) + +### Conservation law enforcement +- On-chain iCKB Logic script already enforces `Input UDT + Input Receipts = Output UDT + Input Deposits` at validation time +- Investigation should explore both: (a) IckbUdt subclass enforcing at tx-building time (prevents invalid tx construction), and (b) caller responsibility (IckbUdt only reports accurate balances) +- No risk of funds loss either way — just risk of building invalid transactions that fail on-chain + +### Header access pattern +- Settled: `client.getTransactionWithHeader(outPoint.txHash)` for per-cell header fetching +- CCC is async-native — no concern about async header fetches inside Udt overrides +- Receipt cells store `depositQuantity` and `depositAmount` (not block numbers) — header provides the DAO AR field for exchange rate computation via `ickbValue()` +- Both receipt and deposit cell value calculation need per-cell headers +- Estimate scenarios (SDK.estimate) use pre-computed `ExchangeRatio` from tip header — this is separate from Udt's per-cell balance methods + +### Claude's Discretion +- Technical investigation methodology (which CCC Udt internals to trace first) +- Decision document format and depth of analysis +- Prototype code scope (if any) + + + + +## Specific Ideas + +- `infoFrom` can detect input vs output cells via outpoint presence — investigate this as a cleaner override strategy. Note: STACK.md research incorrectly claimed `CellAnyLike` lacks `outPoint`; it actually has `outPoint?: OutPointLike | null`. `getInputsInfo()` passes `Cell` objects (always have outPoint) to `infoFrom()`, while `getOutputsInfo()` passes `CellAny` from `tx.outputCells` (no outPoint). Both override points are viable. +- PR #328's `completeInputs(tx, filter, accumulator)` pattern (now in `ccc-fork/ccc/packages/core/src/signer/feePayer/feePayer.ts`) could be the hook for auto-fetching iCKB receipt/deposit cells during transaction completion. Note: STACK.md research recommended `client.getHeaderByTxHash()` which does not exist in CCC — the correct API is `client.getTransactionWithHeader()` as used in the current codebase. +- The `ickbValue()` function (core/udt.ts:151) and `convert()` function (core/udt.ts:179) are the core exchange rate calculation — these must work within the Udt override context +- Current `IckbUdtManager.getInputsUdtBalance()` (core/udt.ts:66) is the reference implementation for multi-representation balance calculation — three cell types: xUDT cells, receipt cells (type = logicScript), deposit cells (lock = logicScript + isDeposit) + + + + +## Deferred Ideas + +None — discussion stayed within phase scope + + + +--- + +*Phase: 03-ccc-udt-integration-investigation* +*Context gathered: 2026-02-23* diff --git a/.planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md b/.planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md new file mode 100644 index 0000000..9cc5670 --- /dev/null +++ b/.planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md @@ -0,0 +1,481 @@ +# Phase 3: CCC Udt Integration Decision + +**Date:** 2026-02-24 +**Status:** Final +**Requirement IDs:** UDT-01, UDT-02, UDT-03 +**Based on:** 03-01-INVESTIGATION.md (source code trace), 03-RESEARCH.md (architecture analysis) + +--- + +## Feasibility Assessment (UDT-01) + +**Can `IckbUdt extends udt.Udt` override `infoFrom()` to account for receipt cells and deposit cells alongside xUDT cells?** + +**Answer: YES -- feasible with no upstream CCC changes required.** + +### Override Point Selection + +**Selected: `infoFrom`** (not `getInputsInfo`/`getOutputsInfo`) + +Rationale: +- `infoFrom` operates at the per-cell level (`ccc-fork/ccc/packages/udt/src/udt/index.ts:624-641`), providing fine-grained control over how each cell contributes to balance +- `getInputsInfo`/`getOutputsInfo` contain input resolution logic (`input.getCell(client)`) and output iteration (`tx.outputCells`) that would need to be duplicated if overridden +- `infoFrom` receives a `client: ccc.Client` parameter (unused in base implementation) that the override needs for header fetches +- `infoFrom` is async, allowing network calls within the override +- A single `infoFrom` override handles both inputs and outputs uniformly -- input/output distinction is via `outPoint` presence + +### Three Cell Types in infoFrom + +**1. xUDT cells (standard UDT balance)** + +- **Identification:** `this.isUdt(cell)` -- checks `cell.cellOutput.type?.eq(this.script)` with full `Script.eq()` (codeHash + hashType + args) and `outputData.length >= 16` bytes (`ccc-fork/ccc/packages/udt/src/udt/index.ts:1063-1069`) +- **Balance:** `udt.Udt.balanceFromUnsafe(cell.outputData)` -- reads first 16 bytes as 128-bit LE integer (`index.ts:590-593`). Replaces the manual `ccc.numFromBytes(ccc.bytesFrom(outputData).slice(0, 16))` pattern in current `IckbUdtManager` +- **Applies to:** Both input and output cells +- **Sign:** Positive + +**2. Receipt cells (pending conversion receipts)** + +- **Identification:** `cell.cellOutput.type?.eq(this.logicScript)` -- the iCKB Logic type script identifies receipt cells by type +- **Balance:** `ReceiptData.decode(cell.outputData)` extracts `depositQuantity` and `depositAmount`, then `ickbValue(depositAmount, header) * depositQuantity` computes the iCKB value using the DAO accumulate rate from the block header +- **Applies to:** Input cells only (output receipt cells are newly created receipts from `LogicManager.deposit`, not value carriers) +- **Sign:** Positive -- receipts consumed as inputs contribute iCKB value + +**3. Deposit cells (DAO deposits locked under iCKB Logic)** + +- **Identification:** Two checks: `cell.cellOutput.lock.eq(this.logicScript)` (lock script matches iCKB Logic) AND `this.daoManager.isDeposit(fullCell)` (DAO deposit data pattern). `isDeposit` requires `ccc.Cell` not `CellAny` (see capacityFree resolution below) +- **Balance:** `ickbValue(cell.capacityFree, header)` -- uses unoccupied capacity (not total capacity) with the DAO accumulate rate +- **Applies to:** Input cells only (output deposit cells have DAO type script, not iCKB xUDT type script) +- **Sign:** NEGATIVE -- deposits consumed as inputs subtract from iCKB balance per the conservation law: `Input UDT + Input Receipts = Output UDT + Input Deposits`, rearranged as `Input UDT + Input Receipts - Input Deposits = Output UDT` + +### Input vs Output Distinction + +The `outPoint` property on cells cleanly separates input cells from output cells within `infoFrom`: + +| Source | Type | outPoint | +|--------|------|----------| +| `getInputsInfo` -> `input.getCell(client)` | `Cell` | Always `OutPoint` (from `CellInput.previousOutput`) | +| `getOutputsInfo` -> `tx.outputCells` | `CellAny` | Always `undefined` (no outPoint passed) | +| `completeInputs` accumulator (new cells found) | `Cell` | Always `OutPoint` | + +This is structural, not accidental: `Cell` (which extends `CellAny`) requires `outPoint` in its constructor (`transaction.ts:498`), while `tx.outputCells` yields `CellAny.from({ cellOutput, outputData })` without outPoint (`transaction.ts:1715-1728`). + +The override gates receipt/deposit logic behind `if (!cell.outPoint) continue` -- output cells skip straight to the `isUdt` check for standard xUDT balance. + +### capacityFree Resolution + +`CellAny` has a `capacityFree` getter (`transaction.ts:404-405`): + +```typescript +get capacityFree() { + return this.cellOutput.capacity - fixedPointFrom(this.occupiedSize); +} +``` + +No `Cell` construction is needed for the `ickbValue(cell.capacityFree, header)` computation on deposit cells. + +However, `DaoManager.isDeposit()` (`packages/dao/src/dao.ts:30`) requires `ccc.Cell` (not `CellAny`). Since deposit cells are only relevant as inputs (where `outPoint` is always present), constructing `Cell.from({ outPoint: cell.outPoint!, cellOutput: cell.cellOutput, outputData: cell.outputData })` is safe. The non-null assertion on `outPoint` is valid because deposit detection is gated behind the `if (!cell.outPoint) continue` check. + +### Completion Pipeline Compatibility + +`completeInputsByBalance` chains through `infoFrom` correctly: + +1. `completeInputsByBalance(tx, signer)` calls `getInputsInfo` and `getOutputsInfo` to compute balance deficit +2. Both delegate to `infoFrom` -- the override automatically participates +3. The accumulator in `completeInputs` calls `infoFrom` per new cell found during completion +4. New cells found during completion are `Cell` objects (with `outPoint`), so receipt/deposit logic applies + +**Filter limitation:** `Udt.filter` only matches xUDT cells (by type script + data length >= 16). `completeInputsByBalance` will only find xUDT cells on-chain, not receipt or deposit cells. This is correct by design: + +- Receipt/deposit cell discovery is a separate concern handled by `LogicManager.completeDeposit()` and `OwnedOwnerManager.requestWithdrawal()` +- Callers pre-add receipt/deposit cells to the transaction +- `infoFrom` then accurately values all cells present in the transaction +- `completeInputsByBalance` accounts for the value of pre-added receipt/deposit inputs when calculating how many additional xUDT inputs are needed + +### Blockers + +**No blockers identified.** All override points are public methods with appropriate signatures. No upstream CCC changes are required for core feasibility. + +--- + +## Header Access Pattern (UDT-02) + +### API + +`client.getTransactionWithHeader(outPoint.txHash)` -- confirmed from investigation (`client.ts:631-661`). + +Returns `{ transaction: ClientTransactionResponse, header?: ClientBlockHeader } | undefined` where `header` contains the `dao` field with `ar` (accumulate rate) needed for `ickbValue()`. + +`getCellWithHeader()` is a convenience wrapper that calls `getTransactionWithHeader()` internally -- either works, but `getTransactionWithHeader` is more direct when the txHash is already available from `cell.outPoint.txHash`. + +### When + +Only for input cells with `outPoint` -- specifically receipt cells and deposit cells. Standard xUDT cells do not need header access. + +### What + +The header provides the DAO accumulate rate (`header.dao.ar`) used by `ickbValue()` to compute the exchange rate between CKB capacity and iCKB UDT value: +- Receipt cells: `ickbValue(depositAmount, header) * depositQuantity` +- Deposit cells: `ickbValue(cell.capacityFree, header)` + +### Caching + +CCC's `Client.cache` handles repeated calls transparently: +- First call for a txHash: network fetch + `cache.recordTransactionResponses()` stores result +- Subsequent calls: `cache.getTransactionResponse(txHash)` returns cached response + `cache.hasHeaderConfirmed(header)` confirms header validity +- Multiple cells from the same transaction share one cached header fetch + +No application-level caching is needed. + +### Async Flow + +`infoFrom` is declared `async` in the base class (`Promise` return type). The override uses `await client.getTransactionWithHeader(...)` for receipt and deposit cells. This integrates naturally -- no async wrapper or callback indirection needed. + +### Performance + +- Receipt and deposit cells are typically few per transaction (1-5 in practice) +- Header fetches are small payloads +- CCC caching ensures each unique txHash is fetched at most once per session +- The completion loop in `completeInputsByBalance` re-calls `infoFrom` for each iteration, but the cache prevents redundant network requests + +### Code Sketch + +```typescript +import * as ccc from "@ckb-ccc/core"; +import * as udt from "@ckb-ccc/udt"; +import type { DaoManager } from "@ickb/dao"; +import { ReceiptData, ickbValue } from "./existing-imports.js"; + +class IckbUdt extends udt.Udt { + constructor( + code: ccc.OutPointLike, + script: ccc.ScriptLike, + public readonly logicScript: ccc.Script, + public readonly daoManager: DaoManager, + config?: udt.UdtConfigLike | null, + ) { + super(code, script, config); + } + + 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); + const { type, lock } = cell.cellOutput; + + // Standard xUDT cell -- applies to both inputs and outputs + if (this.isUdt(cell)) { + info.addAssign({ + balance: udt.Udt.balanceFromUnsafe(cell.outputData), + capacity: cell.cellOutput.capacity, + count: 1, + }); + continue; + } + + // Receipt and deposit cells are only relevant as inputs (with outPoint) + if (!cell.outPoint) { + continue; + } + + // Receipt cell: type === logicScript + if (type?.eq(this.logicScript)) { + const txWithHeader = await client.getTransactionWithHeader( + cell.outPoint.txHash, + ); + if (!txWithHeader?.header) { + throw new Error("Header not found for receipt cell"); + } + 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 && isDeposit + if (lock.eq(this.logicScript)) { + // Construct Cell for isDeposit() which requires ccc.Cell, not CellAny + const fullCell = ccc.Cell.from({ + outPoint: cell.outPoint, + cellOutput: cell.cellOutput, + outputData: cell.outputData, + }); + if (!this.daoManager.isDeposit(fullCell)) { + continue; + } + const txWithHeader = await client.getTransactionWithHeader( + cell.outPoint.txHash, + ); + if (!txWithHeader?.header) { + throw new Error("Header not found for deposit cell"); + } + // Deposits SUBTRACT from iCKB balance (conservation law) + info.addAssign({ + balance: -ickbValue(cell.capacityFree, txWithHeader.header), + capacity: cell.cellOutput.capacity, + count: 1, + }); + continue; + } + } + + return info; + } +} +``` + +This code sketch is derived from: +- The base `infoFrom` implementation (`ccc-fork/ccc/packages/udt/src/udt/index.ts:624-641`) +- The current `IckbUdtManager.getInputsUdtBalance()` logic (`packages/core/src/udt.ts:66-141`) +- The line-by-line migration mapping from `03-01-INVESTIGATION.md` + +--- + +## Decision (UDT-03) + +### Chosen: (a) Subclass CCC Udt + +**`IckbUdt extends udt.Udt`** with `infoFrom` override is the chosen approach. + +### Rationale + +1. **Feasibility confirmed:** The investigation (03-01-INVESTIGATION.md) traced every CCC Udt method chain end-to-end and confirmed that `infoFrom` is a clean, sufficient override point. No upstream CCC changes are required. + +2. **CCC alignment is the primary driver:** Per CONTEXT.md, iCKB should feel native to CCC users and benefit from upstream improvements. Subclassing `udt.Udt` achieves this -- `IckbUdt` is a first-class CCC UDT that participates in all CCC completion and querying pipelines. + +3. **The override is minimal and clean:** A single method override (`infoFrom`) teaches CCC how to value iCKB's three cell types. No other overrides are needed -- `getInputsInfo`, `getOutputsInfo`, `completeInputsByBalance`, `completeInputs`, `filter` all work as inherited. + +4. **No dealbreakers:** The identified risks (filter mismatch, DaoManager.isDeposit type requirement, async performance) all have clean mitigations (see Risks section). No invasive upstream CCC changes are needed. + +### What It Replaces + +| 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 | + +### What CCC Features It Gains + +- **`completeInputsByBalance`:** Dual-constraint (balance + capacity) completion with automatic error reporting via `ErrorUdtInsufficientCoin` +- **`UdtInfo` accumulator:** Structured balance + capacity + count tracking via `addAssign`, replacing ad-hoc `[bigint, bigint]` tuples +- **CCC completion pipeline integration:** `IckbUdt` participates in `Transaction.completeBy()` chains alongside other CCC completers +- **Future upstream improvements:** Any improvements to CCC's Udt class (better completion algorithms, new query methods, SSRI integration) automatically apply to `IckbUdt` +- **`getBalanceBurned`:** Inherited method for conservation law verification (inputs - outputs) + +### What It Loses or Changes + +- **`name`, `symbol`, `decimals` as constructor args:** Current `UdtManager` stores these as direct properties. CCC's `Udt` accesses them via SSRI protocol methods (`udt.name()`, `udt.symbol()`, `udt.decimals()`). If direct properties are needed, they can be added as custom fields on `IckbUdt` +- **`cellDeps` array:** Current `UdtHandler.cellDeps` is an explicit `CellDep[]` array. CCC's `Udt` uses `code: OutPoint` which is resolved to cell deps differently. The `Trait.addCellDeps(tx)` method handles this +- **`getInputsUdtBalance` return type:** Changes from `[FixedPoint, FixedPoint]` to `UdtInfo` (which contains `balance`, `capacity`, `count`). Callers must destructure differently +- **Output balance method:** `getOutputsUdtBalance` is replaced by `getOutputsInfo`. Same semantics but returns `UdtInfo` instead of `[FixedPoint, FixedPoint]` + +--- + +## Conservation Law Strategy + +### The Conservation Law + +`Input UDT + Input Receipts = Output UDT + Input Deposits` + +This is enforced on-chain by the iCKB Logic type script. If a transaction violates it, on-chain validation rejects the transaction. There is no risk of funds loss -- only risk of building invalid transactions that fail at submission. + +### How infoFrom Preserves It + +`infoFrom` reports accurate balances with correct sign conventions: + +- **xUDT cells:** Positive balance (both input and output) +- **Receipt cells (input only):** Positive balance -- represents iCKB value the user is redeeming +- **Deposit cells (input only):** NEGATIVE balance -- represents iCKB value the user is providing as CKB deposits + +This means: +- `getInputsInfo` returns: `xUDT balance + receipt value - deposit value` +- `getOutputsInfo` returns: `xUDT balance` (only standard xUDT outputs carry iCKB value) +- `getBalanceBurned` (inherited) = `getInputsInfo.balance - getOutputsInfo.balance` + +When the conservation law holds, `getBalanceBurned` returns zero (or the intended burn amount). + +### Enforcement Location + +- **On-chain (existing):** iCKB Logic script validates the conservation law during CKB transaction verification. This is the authoritative enforcement point and cannot be circumvented. +- **Build-time (future, optional):** A validation method (e.g., `assertConservationLaw(client, tx)`) can be added to `IckbUdt` as a convenience for callers who want early rejection of invalid transactions. This is separate from `infoFrom`. + +### Recommendation + +Per CONTEXT.md: start with accurate balance reporting (caller responsibility). The `infoFrom` override performs balance calculation, not validation. Conservation law enforcement is NOT embedded in `infoFrom` because: + +1. It would conflate calculation with validation +2. It would break the `completeInputsByBalance` loop, which processes partially-constructed transactions that may not yet satisfy the conservation law +3. The on-chain script is the authoritative enforcer -- build-time validation is a convenience, not a requirement + +Build-time validation can be added later as a separate method if needed. The accurate balance reporting from `infoFrom` provides all the data callers need to verify the conservation law themselves. + +--- + +## Cell Discovery vs Balance Calculation Boundary + +### infoFrom Responsibility: VALUE Cells + +`infoFrom` receives cells that are already in the transaction and computes their iCKB value. It does not search for cells on-chain. It does not add cells to the transaction. + +Specifically: +- Cells arrive via `getInputsInfo` (resolved from `tx.inputs`) or `getOutputsInfo` (from `tx.outputCells`) +- `infoFrom` computes `UdtInfo` (balance + capacity + count) for these cells +- The accumulator in `completeInputsByBalance` also feeds cells through `infoFrom` as they are found + +### Caller Responsibility: FIND and ADD Cells + +Receipt and deposit cell discovery is handled by existing manager classes: + +| Cell Type | Discovery Mechanism | Who Calls It | +|-----------|---------------------|--------------| +| xUDT cells | `completeInputsByBalance` via `Udt.filter` | Automatic (CCC completion pipeline) | +| Receipt cells | `LogicManager.completeDeposit()` | Caller (SDK, application code) | +| Deposit cells | `OwnedOwnerManager.requestWithdrawal()`, `LogicManager` | Caller (SDK, application code) | + +### Why No filter Override + +`Udt.filter` is a `ClientIndexerSearchKeyFilter` that only matches xUDT cells (by type script + minimum data length). Receipt and deposit cells have different type scripts and lock scripts -- they cannot be matched by a single filter. + +This is correct by design: +- CCC's `completeInputs` uses a single filter for cell search +- Multi-filter search would require overriding `completeInputsByBalance` and reimplementing dual-constraint optimization +- Receipt/deposit cell discovery involves domain-specific logic (deposit maturity, receipt validity) that belongs in `LogicManager`/`OwnedOwnerManager`, not in `IckbUdt` + +The correct pattern is: +1. Caller adds receipt/deposit cells to the transaction (via LogicManager/OwnedOwnerManager) +2. `infoFrom` accurately values all cells in the transaction (including pre-added receipt/deposit cells) +3. `completeInputsByBalance` determines remaining xUDT deficit and finds additional xUDT cells if needed + +--- + +## Implementation Guidance for Phases 4-5 + +### Phase 4: dao and order Packages (Deprecated API Replacement) + +Replace deprecated CCC API calls with `udt.Udt` instance methods: + +| 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 | + +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)` + +### Phase 5: core Package (IckbUdt Implementation) + +**Create `IckbUdt extends udt.Udt`** in `packages/core/src/udt.ts`: + +``` +Constructor parameters: + - code: ccc.OutPointLike -- OutPoint for xUDT cell dep + - script: ccc.ScriptLike -- xUDT type script + - logicScript: ccc.Script -- iCKB Logic type script + - daoManager: DaoManager -- for isDeposit() checks + - config?: udt.UdtConfigLike | null -- optional UDT config + +Override: + - infoFrom(client, cells, acc?) -- three-cell-type valuation logic + (see Code Sketch in Header Access Pattern section above) +``` + +**Delete from `packages/core/src/udt.ts`:** +- `IckbUdtManager` class (replaced by `IckbUdt`) +- `getInputsUdtBalance()` override (logic moves to `infoFrom`) +- `getOutputsUdtBalance()` override (base `getOutputsInfo` via `infoFrom` handles this) + +**Delete from `packages/utils/src/udt.ts`:** +- `UdtHandler` interface (replaced by `udt.Udt` type) +- `UdtManager` class (replaced by `udt.Udt` base class) + +**Preserve (do not delete):** +- `ickbValue()` function (`packages/core/src/udt.ts`) -- used within `infoFrom` override and by SDK estimate scenarios +- `convert()` function (`packages/core/src/udt.ts`) -- used by SDK for CKB-to-iCKB conversion +- `ickbExchangeRatio()` function (`packages/core/src/udt.ts`) -- used by SDK estimate scenarios with pre-computed ExchangeRatio from tip header +- `ExchangeRatio` type, `ValueComponents` type -- shared types used across packages +- `ReceiptData` codec -- used within `infoFrom` override for receipt cell decoding + +**Update SDK (`packages/sdk/src/sdk.ts`):** +- Construct `IckbUdt` instance instead of `IckbUdtManager` +- Pass `IckbUdt` instance to managers that need UDT operations +- Update balance queries from `[FixedPoint, FixedPoint]` tuple to `UdtInfo` destructuring + +--- + +## Upstream CCC Changes + +**No upstream changes required for core feasibility.** + +The CCC `udt.Udt` class API surface is sufficient for `IckbUdt` subclassing without modification: +- `infoFrom` is a public async method with the right signature +- `isUdt` is a public method for cell type checking +- `balanceFromUnsafe` is a public static method for balance extraction +- `UdtInfo` supports negative balance values via bigint addition +- `Client` provides `getTransactionWithHeader` for header access +- `CellAny` provides `capacityFree` for unoccupied capacity + +**Potential future upstream contributions (non-blocking):** + +If the "composite UDT" pattern (UDT value distributed across multiple cell types) proves useful to other CKB tokens, a generic version could be proposed upstream. This would be a new base class or mixin, not a modification to existing `Udt`. This is not required for iCKB and should only be pursued if clear demand exists from other projects. + +**PR #328 (FeePayer) compatibility:** The `infoFrom` override is compatible with both current and FeePayer architectures. The override operates below the completion routing layer -- whether cells arrive via `Signer.completeInputs` or `FeePayer.completeInputs`, they flow through `getInputsInfo` -> `infoFrom`. No special handling needed for the FeePayer transition. + +--- + +## Risks and Mitigations + +### 1. Filter Mismatch for Receipt/Deposit Cell Discovery + +**Risk:** `completeInputsByBalance` only finds xUDT cells via `Udt.filter`. If callers forget to pre-add receipt/deposit cells, balance calculation will be incomplete. + +**Mitigation:** Caller-responsibility pattern. Document clearly that receipt/deposit cells must be added by `LogicManager`/`OwnedOwnerManager` before calling `completeInputsByBalance`. The SDK already follows this pattern -- `LogicManager.completeDeposit()` and `OwnedOwnerManager.requestWithdrawal()` add these cells. No behavioral change from current implementation. + +### 2. DaoManager.isDeposit() Requires Cell, Not CellAny + +**Risk:** Type mismatch when calling `isDeposit` from within `infoFrom`, which receives `CellAnyLike`. + +**Mitigation:** Construct `Cell.from({ outPoint: cell.outPoint!, cellOutput: cell.cellOutput, outputData: cell.outputData })` when `outPoint` is present. This is safe because deposit cells are only relevant as inputs (which always have `outPoint`). The `capacityFree` computation itself uses `CellAny.capacityFree` directly -- only `isDeposit` needs the `Cell` construction. + +### 3. Completion Loop Performance with Header Fetches + +**Risk:** `infoFrom` makes network calls (header fetches) inside the `completeInputsByBalance` loop. If re-called repeatedly, this could be slow. + +**Mitigation:** CCC `Client.cache` ensures each txHash is fetched at most once per session. Receipt/deposit cells are typically few per transaction (1-5). Multiple cells from the same transaction share one cached header fetch. The base `infoFrom` was already async -- the override does not change the method's contractual behavior, only adds actual async work. + +### 4. UdtInfo.balance Negative Values + +**Risk:** Deposit cells contribute negative balance. If code assumes `UdtInfo.balance >= 0`, it could produce incorrect results. + +**Mitigation:** `UdtInfo.balance` is `ccc.Num` (bigint), which supports negative values. The `completeInputsByBalance` method already checks `info.balance >= ccc.Zero` as its termination condition, correctly handling negative balance contributions. The `addAssign` method does `this.balance += info.balance` which works with negative values. No special handling needed. + +### 5. Output Receipt/Deposit Cell Misidentification + +**Risk:** Output cells that happen to have logic script as type or lock could be misidentified as iCKB value carriers. + +**Mitigation:** The `if (!cell.outPoint) continue` gate prevents any receipt/deposit logic from applying to output cells. Only input cells (with `outPoint`) are evaluated for receipt/deposit value. This is both correct and defensive. + +### 6. Breaking Change in Return Types + +**Risk:** Callers currently expect `[FixedPoint, FixedPoint]` from `getInputsUdtBalance`/`getOutputsUdtBalance`. The new `getInputsInfo`/`getOutputsInfo` return `UdtInfo`. + +**Mitigation:** This is an intentional API change that occurs during Phases 4-5. The migration is straightforward: `const [balance, capacity] = await udtHandler.getInputsUdtBalance(...)` becomes `const { balance, capacity } = await ickbUdt.getInputsInfo(...)`. The `count` field in `UdtInfo` is new and additive. diff --git a/.planning/phases/03-ccc-udt-integration-investigation/03-RESEARCH.md b/.planning/phases/03-ccc-udt-integration-investigation/03-RESEARCH.md new file mode 100644 index 0000000..55359f8 --- /dev/null +++ b/.planning/phases/03-ccc-udt-integration-investigation/03-RESEARCH.md @@ -0,0 +1,440 @@ +# Phase 3: CCC Udt Integration Investigation - Research + +**Researched:** 2026-02-23 +**Domain:** CCC UDT subclassing, composite UDT patterns, async header access +**Confidence:** HIGH + + +## User Constraints (from CONTEXT.md) + +### Locked Decisions +- CCC alignment is the primary driver -- iCKB should feel native to CCC users and benefit from upstream improvements +- Upstream CCC PRs are explicitly on the table if CCC's Udt class needs small, targeted changes to accommodate iCKB's multi-representation value +- No concern about CCC upgrade risk -- if we contribute to CCC's Udt, we co-own the design +- PR #328 (FeePayer abstraction by ashuralyk) is the target architecture -- investigation should design around it and identify improvements that would better fit iCKB's needs. Now integrated into `ccc-fork/ccc` (available at `ccc-fork/ccc/packages/core/src/signer/feePayer/`) +- Investigation should cover both cell discovery and balance calculation, not just balance +- Design upstream: if CCC Udt changes are needed, design them generically as a "composite UDT" pattern that benefits other CKB tokens beyond iCKB +- Leaning toward `IckbUdt extends udt.Udt` -- iCKB is fundamentally a UDT, just with extra cell types carrying value +- Two viable override points identified: `getInputsInfo/getOutputsInfo` and `infoFrom` +- `infoFrom` can distinguish between input and output cells by checking outpoint presence (inputs have outpoints, outputs don't) +- Dealbreaker for subclass: if upstream CCC changes needed are too invasive (large, likely-to-be-rejected PRs) +- If subclassing doesn't work, reevaluate WHY it fails and determine what CCC Udt changes would fix it -- don't fall back to custom without first trying the upstream path +- Standard xUDT token completion must integrate seamlessly (already supported by CCC) +- Accounting for iCKB-specific cells (receipts, deposits) that carry UDT value must also integrate seamlessly into CCC's completion pipeline +- Auto-fetching and auto-adding of receipt/withdrawal-request cells: to be determined -- investigate how this fits within PR #328's FeePayer framework (`completeInputs()` with accumulator pattern) +- On-chain iCKB Logic script already enforces `Input UDT + Input Receipts = Output UDT + Input Deposits` at validation time +- Investigation should explore both: (a) IckbUdt subclass enforcing at tx-building time (prevents invalid tx construction), and (b) caller responsibility (IckbUdt only reports accurate balances) +- No risk of funds loss either way -- just risk of building invalid transactions that fail on-chain +- Settled: `client.getTransactionWithHeader(outPoint.txHash)` for per-cell header fetching +- CCC is async-native -- no concern about async header fetches inside Udt overrides +- Receipt cells store `depositQuantity` and `depositAmount` (not block numbers) -- header provides the DAO AR field for exchange rate computation via `ickbValue()` +- Both receipt and deposit cell value calculation need per-cell headers +- Estimate scenarios (SDK.estimate) use pre-computed `ExchangeRatio` from tip header -- this is separate from Udt's per-cell balance methods + +### Claude's Discretion +- Technical investigation methodology (which CCC Udt internals to trace first) +- Decision document format and depth of analysis +- Prototype code scope (if any) + +### Deferred Ideas (OUT OF SCOPE) +None -- discussion stayed within phase scope + + + +## Phase Requirements + +| ID | Description | Research Support | +|----|-------------|-----------------| +| UDT-01 | Feasibility assessment: can `IckbUdt extends udt.Udt` override `infoFrom()` or `getInputsInfo()`/`getOutputsInfo()` to account for receipt cells and deposit cells alongside xUDT cells | CCC Udt class traced end-to-end; override points mapped; `infoFrom` identified as optimal override; async compatibility confirmed | +| UDT-02 | Header access pattern for receipt value calculation designed -- determine whether `client.getCellWithHeader()`, `client.getTransactionWithHeader()`, or direct CCC client calls are used within the Udt override | `client.getTransactionWithHeader(outPoint.txHash)` confirmed as correct API; `getCellWithHeader` wraps same; header access within async `infoFrom` confirmed viable | +| UDT-03 | Decision documented: subclass CCC `Udt` vs. keep custom `UdtHandler` interface vs. hybrid approach | Full analysis of all three approaches with clear recommendation for subclass approach | + + +## Summary + +This research investigates whether iCKB's multi-representation UDT value (xUDT cells + receipt cells + deposit cells) can be modeled as a subclass of CCC's `udt.Udt` class. The investigation traces CCC's Udt class internals, the current iCKB `IckbUdtManager` implementation, and PR #328's FeePayer architecture. + +**The subclass approach is feasible.** CCC's `udt.Udt` class provides a clean override point via `infoFrom()` that accepts `CellAnyLike | CellAnyLike[]` and a `ccc.Client` parameter. The method is async, allowing header fetches within the override. Input cells passed through `getInputsInfo()` carry `outPoint` (resolved via `CellInput.getCell(client)`), while output cells from `tx.outputCells` do not -- this outPoint presence/absence is the reliable mechanism for distinguishing input vs output cells within `infoFrom()`, which is necessary because receipt and deposit cells only carry iCKB value as inputs (not outputs). The `completeInputsByBalance` method chains through `infoFrom` cleanly, so the override will automatically participate in CCC's completion pipeline. + +**The header access pattern is straightforward.** Receipt and deposit cell value calculation requires the block header of the transaction that created the cell. This is obtained via `client.getTransactionWithHeader(outPoint.txHash)`, which is already used in the current `IckbUdtManager.getInputsUdtBalance()`. Since `infoFrom()` only needs headers for input cells (which have outPoints), and since the method receives a `client` parameter and is async, the pattern integrates naturally. + +**Primary recommendation:** Subclass CCC's `udt.Udt` with `IckbUdt extends udt.Udt`, overriding `infoFrom()` to recognize receipt cells and deposit cells alongside xUDT cells. No upstream CCC changes are required for the core feasibility. PR #328's FeePayer pattern is compatible but orthogonal -- iCKB's `completeInputs` can work with either the current Signer-based approach or future FeePayer-based approach. + +## 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` override point | +| `@ckb-ccc/core` | workspace (local CCC fork) | `Transaction`, `Client`, `CellAny`, `CellInput`, `Cell` types | CCC core primitives | +| `@ckb-ccc/ssri` | workspace (local CCC fork) | `ssri.Trait` base class (parent of `Udt`) | SSRI protocol support | + +### Supporting +| Library | Version | Purpose | When to Use | +|---------|---------|---------|-------------| +| `@ickb/dao` | workspace | `DaoManager.isDeposit()` for deposit cell identification | Within `infoFrom` override for deposit cells | +| `@ickb/utils` | workspace | `ExchangeRatio`, `ValueComponents`, `ScriptDeps` types | Type definitions shared across packages | + +### Alternatives Considered +| Instead of | Could Use | Tradeoff | +|------------|-----------|----------| +| `infoFrom` override | `getInputsInfo`/`getOutputsInfo` override | `infoFrom` is more granular (per-cell); `getInputsInfo`/`getOutputsInfo` override would duplicate iteration logic already in base class | +| Subclass `udt.Udt` | Keep `UdtHandler` interface | Custom interface doesn't participate in CCC completion pipeline, loses `completeInputsByBalance`/`completeBy`/etc | + +## Architecture Patterns + +### Pattern 1: `infoFrom` Override for Multi-Representation UDT + +**What:** Override `infoFrom()` in `IckbUdt extends udt.Udt` to recognize three cell types (xUDT, receipt, deposit) and accumulate their iCKB value. + +**When to use:** When a UDT's value is distributed across multiple cell types beyond standard xUDT cells. + +**Key insight:** `infoFrom()` receives `CellAnyLike` objects. Input cells (from `getInputsInfo -> CellInput.getCell()`) have `outPoint` set; output cells (from `getOutputsInfo -> tx.outputCells`) do not. This allows `infoFrom` to: +- For cells with outPoint (inputs): check receipt cells, deposit cells, AND xUDT cells +- For cells without outPoint (outputs): only count standard xUDT cells (receipt/deposit outputs don't carry iCKB value in the same way) + +**Example (conceptual):** +```typescript +// Source: Synthesized from CCC Udt source + iCKB IckbUdtManager source +class IckbUdt extends udt.Udt { + constructor( + code: ccc.OutPointLike, + script: ccc.ScriptLike, + public readonly logicScript: ccc.Script, + public readonly daoManager: DaoManager, + config?: udt.UdtConfigLike | null, + ) { + super(code, script, config); + } + + 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); + const { type, lock } = cell.cellOutput; + + // Standard xUDT cell -- delegate to base class logic + if (this.isUdt(cell)) { + info.addAssign({ + balance: udt.Udt.balanceFromUnsafe(cell.outputData), + capacity: cell.cellOutput.capacity, + count: 1, + }); + continue; + } + + // Only input cells (with outPoint) can be receipt/deposit cells + if (!cell.outPoint) { + continue; + } + + // Receipt cell: type === logicScript + if (type?.eq(this.logicScript)) { + const txWithHeader = await client.getTransactionWithHeader( + cell.outPoint.txHash, + ); + if (!txWithHeader?.header) { + throw new Error("Header not found for receipt cell"); + } + 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 && isDeposit + if (lock.eq(this.logicScript)) { + // Construct Cell for isDeposit() which requires ccc.Cell, not CellAny + const fullCell = ccc.Cell.from({ outPoint: cell.outPoint, cellOutput: cell.cellOutput, outputData: cell.outputData }); + if (!this.daoManager.isDeposit(fullCell)) { + continue; + } + const txWithHeader = await client.getTransactionWithHeader( + cell.outPoint.txHash, + ); + if (!txWithHeader?.header) { + throw new Error("Header not found for deposit cell"); + } + // Deposits SUBTRACT from iCKB balance (conservation law) + info.addAssign({ + balance: -ickbValue(cell.capacityFree, txWithHeader.header), + capacity: cell.cellOutput.capacity, + count: 1, + }); + continue; + } + } + + return info; + } +} +``` + +### Pattern 2: Header Access via outPoint.txHash + +**What:** For each receipt or deposit input cell, fetch the block header via `client.getTransactionWithHeader(outPoint.txHash)`, then extract the DAO AR field for exchange rate computation. + +**When to use:** Whenever per-cell iCKB value calculation is needed (not estimate scenarios that use pre-computed ExchangeRatio from tip header). + +**Key details:** +- `client.getTransactionWithHeader()` returns `{ transaction, header? }` where `header` is a `ClientBlockHeader` containing the `dao` field with `ar` (accumulate rate) +- CCC's `Client.cache` transparently caches transaction responses and headers, so repeated calls for the same txHash are cheap +- The method is async, which is compatible with `infoFrom`'s async signature +- `getCellWithHeader()` is a convenience wrapper that calls `getTransactionWithHeader()` internally -- either can be used, but `getTransactionWithHeader` is more direct when you already have the txHash + +### Pattern 3: CCC Completion Pipeline Integration + +**What:** The `IckbUdt` subclass automatically participates in CCC's completion pipeline through inherited methods. + +**How the chain works:** +1. `completeInputsByBalance(tx, signer)` calls `getInputsInfo` and `getOutputsInfo` to compute balance deficit +2. `getInputsInfo` resolves inputs via `CellInput.getCell(client)` then calls `infoFrom(client, inputCells)` +3. `getOutputsInfo` iterates `tx.outputCells` then calls `infoFrom(client, outputCells)` +4. `completeInputs(tx, signer, accumulator, init)` delegates to `tx.completeInputs(signer, this.filter, ...)` +5. The `filter` property on `Udt` controls which cells the signer's `findCellsOnChain` returns + +**Critical detail about `filter`:** The base `Udt` constructor sets `filter` to `{ script: this.script, outputDataLenRange: [16, "0xffffffff"] }` which only finds xUDT cells. The `IckbUdt` subclass should NOT rely on this filter for receipt/deposit cell discovery -- those cells have different type/lock scripts and would not match the xUDT filter. Receipt and deposit cell completion should be handled separately (e.g., by the caller or a dedicated completion method on `IckbUdt`). + +### Anti-Patterns to Avoid +- **Overriding `getInputsInfo`/`getOutputsInfo` directly:** These methods contain resolution logic (resolving `CellInput` to `Cell`, iterating `tx.outputCells`) that would need to be duplicated. Override `infoFrom` instead for cleaner code. +- **Using `infoFrom` for cell discovery:** `infoFrom` is for balance calculation from already-known cells, not for finding cells on-chain. Cell discovery uses `filter` + `completeInputs`. +- **Passing `CellAny` to `DaoManager.isDeposit()`:** `DaoManager.isDeposit()` expects `ccc.Cell`, not `CellAny`. When calling within `infoFrom`, construct `Cell.from({ outPoint, cellOutput, outputData })` from the `CellAny` when `outPoint` is present. This is safe because deposit cells are only relevant as inputs. Note: `capacityFree` is available on `CellAny` directly -- only `isDeposit` requires the `Cell` construction. +- **Ignoring the sign convention for deposits:** In the iCKB conservation law, deposit cells consumed as inputs SUBTRACT from iCKB balance. The current `IckbUdtManager.getInputsUdtBalance()` uses `udtValue - ickbValue(...)` for deposits. The `infoFrom` override must preserve this negative balance contribution. + +## Don't Hand-Roll + +| Problem | Don't Build | Use Instead | Why | +|---------|-------------|-------------|-----| +| UDT balance tracking | Custom balance tracking interface | `udt.UdtInfo` accumulator with `addAssign()` | UdtInfo already handles balance + capacity + count aggregation, tested in CCC | +| Transaction completion | Custom input search + accumulator | `udt.Udt.completeInputsByBalance()` inherited | Handles dual-constraint (balance + capacity) optimization, error reporting | +| Header fetching + caching | Custom header cache | `client.getTransactionWithHeader()` + CCC's `Client.cache` | CCC caches transparently; no need for application-level header caching | +| Cell type checking | Manual script comparison | `this.isUdt(cell)` + `Script.eq()` for logic/deposit checks | Always use full `Script.eq()` (codeHash + hashType + args) per CLAUDE.md | + +**Key insight:** The CCC Udt class already solves the hard problems (completion pipeline, balance/capacity optimization, input deduplication). The iCKB subclass only needs to teach `infoFrom` how to value three cell types instead of one. + +## Common Pitfalls + +### Pitfall 1: Filter Mismatch for Multi-Cell-Type UDTs +**What goes wrong:** The `Udt.filter` only matches xUDT cells (by type script + data length). Calling `completeInputsByBalance` will only find xUDT cells, not receipt or deposit cells. +**Why it happens:** CCC's `completeInputs` uses `Udt.filter` to find cells from the signer. Receipt/deposit cells have different type scripts and lock scripts. +**How to avoid:** Do NOT expect `completeInputsByBalance` to automatically fetch receipt/deposit cells. Either: (a) override `filter` to also include receipt/deposit cells (complex, may not be feasible with single filter), or (b) have the caller pre-add receipt/deposit cells to the transaction before calling completion, or (c) add a custom `completeInputsByBalance` override that performs multiple cell searches. +**Warning signs:** `completeInputsByBalance` reports insufficient balance when receipt/deposit cells exist but are not in the transaction. + +### Pitfall 2: Output Cells vs Input Cells in infoFrom +**What goes wrong:** Applying receipt/deposit valuation logic to output cells, which do not have outPoints and do not carry iCKB value in the same way. +**Why it happens:** `infoFrom` is called for both inputs and outputs. Receipt output cells and deposit output cells exist in transactions but their iCKB value semantics differ from inputs. +**How to avoid:** Check `cell.outPoint` presence before applying receipt/deposit logic. Only input cells (with outPoint) contribute receipt/deposit value. Output receipt cells are new receipts being created (handled by LogicManager.deposit), not value carriers for balance purposes. +**Warning signs:** Double-counting or sign errors in balance calculation. + +### Pitfall 3: Deposit Cell Balance Sign Convention +**What goes wrong:** Treating deposit cell iCKB value as positive when it should be negative (conservation law: consuming a deposit reduces the iCKB balance the user must provide). +**Why it happens:** The conservation law is `Input UDT + Input Receipts = Output UDT + Input Deposits`. Rearranging for net balance: `Input UDT + Input Receipts - Input Deposits = Output UDT`. So deposits consumed as inputs have negative iCKB balance contribution. +**How to avoid:** In `infoFrom`, deposit cells use `balance: -ickbValue(...)` (negative). The current `IckbUdtManager.getInputsUdtBalance()` already uses this convention. +**Warning signs:** Transactions fail on-chain with conservation law violation. + +### Pitfall 4: Capacity Calculation for Deposit Cells +**What goes wrong:** Using `cell.cellOutput.capacity` directly instead of `cell.capacityFree` (unoccupied capacity) for deposit cell iCKB value. +**Why it happens:** The iCKB exchange rate applies to unoccupied capacity, not total capacity. Deposit cells have 82 CKB occupied capacity. +**How to avoid:** Use `cell.capacityFree` (which is `capacity - fixedPointFrom(occupiedSize)`) when computing `ickbValue()` for deposit cells. `CellAny` has `capacityFree` (transaction.ts:404-405), so this works directly within `infoFrom`. +**Warning signs:** Slight over-valuation of deposit cells leading to invalid transactions. + +### Pitfall 5: Async infoFrom and CCC's Completion Loop +**What goes wrong:** The `infoFrom` override makes network calls (header fetches) inside the completion loop, potentially causing performance issues. +**Why it happens:** `completeInputsByBalance` calls `getInputsInfo` which calls `infoFrom` for each input. If there are many receipt/deposit cells, each triggers a header fetch. +**How to avoid:** CCC's `Client.cache` mitigates this -- repeated calls for the same txHash are served from cache. For the initial fetch, the overhead is inherent and acceptable because: (a) receipt/deposit cells are typically few per transaction, and (b) headers are small payloads. No custom optimization needed. +**Warning signs:** Slow transaction building with many receipt/deposit inputs (unlikely in practice). + +## Code Examples + +### Current IckbUdtManager Balance Calculation (Reference) +```typescript +// Source: packages/core/src/udt.ts lines 66-141 +// This is the existing implementation that the IckbUdt subclass must replicate +override async 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) => { + await input.completeExtraInfos(client); + const { previousOutput: outPoint, cellOutput, outputData } = input; + if (!cellOutput || !outputData) throw new Error("Unable to complete input"); + const { type, lock } = cellOutput; + if (!type) return acc; + const cell = new ccc.Cell(outPoint, cellOutput, outputData); + const [udtValue, capacity] = acc; + + // xUDT cell + if (this.isUdt(cell)) { + return [udtValue + ccc.numFromBytes(ccc.bytesFrom(outputData).slice(0, 16)), capacity + cellOutput.capacity]; + } + // Receipt cell + if (this.logicScript.eq(type)) { + const txWithHeader = await client.getTransactionWithHeader(outPoint.txHash); + if (!txWithHeader?.header) throw new Error("Header not found"); + const { depositQuantity, depositAmount } = ReceiptData.decode(outputData); + return [udtValue + ickbValue(depositAmount, txWithHeader.header) * depositQuantity, capacity + cellOutput.capacity]; + } + // Deposit cell + if (this.logicScript.eq(lock) && this.daoManager.isDeposit(cell)) { + const txWithHeader = await client.getTransactionWithHeader(outPoint.txHash); + if (!txWithHeader?.header) throw new Error("Header not found"); + return [udtValue - ickbValue(cell.capacityFree, txWithHeader.header), capacity + cellOutput.capacity]; + } + return acc; + }, + [0n, 0n], + ); +} +``` + +### CCC Udt.infoFrom Base Implementation (Override Target) +```typescript +// Source: ccc-fork/ccc/packages/udt/src/udt/index.ts lines 624-641 +async infoFrom( + _client: ccc.Client, + cells: ccc.CellAnyLike | ccc.CellAnyLike[], + acc?: UdtInfoLike, +): Promise { + return [cells].flat().reduce((acc, cellLike) => { + const cell = ccc.CellAny.from(cellLike); + if (!this.isUdt(cell)) { + return acc; + } + return acc.addAssign({ + balance: Udt.balanceFromUnsafe(cell.outputData), + capacity: cell.cellOutput.capacity, + count: 1, + }); + }, UdtInfo.from(acc).clone()); +} +``` + +### CCC getInputsInfo Chain (How Input Cells Reach infoFrom) +```typescript +// Source: ccc-fork/ccc/packages/udt/src/udt/index.ts lines 1099-1108 +async getInputsInfo(client: ccc.Client, txLike: ccc.TransactionLike): Promise { + const tx = ccc.Transaction.from(txLike); + const inputCells = await Promise.all( + tx.inputs.map((input) => input.getCell(client)), + // getCell returns Cell.from({ outPoint: input.previousOutput, cellOutput, outputData }) + // These Cell objects ALWAYS have outPoint set + ); + return this.infoFrom(client, inputCells); +} + +// Source: ccc-fork/ccc/packages/udt/src/udt/index.ts lines 1178-1184 +async getOutputsInfo(client: ccc.Client, txLike: ccc.TransactionLike): Promise { + const tx = ccc.Transaction.from(txLike); + return this.infoFrom(client, Array.from(tx.outputCells)); + // tx.outputCells yields CellAny WITHOUT outPoint +} +``` + +### CellAny OutPoint Presence (Input vs Output Detection) +```typescript +// Source: ccc-fork/ccc/packages/core/src/ckb/transaction.ts lines 313-318, 331-348 +type CellAnyLike = { + outPoint?: OutPointLike | null; // present for inputs, absent for outputs + previousOutput?: OutPointLike | null; + cellOutput: CellOutputLike; + outputData?: HexLike | null; +}; + +class CellAny { + public outPoint: OutPoint | undefined; // undefined for output cells + get capacityFree() { return this.cellOutput.capacity - fixedPointFrom(this.occupiedSize); } + // ... +} +``` + +### PR #328 FeePayer.completeInputs Signature +```typescript +// Source: ccc-fork/ccc/packages/core/src/signer/feePayer/feePayer.ts lines 26-39 +abstract completeInputs( + tx: Transaction, + filter: ClientCollectableSearchKeyFilterLike, + accumulator: (acc: T, v: Cell, i: number, array: Cell[]) => Promise | T | undefined, + init: T, +): Promise<{ + addedCount: number; + accumulated?: T; +}>; +``` + +## State of the Art + +| Old Approach | Current Approach | When Changed | Impact | +|--------------|------------------|--------------|--------| +| `UdtHandler` interface + `UdtManager` class (iCKB custom) | CCC `udt.Udt` class with `infoFrom`/`getInputsInfo`/`getOutputsInfo` | CCC `@ckb-ccc/udt` package | Udt class provides completion pipeline; UdtHandler/UdtManager don't | +| `ccc.udtBalanceFrom()` (deprecated) | `udt.Udt.balanceFromUnsafe(outputData)` | Current CCC | Old API deprecated, new one in Udt class | +| `tx.completeInputsByUdt()` (deprecated) | `udt.completeInputsByBalance(tx, signer)` | Current CCC | Old on Transaction, new on Udt instance | +| `tx.getInputsUdtBalance()` / `tx.getOutputsUdtBalance()` (deprecated) | `udt.getInputsInfo(client, tx)` / `udt.getOutputsInfo(client, tx)` | Current CCC | New methods return UdtInfo (balance + capacity + count) | +| PR #328 FeePayer Udt (uses deprecated APIs) | Current CCC Udt (uses `infoFrom`) | Integrated into ccc-fork/ccc | PR #328's Udt is simpler, still uses old deprecated APIs; current CCC Udt is more complete | + +**Deprecated/outdated:** +- `ccc.udtBalanceFrom()`: Replaced by `udt.Udt.balanceFromUnsafe()` +- `tx.completeInputsByUdt()`: Replaced by `udt.Udt.completeInputsByBalance()` +- `tx.getInputsUdtBalance()` / `tx.getOutputsUdtBalance()`: Replaced by `udt.Udt.getInputsInfo()` / `udt.Udt.getOutputsInfo()` +- PR #328 FeePayer branch's Udt class: Uses deprecated APIs above; the current CCC Udt class (which we work with via ccc-fork) is more advanced + +## Open Questions + +1. **Receipt/Deposit Cell Discovery in completeInputsByBalance** + - What we know: `completeInputsByBalance` uses `this.filter` to find cells, which only matches xUDT cells by type script. Receipt and deposit cells have different scripts and won't be found. + - What's unclear: Should `IckbUdt` override `completeInputsByBalance` to also search for receipt/deposit cells? Or should receipt/deposit cells be pre-added to the transaction by the caller (as the current `LogicManager.completeDeposit` and `OwnedOwnerManager.requestWithdrawal` do)? + - Recommendation: **Caller responsibility.** The current pattern already has `LogicManager` and `OwnedOwnerManager` handling receipt/deposit cell discovery and addition. `IckbUdt.infoFrom` should accurately VALUE these cells when they appear in the transaction, but should NOT be responsible for FINDING them. This keeps the subclass clean and avoids fighting CCC's single-filter design. The `completeInputsByBalance` method then correctly accounts for receipt/deposit value that the caller has already added. + +2. **`capacityFree` on CellAny vs Cell** + - What we know: `ickbValue()` for deposit cells uses `cell.capacityFree` (unoccupied capacity). + - **Resolved:** `CellAny` has `capacityFree` getter (transaction.ts:404-405): `get capacityFree() { return this.cellOutput.capacity - fixedPointFrom(this.occupiedSize); }`. No need to construct a `Cell` -- `CellAny.from(cellLike).capacityFree` works directly in `infoFrom`. + - Note: `DaoManager.isDeposit()` still requires `ccc.Cell` (not `CellAny`). For deposit cells (which have `outPoint`), construct `Cell.from({ outPoint, cellOutput, outputData })` for the `isDeposit` call only. + +3. **PR #328 FeePayer Integration** + - What we know: PR #328 abstracts `completeInputs` into `FeePayer.completeInputs(tx, filter, accumulator, init)`. The current CCC Udt's `completeInputs` delegates to `tx.completeInputs(from, this.filter, ...)` which delegates to `from.completeInputs(...)` (Signer). PR #328 changes this to go through `FeePayer.completeInputs`. + - What's unclear: Whether IckbUdt needs any special handling for the FeePayer transition. + - Recommendation: **No special handling needed.** The `infoFrom` override is at a level below the completion plumbing. Whether `completeInputs` goes through Signer (current) or FeePayer (PR #328), the cells still flow through `getInputsInfo` -> `infoFrom`. The override is compatible with both architectures. + +4. **Conservation Law Enforcement in IckbUdt** + - What we know: On-chain iCKB Logic script enforces `Input UDT + Input Receipts = Output UDT + Input Deposits`. User decision says to explore both enforcement-at-build-time and caller-responsibility. + - What's unclear: The exact enforcement mechanism if implemented at build time. + - Recommendation: Start with **accurate balance reporting only** (caller responsibility). `infoFrom` correctly values all three cell types with proper sign conventions. A `getBalanceBurned()` call (inherited from base `Udt`) can then be used by callers to verify the conservation law before submitting. Enforcement at build time can be added later as a validation method if needed, but should not be embedded in `infoFrom` (which is a balance calculation method, not a validation method). + +## Sources + +### Primary (HIGH confidence) +- `ccc-fork/ccc/packages/udt/src/udt/index.ts` -- CCC Udt class source, `infoFrom`, `getInputsInfo`, `getOutputsInfo`, `completeInputsByBalance` full implementation +- `ccc-fork/ccc/packages/core/src/ckb/transaction.ts` -- `CellAny`, `CellAnyLike`, `Cell`, `CellInput.getCell()`, `outputCells` getter +- `ccc-fork/ccc/packages/core/src/client/client.ts` -- `getTransactionWithHeader()`, `getCellWithHeader()` implementations +- `packages/core/src/udt.ts` -- Current `IckbUdtManager`, `ickbValue()`, `convert()`, `ickbExchangeRatio()` +- `packages/utils/src/udt.ts` -- Current `UdtHandler` interface, `UdtManager` base class +- `packages/core/src/logic.ts` -- `LogicManager`, receipt/deposit identification +- `packages/core/src/cells.ts` -- `ReceiptCell`, `IckbDepositCell`, `receiptCellFrom` header access pattern +- `ccc-fork/ccc/packages/core/src/signer/feePayer/feePayer.ts` -- PR #328 FeePayer abstract class (now integrated into ccc-fork/ccc) + +### Secondary (MEDIUM confidence) +- None -- all findings verified from source code + +### Tertiary (LOW confidence) +- None + +## Metadata + +**Confidence breakdown:** +- Standard stack: HIGH -- all source code directly inspected in the workspace +- Architecture: HIGH -- override points verified by tracing full method chains in CCC source +- Pitfalls: HIGH -- derived from comparing current iCKB implementation with CCC Udt API surface; sign conventions and cell type distinctions verified from existing code + +**Research date:** 2026-02-23 +**Valid until:** Stable -- CCC Udt class API is mature; valid until major CCC refactor diff --git a/.planning/phases/03-ccc-udt-integration-investigation/03-VERIFICATION.md b/.planning/phases/03-ccc-udt-integration-investigation/03-VERIFICATION.md new file mode 100644 index 0000000..e96e8ae --- /dev/null +++ b/.planning/phases/03-ccc-udt-integration-investigation/03-VERIFICATION.md @@ -0,0 +1,111 @@ +--- +phase: 03-ccc-udt-integration-investigation +verified: 2026-02-24T12:00:00Z +status: passed +score: 6/6 must-haves verified +re_verification: false +--- + +# Phase 3: CCC Udt Integration Investigation Verification Report + +**Phase Goal:** Clear, documented decision on whether IckbUdt should extend CCC's `udt.Udt` class for iCKB's multi-representation value (xUDT + receipts + deposits), with the header access pattern designed. This decision determines the replacement for UdtHandler/UdtManager (which remain in `@ickb/utils` with updated signatures after Phase 1). +**Verified:** 2026-02-24T12:00:00Z +**Status:** passed +**Re-verification:** No — initial verification + +--- + +## Goal Achievement + +### Observable Truths + +Success criteria are drawn from ROADMAP.md Phase 3 and the `must_haves` frontmatter of 03-02-PLAN.md. + +| # | Truth | Status | Evidence | +|---|-------|--------|----------| +| 1 | A written feasibility assessment exists answering whether `IckbUdt extends udt.Udt` can override `infoFrom()` to account for receipt cells and deposit cells alongside xUDT cells without breaking CCC method chains | VERIFIED | `03-DECISION.md` "## Feasibility Assessment" section, 481-line document with YES answer, six sub-sections covering override point, three cell types, input/output distinction, capacityFree resolution, completion pipeline compatibility, and blockers | +| 2 | The header access pattern for receipt value calculation is designed and documented, specifying which CCC client API is used within the Udt override | VERIFIED | `03-DECISION.md` "## Header Access Pattern" section documents `client.getTransactionWithHeader(outPoint.txHash)` with confirmed line reference `client.ts:631-661`, caching behavior, async flow, and code sketch | +| 3 | A decision document exists with one of three outcomes (subclass/custom/hybrid) and rationale | VERIFIED | `03-DECISION.md` "## Decision" section explicitly states "Chosen: (a) Subclass CCC Udt" with four numbered rationale points, replacement table, gained features, and changed behaviors | +| 4 | The conservation law preservation strategy is documented (sign conventions and cell type handling) | VERIFIED | `03-DECISION.md` "## Conservation Law Strategy" section documents sign conventions (deposits negative), enforcement location (on-chain authoritative, optional build-time later), and `getBalanceBurned` inherited usage | +| 5 | The cell discovery vs balance calculation boundary is defined | VERIFIED | `03-DECISION.md` "## Cell Discovery vs Balance Calculation Boundary" section defines boundary: `infoFrom` values cells present in transaction; LogicManager/OwnedOwnerManager find and add receipt/deposit cells; rationale for no filter override provided | +| 6 | The decision document provides sufficient detail for Phase 4 and Phase 5 implementers to proceed without ambiguity | VERIFIED | `03-DECISION.md` "## Implementation Guidance for Phases 4-5" contains explicit deprecated API replacement table (4 mappings), manager constructor changes, Phase 5 constructor spec, deletion list, preservation list, and SDK update guidance | + +**Score:** 6/6 truths verified + +--- + +### Required Artifacts + +| Artifact | Expected | Status | Details | +|----------|----------|--------|---------| +| `.planning/phases/03-ccc-udt-integration-investigation/03-01-INVESTIGATION.md` | Detailed source code trace findings with exact line references and code snippets | VERIFIED | 717 lines; contains all 8 required sections; 24 concrete `file:line` references to CCC source; line-by-line mapping from `IckbUdtManager.getInputsUdtBalance` to `infoFrom` override | +| `.planning/phases/03-ccc-udt-integration-investigation/03-DECISION.md` | Formal decision document covering UDT-01, UDT-02, UDT-03 | VERIFIED | 481 lines; all 8 required sections present; "## Decision" section with explicit "Chosen: (a) Subclass CCC Udt"; 2 TypeScript code blocks; 39 references to `infoFrom`; implementation guidance for both phases | + +Both artifacts pass all three verification levels: +- **Level 1 (Exists):** Both files present on disk +- **Level 2 (Substantive):** Both exceed 400 lines, contain required section headings, include concrete code references +- **Level 3 (Wired):** DECISION.md is derived from INVESTIGATION.md (03-02-SUMMARY.md documents this chain); investigation is summarized in 03-01-SUMMARY.md which feeds 03-02 plan context + +--- + +### Key Link Verification + +| From | To | Via | Status | Details | +|------|----|-----|--------|---------| +| `03-01-INVESTIGATION.md` | `03-DECISION.md` | Investigation findings cited in decision (line references, code snippets in DECISION match investigation source) | WIRED | DECISION.md cites `index.ts:624-641`, `transaction.ts:404-405`, `client.ts:631-661` matching investigation findings; 03-02-PLAN.md explicitly depends on 03-01 | +| `03-DECISION.md` | ROADMAP.md Phase 4 and Phase 5 approach | "Decision outcome determines Phase 4 and Phase 5 approach" | WIRED | DECISION.md "## Implementation Guidance" contains explicit Phase 4 (dao/order API replacement table) and Phase 5 (IckbUdt creation, deletion list, SDK update) guidance; ROADMAP.md Phase 4/5 both reference "based on Phase 3 findings" | + +--- + +### Requirements Coverage + +| Requirement | Source Plan | Description | Status | Evidence | +|-------------|-------------|-------------|--------|----------| +| UDT-01 | 03-01-PLAN.md, 03-02-PLAN.md | Feasibility assessment: can `IckbUdt extends udt.Udt` override `infoFrom()` or `getInputsInfo()`/`getOutputsInfo()` to account for receipt and deposit cells alongside xUDT cells | SATISFIED | `03-DECISION.md` "## Feasibility Assessment" answers YES with source code evidence from investigation; REQUIREMENTS.md marks `[x]` complete | +| UDT-02 | 03-01-PLAN.md, 03-02-PLAN.md | Header access pattern for receipt value calculation is designed -- which CCC client API is used within the Udt override | SATISFIED | `03-DECISION.md` "## Header Access Pattern" documents `client.getTransactionWithHeader()` confirmed at `client.ts:631-661`; caching, async flow, code sketch included; REQUIREMENTS.md marks `[x]` complete | +| UDT-03 | 03-02-PLAN.md | Decision documented: subclass CCC Udt vs. keep custom UdtHandler interface vs. hybrid approach | SATISFIED | `03-DECISION.md` "## Decision" states "(a) Subclass CCC Udt" with rationale; REQUIREMENTS.md marks `[x]` complete | + +No orphaned requirements: REQUIREMENTS.md maps only UDT-01, UDT-02, UDT-03 to Phase 3. All three claimed by plans. All three present in DECISION.md with dedicated sections. + +--- + +### Anti-Patterns Found + +No modified source code files in this phase — it is a documentation-only investigation phase. Anti-pattern scanning is not applicable. Files created: `03-01-INVESTIGATION.md`, `03-DECISION.md`, `03-01-SUMMARY.md`, `03-02-SUMMARY.md`. + +No TODO/FIXME/placeholder patterns in any created documentation files. + +--- + +### Human Verification Required + +None. This phase produces documentation artifacts (a decision document and an investigation document). All goal criteria are verifiable by inspecting file existence, section headings, content specificity, and commit hash validity — all automated checks pass. + +--- + +### Commits Verified + +All commits documented in summaries confirmed to exist in git history: +- `b2827e5` — Investigation document (03-01) — EXISTS +- `681248e` — Decision document feasibility and header sections (03-02 Task 1) — EXISTS +- `8daffd7` — Decision document decision and implementation guidance (03-02 Task 2) — EXISTS + +--- + +## Summary + +Phase 3 goal is achieved. The phase produced two substantive documentation artifacts: + +**`03-01-INVESTIGATION.md`** (717 lines) traces every CCC Udt method chain (`infoFrom`, `getInputsInfo`, `getOutputsInfo`, `completeInputsByBalance`, `completeInputs`) with 24 concrete source file:line references. It resolves all 4 open questions from 03-RESEARCH.md with code evidence and provides a line-by-line mapping from the current `IckbUdtManager.getInputsUdtBalance` to the planned `IckbUdt.infoFrom` override. + +**`03-DECISION.md`** (481 lines) contains all 8 required sections. It answers the phase's three requirements: +- UDT-01: Feasibility YES, `infoFrom` is the sole override point, no upstream CCC changes required +- UDT-02: `client.getTransactionWithHeader(outPoint.txHash)` is the header access API, with caching handled by CCC `Client.cache` +- UDT-03: Decision is (a) subclass CCC Udt — `IckbUdt extends udt.Udt` with `infoFrom` override + +The document is self-contained: a Phase 4 or Phase 5 implementer can read it and know exactly what classes to create, delete, and modify, what APIs replace what, and how the conservation law is preserved. No re-investigation is needed. + +--- + +_Verified: 2026-02-24T12:00:00Z_ +_Verifier: AI Coworker (gsd-verifier)_ From 3694442cf16540bb9155100dd890bbbe2c193d5d Mon Sep 17 00:00:00 2001 From: phroi <90913182+phroi@users.noreply.github.com> Date: Wed, 25 Feb 2026 02:08:18 +0000 Subject: [PATCH 2/3] docs: refine research and earlier phase docs with phase-03 findings Update infoFrom override strategy (single override point, not getInputsInfo/getOutputsInfo), correct fork-scripts paths in phase-01 summaries, and incorporate CCC Udt investigation results into research documents. --- .../01-01-PLAN.md | 61 ++++--- .../01-01-SUMMARY.md | 43 +++-- .../01-02-SUMMARY.md | 8 +- .../01-CONTEXT.md | 4 +- .../01-RESEARCH.md | 28 ++-- .../01-VERIFICATION.md | 8 +- .../02-ccc-utility-adoption/02-RESEARCH.md | 18 +-- .planning/research/ARCHITECTURE.md | 153 ++++++------------ .planning/research/FEATURES.md | 2 +- .planning/research/PITFALLS.md | 6 +- .planning/research/STACK.md | 50 +++--- .planning/research/SUMMARY.md | 30 ++-- 12 files changed, 179 insertions(+), 232 deletions(-) diff --git a/.planning/phases/01-ickb-utils-smarttransaction-removal/01-01-PLAN.md b/.planning/phases/01-ickb-utils-smarttransaction-removal/01-01-PLAN.md index 6845796..0193523 100644 --- a/.planning/phases/01-ickb-utils-smarttransaction-removal/01-01-PLAN.md +++ b/.planning/phases/01-ickb-utils-smarttransaction-removal/01-01-PLAN.md @@ -5,11 +5,10 @@ type: execute wave: 1 depends_on: [] files_modified: - - ccc-dev/ccc/packages/core/src/ckb/transactionErrors.ts - - ccc-dev/ccc/packages/core/src/ckb/transaction.ts - - ccc-dev/ccc/packages/core/src/ckb/index.ts - - ccc-dev/pins/REFS - - ccc-dev/pins/HEAD + - ccc-fork/ccc/packages/core/src/ckb/transactionErrors.ts + - ccc-fork/ccc/packages/core/src/ckb/transaction.ts + - ccc-fork/ccc/packages/core/src/ckb/index.ts + - ccc-fork/pins/ - packages/dao/src/dao.ts - packages/core/src/logic.ts - packages/core/src/owned_owner.ts @@ -25,23 +24,23 @@ must_haves: - "The `ErrorNervosDaoOutputLimit` error class exists in CCC `transactionErrors.ts` with count and limit fields" - "`pnpm check:full` passes after DAO check consolidation" artifacts: - - path: "ccc-dev/ccc/packages/core/src/ckb/transactionErrors.ts" + - path: "ccc-fork/ccc/packages/core/src/ckb/transactionErrors.ts" provides: "ErrorNervosDaoOutputLimit error class" contains: "ErrorNervosDaoOutputLimit" - - path: "ccc-dev/ccc/packages/core/src/ckb/transaction.ts" + - path: "ccc-fork/ccc/packages/core/src/ckb/transaction.ts" provides: "assertDaoOutputLimit utility function and completeFee safety net" contains: "assertDaoOutputLimit" key_links: - from: "packages/dao/src/dao.ts" - to: "ccc-dev/ccc/packages/core/src/ckb/transaction.ts" + to: "ccc-fork/ccc/packages/core/src/ckb/transaction.ts" via: "import and call assertDaoOutputLimit" pattern: "assertDaoOutputLimit" - from: "packages/core/src/logic.ts" - to: "ccc-dev/ccc/packages/core/src/ckb/transaction.ts" + to: "ccc-fork/ccc/packages/core/src/ckb/transaction.ts" via: "import and call assertDaoOutputLimit" pattern: "assertDaoOutputLimit" - from: "packages/core/src/owned_owner.ts" - to: "ccc-dev/ccc/packages/core/src/ckb/transaction.ts" + to: "ccc-fork/ccc/packages/core/src/ckb/transaction.ts" via: "import and call assertDaoOutputLimit" pattern: "assertDaoOutputLimit" --- @@ -51,7 +50,7 @@ Build the 64-output NervosDAO limit check as a CCC core utility and replace all Purpose: Consolidate duplicated DAO output limit logic into a single source of truth in CCC core, preparing for SmartTransaction deletion by removing one of its responsibilities. This is the first feature-slice step (purely additive, nothing breaks). -Output: `ErrorNervosDaoOutputLimit` error class + `assertDaoOutputLimit` utility in CCC core; all iCKB packages calling the utility instead of inline checks; ccc-dev pins recorded. +Output: `ErrorNervosDaoOutputLimit` error class + `assertDaoOutputLimit` utility in CCC core; all iCKB packages calling the utility instead of inline checks; ccc-fork pins recorded. @@ -68,9 +67,9 @@ Output: `ErrorNervosDaoOutputLimit` error class + `assertDaoOutputLimit` utility @.planning/codebase/ARCHITECTURE.md Key source files to read: -@ccc-dev/ccc/packages/core/src/ckb/transactionErrors.ts (error class patterns) -@ccc-dev/ccc/packages/core/src/ckb/transaction.ts (completeFee method, where safety net goes) -@ccc-dev/ccc/packages/core/src/ckb/index.ts (barrel exports) +@ccc-fork/ccc/packages/core/src/ckb/transactionErrors.ts (error class patterns) +@ccc-fork/ccc/packages/core/src/ckb/transaction.ts (completeFee method, where safety net goes) +@ccc-fork/ccc/packages/core/src/ckb/index.ts (barrel exports) @packages/utils/src/transaction.ts (SmartTransaction.completeFee with DAO check at lines 85-95) @packages/dao/src/dao.ts (DaoManager with 3 DAO checks at lines 100, 174, 245) @packages/core/src/logic.ts (LogicManager.deposit with DAO check at line 106) @@ -82,14 +81,14 @@ Key source files to read: Task 1: Build ErrorNervosDaoOutputLimit and assertDaoOutputLimit in CCC core - ccc-dev/ccc/packages/core/src/ckb/transactionErrors.ts - ccc-dev/ccc/packages/core/src/ckb/transaction.ts - ccc-dev/ccc/packages/core/src/ckb/index.ts + ccc-fork/ccc/packages/core/src/ckb/transactionErrors.ts + ccc-fork/ccc/packages/core/src/ckb/transaction.ts + ccc-fork/ccc/packages/core/src/ckb/index.ts **Step 1: Add error class to transactionErrors.ts** -Read `ccc-dev/ccc/packages/core/src/ckb/transactionErrors.ts`. Add `ErrorNervosDaoOutputLimit` following the existing `ErrorTransactionInsufficientCapacity` pattern: +Read `ccc-fork/ccc/packages/core/src/ckb/transactionErrors.ts`. Add `ErrorNervosDaoOutputLimit` following the existing `ErrorTransactionInsufficientCapacity` pattern: ```typescript export class ErrorNervosDaoOutputLimit extends Error { @@ -108,7 +107,7 @@ export class ErrorNervosDaoOutputLimit extends Error { **Step 2: Add assertDaoOutputLimit to transaction.ts** -Read `ccc-dev/ccc/packages/core/src/ckb/transaction.ts`. Add the standalone utility function as a module-level export (NOT a method on Transaction). Place it after the Transaction class definition. Import `ErrorNervosDaoOutputLimit` from `./transactionErrors.js`, `KnownScript` from the appropriate module, and `Script` if not already imported: +Read `ccc-fork/ccc/packages/core/src/ckb/transaction.ts`. Add the standalone utility function as a module-level export (NOT a method on Transaction). Place it after the Transaction class definition. Import `ErrorNervosDaoOutputLimit` from `./transactionErrors.js`, `KnownScript` from the appropriate module, and `Script` if not already imported: ```typescript /** @@ -154,20 +153,20 @@ In the `completeFee` method of the Transaction class (around line 2183), add a c **Step 4: Export from barrel** -Read `ccc-dev/ccc/packages/core/src/ckb/index.ts`. Add exports for `ErrorNervosDaoOutputLimit` (from `./transactionErrors.js`) and `assertDaoOutputLimit` (from `./transaction.js`). Follow existing export patterns in the file. +Read `ccc-fork/ccc/packages/core/src/ckb/index.ts`. Add exports for `ErrorNervosDaoOutputLimit` (from `./transactionErrors.js`) and `assertDaoOutputLimit` (from `./transaction.js`). Follow existing export patterns in the file. -**Step 5: Record ccc-dev pins** +**Step 5: Record ccc-fork pins** -Run: `pnpm ccc:record` +Run: `pnpm fork:record` -This updates `ccc-dev/pins/REFS` and `ccc-dev/pins/HEAD` to reflect the new CCC state. +This updates `ccc-fork/pins/` to reflect the new CCC state. -Verify: `pnpm ccc:status` should exit 0 (no pending work). +Verify: `pnpm fork:status` should exit 0 (no pending work). -1. `pnpm ccc:status` exits 0 -2. `grep -r "ErrorNervosDaoOutputLimit" ccc-dev/ccc/packages/core/src/ckb/transactionErrors.ts` finds the class -3. `grep -r "assertDaoOutputLimit" ccc-dev/ccc/packages/core/src/ckb/transaction.ts` finds the function +1. `pnpm fork:status` exits 0 +2. `grep -r "ErrorNervosDaoOutputLimit" ccc-fork/ccc/packages/core/src/ckb/transactionErrors.ts` finds the class +3. `grep -r "assertDaoOutputLimit" ccc-fork/ccc/packages/core/src/ckb/transaction.ts` finds the function 4. `pnpm check:full` passes (purely additive change, nothing should break) @@ -244,9 +243,9 @@ All 7 inline DAO output checks are removed. Every location now calls `await asse 1. `grep -rn "outputs.length > 64" packages/` -- should return 0 results -2. `grep -rn "assertDaoOutputLimit" packages/ ccc-dev/ccc/packages/core/src/ckb/` -- should show usage in 4 iCKB files + definition in CCC -3. `grep -rn "ErrorNervosDaoOutputLimit" ccc-dev/ccc/packages/core/src/ckb/` -- should show class definition + export -4. `pnpm ccc:status` -- should exit 0 +2. `grep -rn "assertDaoOutputLimit" packages/ ccc-fork/ccc/packages/core/src/ckb/` -- should show usage in 4 iCKB files + definition in CCC +3. `grep -rn "ErrorNervosDaoOutputLimit" ccc-fork/ccc/packages/core/src/ckb/` -- should show class definition + export +4. `pnpm fork:status` -- should exit 0 5. `pnpm check:full` -- should pass clean @@ -256,7 +255,7 @@ All 7 inline DAO output checks are removed. Every location now calls `await asse - completeFee has DAO safety net - All 7 scattered inline checks replaced with centralized utility calls - No intermediate broken build state -- ccc-dev pins recorded +- ccc-fork pins recorded diff --git a/.planning/phases/01-ickb-utils-smarttransaction-removal/01-01-SUMMARY.md b/.planning/phases/01-ickb-utils-smarttransaction-removal/01-01-SUMMARY.md index 634b3a9..72b8256 100644 --- a/.planning/phases/01-ickb-utils-smarttransaction-removal/01-01-SUMMARY.md +++ b/.planning/phases/01-ickb-utils-smarttransaction-removal/01-01-SUMMARY.md @@ -10,34 +10,34 @@ provides: - ErrorNervosDaoOutputLimit error class in CCC core - assertDaoOutputLimit centralized utility function in CCC core - completeFee safety net for DAO output limit in CCC core - - ccc-dev local patch mechanism for deterministic builds + - ccc-fork local patch mechanism for deterministic builds affects: [01-02, 01-03, 01-04] # Tech tracking tech-stack: added: [] - patterns: [centralized-dao-limit-check, ccc-dev-local-patches] + patterns: [centralized-dao-limit-check, ccc-fork-local-patches] key-files: created: - - ccc-dev/pins/local/001-dao-output-limit.patch + - ccc-fork/pins/local-001-dao-output-limit.patch (now part of pins/ multi-file format) modified: - - ccc-dev/ccc/packages/core/src/ckb/transactionErrors.ts - - ccc-dev/ccc/packages/core/src/ckb/transaction.ts - - ccc-dev/record.sh - - ccc-dev/replay.sh + - ccc-fork/ccc/packages/core/src/ckb/transactionErrors.ts + - ccc-fork/ccc/packages/core/src/ckb/transaction.ts + - ccc-fork/record.sh + - ccc-fork/replay.sh - packages/dao/src/dao.ts - packages/core/src/logic.ts - packages/core/src/owned_owner.ts - packages/utils/src/transaction.ts key-decisions: - - "Added ccc-dev local patch mechanism (pins/local/*.patch) to support deterministic replay of CCC source modifications" + - "Added ccc-fork local patch mechanism (pins/local-*.patch) to support deterministic replay of CCC source modifications" - "Moved client parameter before optional options in DaoManager.requestWithdrawal and DaoManager.withdraw signatures" - "assertDaoOutputLimit uses early return when outputs <= 64 for zero-cost in common case" patterns-established: - - "Local CCC patches: place .patch files in ccc-dev/pins/local/ for changes applied after standard merge+patch cycle" + - "Local CCC patches: pins/local-*.patch files applied after standard merge+patch cycle" - "DAO output limit: always use ccc.assertDaoOutputLimit(tx, client) instead of inline checks" requirements-completed: [SMTX-06] @@ -64,7 +64,7 @@ completed: 2026-02-22 - Built assertDaoOutputLimit utility function that checks both inputs and outputs for DAO type script using full Script.eq() comparison - Added completeFee safety net in CCC Transaction class (both return paths) - Replaced all 7 inline DAO output checks across 4 files with centralized utility calls -- Added local patch mechanism to ccc-dev record/replay for deterministic builds of CCC modifications +- Added local patch mechanism to ccc-fork record/replay for deterministic builds of CCC modifications ## Task Commits @@ -74,19 +74,18 @@ Each task was committed atomically: 2. **Task 2: Replace all 7 scattered DAO checks with assertDaoOutputLimit calls** - `2decd06` (refactor) ## Files Created/Modified -- `ccc-dev/ccc/packages/core/src/ckb/transactionErrors.ts` - ErrorNervosDaoOutputLimit error class -- `ccc-dev/ccc/packages/core/src/ckb/transaction.ts` - assertDaoOutputLimit utility + completeFee safety net -- `ccc-dev/pins/local/001-dao-output-limit.patch` - Local patch for deterministic replay -- `ccc-dev/pins/HEAD` - Updated pinned HEAD SHA -- `ccc-dev/record.sh` - Added local patch preservation and application -- `ccc-dev/replay.sh` - Added local patch application after standard merge+patch +- `ccc-fork/ccc/packages/core/src/ckb/transactionErrors.ts` - ErrorNervosDaoOutputLimit error class +- `ccc-fork/ccc/packages/core/src/ckb/transaction.ts` - assertDaoOutputLimit utility + completeFee safety net +- `ccc-fork/pins/` - Updated pins for deterministic replay +- `ccc-fork/record.sh` - Added local patch preservation and application +- `ccc-fork/replay.sh` - Added local patch application after standard merge+patch - `packages/dao/src/dao.ts` - DaoManager.deposit/requestWithdrawal/withdraw now async with client param - `packages/core/src/logic.ts` - LogicManager.deposit now async with client param - `packages/core/src/owned_owner.ts` - OwnedOwnerManager.requestWithdrawal/withdraw now async with client param - `packages/utils/src/transaction.ts` - SmartTransaction.completeFee DAO check replaced ## Decisions Made -- Added ccc-dev local patch mechanism (pins/local/*.patch) because the existing record/replay infrastructure had no way to persist source-level CCC modifications through the clean/replay cycle. This was a necessary blocking-issue fix (Rule 3). +- Added ccc-fork local patch mechanism (pins/local-*.patch) because the existing record/replay infrastructure had no way to persist source-level CCC modifications through the clean/replay cycle. This was a necessary blocking-issue fix (Rule 3). - Placed `client: ccc.Client` parameter before optional `options` parameters in DaoManager signatures for cleaner API design (required params before optional). - assertDaoOutputLimit uses early return when `outputs.length <= 64` so the common-case path has zero async overhead. @@ -94,11 +93,11 @@ Each task was committed atomically: ### Auto-fixed Issues -**1. [Rule 3 - Blocking] Added ccc-dev local patch mechanism** +**1. [Rule 3 - Blocking] Added ccc-fork local patch mechanism** - **Found during:** Task 1 (CCC core changes) -- **Issue:** ccc-dev record/replay infrastructure had no way to persist local CCC source modifications. Running `pnpm ccc:record` or `pnpm check:full` would wipe changes because replay clones fresh from upstream. -- **Fix:** Added `pins/local/` directory for `.patch` files. Modified `record.sh` to preserve local patches during re-recording and apply them after standard merge+patch. Modified `replay.sh` to apply local patches after standard replay. Both use deterministic git identity/timestamps for reproducible HEAD SHAs. -- **Files modified:** `ccc-dev/record.sh`, `ccc-dev/replay.sh`, `ccc-dev/pins/local/001-dao-output-limit.patch` +- **Issue:** ccc-fork record/replay infrastructure had no way to persist local CCC source modifications. Running `pnpm fork:record` or `pnpm check:full` would wipe changes because replay clones fresh from upstream. +- **Fix:** Added `pins/local-*.patch` mechanism. Modified `record.sh` to preserve local patches during re-recording and apply them after standard merge+patch. Modified `replay.sh` to apply local patches after standard replay. Both use deterministic git identity/timestamps for reproducible HEAD SHAs. +- **Files modified:** `ccc-fork/record.sh`, `ccc-fork/replay.sh`, `ccc-fork/pins/local-001-dao-output-limit.patch` - **Verification:** `pnpm check:full` passes (clean wipe + replay + build cycle) - **Committed in:** 7081869 (Task 1 commit) @@ -120,7 +119,7 @@ None - no external service configuration required. ## Self-Check: PASSED -- FOUND: ccc-dev/pins/local/001-dao-output-limit.patch +- FOUND: ccc-fork/pins/ (local patch integrated into multi-file format) - FOUND: 01-01-SUMMARY.md - FOUND: commit 7081869 (Task 1) - FOUND: commit 2decd06 (Task 2) diff --git a/.planning/phases/01-ickb-utils-smarttransaction-removal/01-02-SUMMARY.md b/.planning/phases/01-ickb-utils-smarttransaction-removal/01-02-SUMMARY.md index abc27f5..04f5e3b 100644 --- a/.planning/phases/01-ickb-utils-smarttransaction-removal/01-02-SUMMARY.md +++ b/.planning/phases/01-ickb-utils-smarttransaction-removal/01-02-SUMMARY.md @@ -33,7 +33,7 @@ key-files: - packages/sdk/src/sdk.ts key-decisions: - - "Moved getHeader/HeaderKey to transaction.ts as non-exported internals rather than deleting entirely, since SmartTransaction class still uses them internally until Plan 03 deletion" + - "Moved getHeader/HeaderKey to transaction.ts as non-exported internals (deleted alongside SmartTransaction in 01-03)" - "TransactionHeader moved to utils.ts as canonical location, imported by transaction.ts" - "Inlined CCC client calls use explicit null checks with descriptive error messages matching original getHeader throw semantics" @@ -84,7 +84,7 @@ Each task was committed atomically: - `packages/sdk/src/sdk.ts` - Inlined 1 getHeader call with client.getTransactionWithHeader ## Decisions Made -- Moved getHeader/HeaderKey into transaction.ts as non-exported internals rather than deleting entirely. SmartTransaction's own instance methods (getHeader, encodeHeaderKey, addHeaders) still reference these internally. Deleting them would break SmartTransaction, which is not removed until Plan 03. This keeps the public API clean while maintaining internal consistency. +- Moved getHeader/HeaderKey into transaction.ts as non-exported internals rather than deleting entirely. SmartTransaction's own instance methods (getHeader, encodeHeaderKey, addHeaders) still reference these internally. Deleting them would break SmartTransaction, which was removed in Plan 01-03. This kept the public API clean while maintaining internal consistency. - TransactionHeader placed in utils.ts as the canonical location since it outlives SmartTransaction (used by DaoCell.headers and ReceiptCell.header). - Inlined CCC client calls preserve the original error semantics: getHeader always threw on null results, and the inlined code also throws with descriptive messages. @@ -95,7 +95,7 @@ Each task was committed atomically: **1. [Rule 3 - Blocking] Retained getHeader/HeaderKey as non-exported internals in transaction.ts** - **Found during:** Task 1 (Step 5 - removing getHeader from utils.ts) - **Issue:** SmartTransaction class in transaction.ts imports and uses the standalone getHeader function and HeaderKey type internally. Removing them from utils.ts without providing them in transaction.ts would break the class. -- **Fix:** Moved getHeader function and HeaderKey type into transaction.ts as non-exported (internal) declarations. They are no longer part of the @ickb/utils public API but remain available for SmartTransaction's internal use until Plan 03 deletes the class. +- **Fix:** Moved getHeader function and HeaderKey type into transaction.ts as non-exported (internal) declarations. They are no longer part of the @ickb/utils public API but remained available for SmartTransaction's internal use until 01-03 deleted the class. - **Files modified:** packages/utils/src/transaction.ts - **Verification:** pnpm check:full passes; HeaderKey/getHeader not found in any consumer packages - **Committed in:** 85ead3a (Task 1 commit) @@ -103,7 +103,7 @@ Each task was committed atomically: --- **Total deviations:** 1 auto-fixed (1 blocking) -**Impact on plan:** Necessary to keep SmartTransaction functional until Plan 03 removes it. No scope creep. +**Impact on plan:** Necessary to keep SmartTransaction functional until 01-03 removed it. No scope creep. ## Issues Encountered None - plan executed smoothly once the internal SmartTransaction dependency was handled. diff --git a/.planning/phases/01-ickb-utils-smarttransaction-removal/01-CONTEXT.md b/.planning/phases/01-ickb-utils-smarttransaction-removal/01-CONTEXT.md index d678251..5589ebd 100644 --- a/.planning/phases/01-ickb-utils-smarttransaction-removal/01-CONTEXT.md +++ b/.planning/phases/01-ickb-utils-smarttransaction-removal/01-CONTEXT.md @@ -13,9 +13,9 @@ Delete SmartTransaction class and its infrastructure across all packages; contri ## Implementation Decisions -### CCC DAO Contribution (via ccc-dev/) +### CCC DAO Contribution (via ccc-fork/) - Build the 64-output NervosDAO limit check **in CCC core**, not in @ickb/utils -- Develop in `ccc-dev/ccc/`, record pins, use immediately via workspace override while waiting for upstream merge +- Develop in `ccc-fork/ccc/`, record pins, use immediately via workspace override while waiting for upstream merge - **Submit the upstream CCC PR during Phase 1 execution** - CCC PR includes three components: 1. **`completeFee()` safety net** — async check using `client.getKnownScript(KnownScript.NervosDao)` with full `Script.eq()` comparison diff --git a/.planning/phases/01-ickb-utils-smarttransaction-removal/01-RESEARCH.md b/.planning/phases/01-ickb-utils-smarttransaction-removal/01-RESEARCH.md index 54aed2a..fe713b1 100644 --- a/.planning/phases/01-ickb-utils-smarttransaction-removal/01-RESEARCH.md +++ b/.planning/phases/01-ickb-utils-smarttransaction-removal/01-RESEARCH.md @@ -6,7 +6,7 @@ ## Summary -Phase 1 removes `SmartTransaction`, `CapacityManager`, `getHeader()`/`HeaderKey`, and 7 scattered 64-output DAO limit checks. It contributes the DAO check to CCC core via `ccc-dev/`, updates all manager method signatures across all 5 library packages from `SmartTransaction` to `ccc.TransactionLike`, and keeps the build green after every step. +Phase 1 removes `SmartTransaction`, `CapacityManager`, `getHeader()`/`HeaderKey`, and 7 scattered 64-output DAO limit checks. It contributes the DAO check to CCC core via `ccc-fork/`, updates all manager method signatures across all 5 library packages from `SmartTransaction` to `ccc.TransactionLike`, and keeps the build green after every step. The codebase is well-structured: SmartTransaction has exactly 9 consumer files across 5 packages; `getHeader` has 5 standalone call sites plus 4 instance method call sites; the 64-output DAO check appears in 7 locations across 4 files. CCC's native `Transaction` already handles DAO profit in `getInputsCapacity()` via `getInputsCapacityExtra()` -> `CellInput.getExtraCapacity()` -> `Cell.getDaoProfit()`, making SmartTransaction's `getInputsCapacity` override redundant. CCC's `Transaction.from(txLike)` provides the `TransactionLike` -> `Transaction` entry-point conversion pattern that all updated method signatures will follow. @@ -17,7 +17,7 @@ The codebase is well-structured: SmartTransaction has exactly 9 consumer files a ### Locked Decisions - Build the 64-output NervosDAO limit check **in CCC core**, not in @ickb/utils -- Develop in `ccc-dev/ccc/`, record pins, use immediately via workspace override while waiting for upstream merge +- Develop in `ccc-fork/ccc/`, record pins, use immediately via workspace override while waiting for upstream merge - **Submit the upstream CCC PR during Phase 1 execution** - CCC PR includes three components: 1. **`completeFee()` safety net** -- async check using `client.getKnownScript(KnownScript.NervosDao)` with full `Script.eq()` comparison @@ -76,14 +76,14 @@ The codebase is well-structured: SmartTransaction has exactly 9 consumer files a |---------|---------|---------|--------------| | `@ckb-ccc/core` | catalog: (^1.12.2) | CKB blockchain SDK | Project's core dependency; `Transaction`, `TransactionLike`, `Client`, `Script.eq()` | | TypeScript | ^5.9.3 (strict mode) | Type safety | `noUncheckedIndexedAccess`, `verbatimModuleSyntax`, `noImplicitOverride` | -| tsgo | native-preview | Type checking | Used via `ccc-dev/tsgo-filter.sh` when CCC is cloned | +| tsgo | native-preview | Type checking | Used via `ccc-fork/tsgo-filter.sh` when CCC is cloned | | vitest | ^3.2.4 | Testing | CCC's test framework; tests for the CCC PR | | pnpm | 10.30.1 | Package management | Workspace protocol, catalog specifiers | ### Supporting | Library | Version | Purpose | When to Use | |---------|---------|---------|-------------| -| `ccc-dev/` system | local | Local CCC development | Building/testing CCC DAO contribution before upstream merge | +| `ccc-fork/` system | local | Local CCC development | Building/testing CCC DAO contribution before upstream merge | | `@changesets/cli` | ^2.29.8 | Versioning | After API changes, run `pnpm changeset` | ### Alternatives Considered @@ -115,7 +115,7 @@ packages/ ├── sdk/src/ │ ├── sdk.ts # IckbSdk (SmartTransaction -> TransactionLike, CapacityManager removed) │ └── constants.ts # getConfig (CapacityManager removed) -ccc-dev/ccc/packages/core/src/ckb/ +ccc-fork/ccc/packages/core/src/ckb/ ├── transactionErrors.ts # + ErrorNervosDaoOutputLimit (new) └── transaction.ts # + completeFee safety net + assertDaoOutputLimit (new) ``` @@ -182,7 +182,7 @@ if (!tx.headerDeps.some((h) => h === hash)) { **When to use:** The new `ErrorNervosDaoOutputLimit` **Example:** ```typescript -// Source: ccc-dev/ccc/packages/core/src/ckb/transactionErrors.ts +// Source: ccc-fork/ccc/packages/core/src/ckb/transactionErrors.ts // Follow the ErrorTransactionInsufficientCapacity pattern: export class ErrorNervosDaoOutputLimit extends Error { public readonly count: number; @@ -310,17 +310,17 @@ tx.addCellDeps(this.udtHandler.cellDeps); **How to avoid:** Replace `tx.getHeader(client, { type: "txHash", value: outPoint.txHash })` with inlined CCC client calls. The headerDeps validation from the old `getHeader` instance method was a runtime check that headers were pre-populated -- after removal, the client call fetches headers directly. **Warning signs:** TypeScript error `Property 'getHeader' does not exist on type 'Transaction'`. -### Pitfall 8: ccc-dev pins must be recorded after CCC changes -**What goes wrong:** Making changes to `ccc-dev/ccc/` without running `pnpm ccc:record` means the pins don't reflect the new state. -**Why it happens:** `ccc-dev/pins/HEAD` is a hash integrity check. If ccc code changes but pins don't update, replay won't reproduce the same state. -**How to avoid:** After developing the DAO utility in `ccc-dev/ccc/`, run `pnpm ccc:record` to update pins. Check `pnpm ccc:status` to verify. -**Warning signs:** `pnpm ccc:status` reports exit code 1 (pending work). +### Pitfall 8: ccc-fork pins must be recorded after CCC changes +**What goes wrong:** Making changes to `ccc-fork/ccc/` without running `pnpm fork:record` means the pins don't reflect the new state. +**Why it happens:** `ccc-fork/pins/` contains an integrity check. If ccc code changes but pins don't update, replay won't reproduce the same state. +**How to avoid:** After developing the DAO utility in `ccc-fork/ccc/`, run `pnpm fork:record` to update pins. Check `pnpm fork:status` to verify. +**Warning signs:** `pnpm fork:status` reports exit code 1 (pending work). ## Code Examples ### Complete DAO Check Replacement Pattern ```typescript -// Source: Verified from ccc-dev/ccc/packages/core/src/ckb/transaction.ts +// Source: Verified from ccc-fork/ccc/packages/core/src/ckb/transaction.ts // and packages/dao/src/dao.ts // Before (scattered in 7 locations): @@ -392,7 +392,7 @@ for (const lock of unique(this.bots)) { ### CCC Vitest Test Pattern ```typescript -// Source: ccc-dev/ccc/packages/core/src/ckb/transaction.test.ts +// Source: ccc-fork/ccc/packages/core/src/ckb/transaction.test.ts import { beforeEach, describe, expect, it, vi } from "vitest"; import { ccc } from "../index.js"; @@ -547,7 +547,7 @@ This interface must be moved to a surviving file before `transaction.ts` is dele ### Primary (HIGH confidence) - Codebase source files in `/workspaces/stack/packages/` -- all SmartTransaction consumers, getHeader call sites, DAO checks inventoried directly -- CCC source in `/workspaces/stack/ccc-dev/ccc/packages/core/src/` -- Transaction class, TransactionLike type, error patterns, completeFee implementation, getInputsCapacity, test patterns +- CCC source in `/workspaces/stack/ccc-fork/ccc/packages/core/src/` -- Transaction class, TransactionLike type, error patterns, completeFee implementation, getInputsCapacity, test patterns - `.planning/phases/01-ickb-utils-smarttransaction-removal/01-CONTEXT.md` -- User decisions and constraints - `.planning/REQUIREMENTS.md` -- Requirement definitions and traceability diff --git a/.planning/phases/01-ickb-utils-smarttransaction-removal/01-VERIFICATION.md b/.planning/phases/01-ickb-utils-smarttransaction-removal/01-VERIFICATION.md index c513edd..3a874d1 100644 --- a/.planning/phases/01-ickb-utils-smarttransaction-removal/01-VERIFICATION.md +++ b/.planning/phases/01-ickb-utils-smarttransaction-removal/01-VERIFICATION.md @@ -23,7 +23,7 @@ re_verification: false | 1 | `SmartTransaction` class and `CapacityManager` class no longer exist in `@ickb/utils` source or exports | VERIFIED | `packages/utils/src/transaction.ts` and `packages/utils/src/capacity.ts` are deleted; `packages/utils/src/index.ts` exports only `codec.js`, `heap.js`, `udt.js`, `utils.js`; `grep SmartTransaction packages/ apps/` returns zero results | | 2 | `UdtHandler` interface and `UdtManager` class remain in `@ickb/utils` with method signatures updated from `SmartTransaction` to `ccc.TransactionLike` | VERIFIED | `packages/utils/src/udt.ts` exports both `UdtHandler` interface and `UdtManager` class; all methods accept `txLike: ccc.TransactionLike` and convert with `ccc.Transaction.from(txLike)` at entry | | 3 | `getHeader()` function and `HeaderKey` type are removed from `@ickb/utils`; all call sites inline CCC client calls; `SmartTransaction.addHeaders()` call sites push to `tx.headerDeps` directly | VERIFIED | `grep getHeader packages/utils/src/` returns zero results; `grep HeaderKey packages/` returns zero results; `grep addHeaders packages/` returns zero results; all 7 call sites replaced with `client.getTransactionWithHeader()` / `client.getHeaderByNumber()` with null-check-and-throw; 3 `headerDeps.push()` sites with `.some()` dedup in `dao/dao.ts` and `core/logic.ts` | -| 4 | A 64-output NervosDAO limit check exists in CCC core: `completeFee()` safety net, standalone async utility, and `ErrorNervosDaoOutputLimit` error class; all 6+ scattered checks replaced | VERIFIED | `ErrorNervosDaoOutputLimit` in `ccc-dev/ccc/packages/core/src/ckb/transactionErrors.ts` with `count` and `limit` fields; `assertDaoOutputLimit` exported from `ccc-dev/ccc/packages/core/src/ckb/transaction.ts`; called at lines 2257 and 2285 in `completeFee`; called in `packages/dao/src/dao.ts` (3×), `packages/core/src/logic.ts` (1×), `packages/core/src/owned_owner.ts` (2×); `grep "outputs.length > 64" packages/` returns zero results | +| 4 | A 64-output NervosDAO limit check exists in CCC core: `completeFee()` safety net, standalone async utility, and `ErrorNervosDaoOutputLimit` error class; all 6+ scattered checks replaced | VERIFIED | `ErrorNervosDaoOutputLimit` in `ccc-fork/ccc/packages/core/src/ckb/transactionErrors.ts` with `count` and `limit` fields; `assertDaoOutputLimit` exported from `ccc-fork/ccc/packages/core/src/ckb/transaction.ts`; called at lines 2257 and 2285 in `completeFee`; called in `packages/dao/src/dao.ts` (3×), `packages/core/src/logic.ts` (1×), `packages/core/src/owned_owner.ts` (2×); `grep "outputs.length > 64" packages/` returns zero results | | 5 | ALL manager method signatures across ALL 5 library packages accept `ccc.TransactionLike` instead of `SmartTransaction`, following CCC's convention (TransactionLike input, Transaction output with `Transaction.from()` conversion at entry point) | VERIFIED | `txLike: ccc.TransactionLike` present in dao, core/logic, core/owned_owner, core/udt, order, sdk, and utils/udt; `ccc.Transaction.from(txLike)` at entry in all 15 confirmed conversion points; `return tx;` present at all method exits across dao, core, order, sdk; `addUdtHandlers` fully removed, replaced with `tx.addCellDeps(this.udtHandler.cellDeps)` at 7 sites | | 6 | `pnpm check` passes after each feature-slice removal step — no intermediate broken states | VERIFIED | All 5 plans committed atomically with individual task commits (7081869, 2decd06, 85ead3a, 2e832ae, de8f4a7); `pnpm check` passes on current state (confirmed by build execution: all 5 packages compile clean) | @@ -33,8 +33,8 @@ re_verification: false | Artifact | Expected | Status | Details | |----------|----------|--------|---------| -| `ccc-dev/ccc/packages/core/src/ckb/transactionErrors.ts` | `ErrorNervosDaoOutputLimit` error class with `count` and `limit` fields | VERIFIED | Class exists, `public readonly count: number` and `public readonly limit: number` confirmed | -| `ccc-dev/ccc/packages/core/src/ckb/transaction.ts` | `assertDaoOutputLimit` utility + `completeFee` safety net | VERIFIED | Function at line 2465, called in `completeFee` at lines 2257 and 2285 | +| `ccc-fork/ccc/packages/core/src/ckb/transactionErrors.ts` | `ErrorNervosDaoOutputLimit` error class with `count` and `limit` fields | VERIFIED | Class exists, `public readonly count: number` and `public readonly limit: number` confirmed | +| `ccc-fork/ccc/packages/core/src/ckb/transaction.ts` | `assertDaoOutputLimit` utility + `completeFee` safety net | VERIFIED | Function at line 2465, called in `completeFee` at lines 2257 and 2285 | | `packages/utils/src/utils.ts` | `TransactionHeader` type preserved; `getHeader` and `HeaderKey` absent | VERIFIED | `TransactionHeader` interface at line 19; no `getHeader` function or `HeaderKey` type found | | `packages/utils/src/index.ts` | Barrel exports without `transaction.js` or `capacity.js` | VERIFIED | Exports only `codec.js`, `heap.js`, `udt.js`, `utils.js` | | `packages/utils/src/udt.ts` | `UdtHandler` interface and `UdtManager` class with `TransactionLike` signatures | VERIFIED | Both present; all methods accept `txLike: ccc.TransactionLike` | @@ -48,7 +48,7 @@ re_verification: false | `packages/sdk/src/sdk.ts` | TransactionLike signatures + findCellsOnChain (replacing CapacityManager) | VERIFIED | 2× `txLike: ccc.TransactionLike`; `findCellsOnChain` at line 373 with `scriptLenRange` filter; `getTransactionWithHeader` with null check at line 401 | | `packages/utils/src/transaction.ts` | DELETED | VERIFIED | File does not exist | | `packages/utils/src/capacity.ts` | DELETED | VERIFIED | File does not exist | -| `ccc-dev/pins/local/001-dao-output-limit.patch` | Local patch for deterministic CCC replay | VERIFIED | File exists at expected path | +| `ccc-fork/pins/` | Local patches for deterministic CCC replay | VERIFIED | Pins directory with multi-file format (manifest + resolutions + patches) | ### Key Link Verification (from PLAN frontmatter) diff --git a/.planning/phases/02-ccc-utility-adoption/02-RESEARCH.md b/.planning/phases/02-ccc-utility-adoption/02-RESEARCH.md index 565ea7d..d266ab5 100644 --- a/.planning/phases/02-ccc-utility-adoption/02-RESEARCH.md +++ b/.planning/phases/02-ccc-utility-adoption/02-RESEARCH.md @@ -6,7 +6,7 @@ ## Summary -Phase 2 replaces five local utility functions in `@ickb/utils` (`max`, `min`, `gcd`, `isHex`, `hexFrom`) with their CCC equivalents, then deletes the local implementations. The CCC equivalents (`ccc.numMax`, `ccc.numMin`, `ccc.gcd`, `ccc.isHex`, `ccc.numToHex`, `ccc.hexFrom`) are all verified to exist in the CCC core barrel at `@ckb-ccc/core` (verified against `ccc-dev/ccc/packages/core/src/`). +Phase 2 replaces five local utility functions in `@ickb/utils` (`max`, `min`, `gcd`, `isHex`, `hexFrom`) with their CCC equivalents, then deletes the local implementations. The CCC equivalents (`ccc.numMax`, `ccc.numMin`, `ccc.gcd`, `ccc.isHex`, `ccc.numToHex`, `ccc.hexFrom`) are all verified to exist in the CCC core barrel at `@ckb-ccc/core` (verified against `ccc-fork/ccc/packages/core/src/`). The main complexity is that the replacements are not all 1:1 drop-ins. The local `max()`/`min()` is generic `` and both current call sites pass `number` (not `bigint`), while `ccc.numMax()`/`ccc.numMin()` return `bigint`. The local `hexFrom()` accepts `bigint | Entity | BytesLike`, while CCC's `hexFrom()` only accepts `HexLike` (= `BytesLike`). All external `hexFrom` call sites pass `ccc.Entity` instances, which have a `.toHex()` method that produces the same result. The `gcd` and `isHex` replacements are straightforward. Seven iCKB-unique utilities are confirmed to have no CCC equivalents and remain unchanged. @@ -48,7 +48,7 @@ No additional libraries needed. This phase only rearranges existing imports. **When to use:** Anywhere the local `hexFrom(entity)` was used with a `ccc.Entity` argument. **Example:** ```typescript -// Source: ccc-dev/ccc/packages/core/src/codec/entity.ts:135-137 +// Source: ccc-fork/ccc/packages/core/src/codec/entity.ts:135-137 // Before (local hexFrom): const key = hexFrom(cell.cellOutput.lock); @@ -61,7 +61,7 @@ const key = cell.cellOutput.lock.toHex(); **When to use:** For `gcd`, where the CCC equivalent is a direct function call. **Example:** ```typescript -// Source: ccc-dev/ccc/packages/core/src/utils/index.ts:276-285 +// Source: ccc-fork/ccc/packages/core/src/utils/index.ts:276-285 // Before: import { gcd } from "@ickb/utils"; const g = gcd(aScale, bScale); @@ -149,7 +149,7 @@ return Math.ceil(Math.log2(1 + Math.max(1, ...bins))); ## Code Examples -Verified patterns from CCC source (`ccc-dev/ccc/packages/core/src/`): +Verified patterns from CCC source (`ccc-fork/ccc/packages/core/src/`): ### numMax / numMin (from num/index.ts:30-62) ```typescript @@ -263,11 +263,11 @@ const hex2 = outPoint.toHex(); // OutPoint -> Hex ## Sources ### Primary (HIGH confidence) -- `ccc-dev/ccc/packages/core/src/num/index.ts` -- `numMax`, `numMin`, `numFrom`, `numToHex` signatures and implementations -- `ccc-dev/ccc/packages/core/src/utils/index.ts` -- `gcd` signature and implementation -- `ccc-dev/ccc/packages/core/src/hex/index.ts` -- `isHex`, `hexFrom` signatures and implementations -- `ccc-dev/ccc/packages/core/src/codec/entity.ts` -- `Entity.toHex()` method -- `ccc-dev/ccc/packages/core/src/barrel.ts` -- confirms all functions exported via CCC barrel +- `ccc-fork/ccc/packages/core/src/num/index.ts` -- `numMax`, `numMin`, `numFrom`, `numToHex` signatures and implementations +- `ccc-fork/ccc/packages/core/src/utils/index.ts` -- `gcd` signature and implementation +- `ccc-fork/ccc/packages/core/src/hex/index.ts` -- `isHex`, `hexFrom` signatures and implementations +- `ccc-fork/ccc/packages/core/src/codec/entity.ts` -- `Entity.toHex()` method +- `ccc-fork/ccc/packages/core/src/barrel.ts` -- confirms all functions exported via CCC barrel - `packages/utils/src/utils.ts` -- local implementations being replaced - All call sites verified via ripgrep across `packages/` and `apps/` diff --git a/.planning/research/ARCHITECTURE.md b/.planning/research/ARCHITECTURE.md index a0c2458..68564af 100644 --- a/.planning/research/ARCHITECTURE.md +++ b/.planning/research/ARCHITECTURE.md @@ -137,7 +137,7 @@ Target: All manager methods accept `ccc.TransactionLike` instead of `SmartTransa |---|---| | `udtHandlers` map + `addUdtHandlers()` | CCC `Udt.completeBy()` / `Udt.completeInputsByBalance()` called at transaction completion time | | `completeFee()` override that calls UDT handlers | App-level orchestration: call `ickbUdt.completeBy(tx, signer)` then `tx.completeFeeBy(signer)` | -| `getInputsUdtBalance()`/`getOutputsUdtBalance()` overrides | `IckbUdt.getInputsInfo(client, tx)` / `IckbUdt.getOutputsInfo(client, tx)` | +| `getInputsUdtBalance()`/`getOutputsUdtBalance()` overrides | `ickbUdt.getInputsInfo(client, tx)` / `ickbUdt.getOutputsInfo(client, tx)` (delegating to overridden `infoFrom()`) | | `getInputsCapacity()` DAO profit override | Not needed -- CCC's `Transaction.getInputsCapacity()` handles DAO profit natively via `getInputsCapacityExtra()` -> `Cell.getDaoProfit()` | | `headers` map + `addHeaders()` + `getHeader()` | Removed entirely. `getHeader()` call sites inline CCC client calls (`client.getTransactionWithHeader()`, `client.getHeaderByNumber()`). `addHeaders()` call sites push to `tx.headerDeps` directly. CCC's Client Cache handles caching transparently | | `addCellDeps()` deduplication | `tx.addCellDeps()` (already on `ccc.Transaction`) | @@ -159,9 +159,9 @@ iCKB's triple-representation value requires custom balance calculation to accoun 2. Receipt cells (valued as `depositQuantity * ickbValue(depositAmount, header)`) 3. iCKB deposit cells consumed as inputs (negative iCKB value: `-ickbValue(cell.capacityFree, header)`) -**Recommended approach: Subclass `Udt` as `IckbUdt`, overriding `getInputsInfo()` and `getOutputsInfo()`.** +**Recommended approach: Subclass `Udt` as `IckbUdt`, overriding `infoFrom()`.** -The natural override point would be `infoFrom()` (called by all balance/info methods), but `infoFrom()` receives `CellAnyLike` which lacks `outPoint` -- needed to fetch the deposit header via transaction hash for receipt and deposit value calculation. Instead, `getInputsInfo()` and `getOutputsInfo()` receive the full transaction, so input outpoints are available for header lookups. +The `infoFrom()` method is called by both `getInputsInfo()` and `getOutputsInfo()`, so overriding it propagates to all balance/info methods. Input cells (from `getInputsInfo` → `CellInput.getCell()`) always have `outPoint` set, enabling header fetches for receipt/deposit value calculation. Output cells (from `getOutputsInfo` → `tx.outputCells`) lack `outPoint`, allowing `infoFrom` to distinguish inputs from outputs. `CellAny` has `capacityFree` for deposit cell valuation. ```typescript // packages/core/src/udt.ts -- refactored @@ -178,28 +178,18 @@ export class IckbUdt extends udt.Udt { super(code, script, config); } - /** - * Override getInputsInfo to account for iCKB's three value representations: - * 1. xUDT cells (standard 16-byte balance) - * 2. Receipt cells (type = logicScript, balance = depositQuantity * ickbValue) - * 3. Deposit cells being withdrawn (lock = logicScript + DAO deposit, - * negative balance = -ickbValue) - * - * Uses getInputsInfo (not infoFrom) because receipt/deposit value calculation - * requires the cell's outPoint for header fetching, which CellAnyLike lacks. - */ - override async getInputsInfo( + override async infoFrom( client: ccc.Client, - txLike: ccc.TransactionLike, + cells: ccc.CellAnyLike | ccc.CellAnyLike[], + acc?: udt.UdtInfoLike, ): Promise { - const tx = ccc.Transaction.from(txLike); - const info = udt.UdtInfo.default(); + const info = udt.UdtInfo.from(acc).clone(); - for (const input of tx.inputs) { - const cell = await input.getCell(client); - if (!cell) continue; + for (const cellLike of [cells].flat()) { + const cell = ccc.CellAny.from(cellLike); + const { type, lock } = cell.cellOutput; - // Standard xUDT + // Standard xUDT cell -- delegate to base class logic if (this.isUdt(cell)) { info.addAssign({ balance: udt.Udt.balanceFromUnsafe(cell.outputData), @@ -209,23 +199,46 @@ export class IckbUdt extends udt.Udt { continue; } - // iCKB Receipt - if (cell.cellOutput.type?.eq(this.logicScript)) { - const header = await client.getHeaderByTxHash(input.previousOutput.txHash); - const { depositQuantity, depositAmount } = ReceiptData.decode(cell.outputData); + // Only input cells (with outPoint) can be receipt/deposit cells + if (!cell.outPoint) { + continue; + } + + // Receipt cell: type === logicScript + if (type?.eq(this.logicScript)) { + const txWithHeader = await client.getTransactionWithHeader( + cell.outPoint.txHash, + ); + if (!txWithHeader?.header) { + throw new Error("Header not found for receipt cell"); + } + const { depositQuantity, depositAmount } = + ReceiptData.decode(cell.outputData); info.addAssign({ - balance: ickbValue(depositAmount, header) * BigInt(depositQuantity), + balance: ickbValue(depositAmount, txWithHeader.header) * depositQuantity, capacity: cell.cellOutput.capacity, count: 1, }); continue; } - // iCKB Deposit being withdrawn (negative UDT balance) - if (cell.cellOutput.lock.eq(this.logicScript) && this.daoManager.isDeposit(cell)) { - const header = await client.getHeaderByTxHash(input.previousOutput.txHash); + // Deposit cell: lock === logicScript && isDeposit + if (lock.eq(this.logicScript)) { + const fullCell = ccc.Cell.from({ + outPoint: cell.outPoint, cellOutput: cell.cellOutput, outputData: cell.outputData, + }); + if (!this.daoManager.isDeposit(fullCell)) { + continue; + } + const txWithHeader = await client.getTransactionWithHeader( + cell.outPoint.txHash, + ); + if (!txWithHeader?.header) { + throw new Error("Header not found for deposit cell"); + } + // Deposits SUBTRACT from iCKB balance (conservation law) info.addAssign({ - balance: -ickbValue(cell.capacityFree, header), + balance: -ickbValue(cell.capacityFree, txWithHeader.header), capacity: cell.cellOutput.capacity, count: 1, }); @@ -239,12 +252,10 @@ export class IckbUdt extends udt.Udt { ``` This approach works because: -- CCC's `Udt.completeInputsByBalance()` calls `this.getInputsInfo()` -- overriding it changes balancing behavior -- CCC's `Udt.getBalanceBurned()` delegates to `getInputsBalance()` - `getOutputsBalance()` which call `getInputsInfo()`/`getOutputsInfo()` -- so the conservation law check naturally accounts for all three representations +- CCC's `Udt.completeInputsByBalance()` chains through `getInputsInfo()` → `infoFrom()` -- overriding `infoFrom` changes balancing behavior without duplicating cell resolution logic +- CCC's `Udt.getBalanceBurned()` delegates to `getInputsBalance()` - `getOutputsBalance()` which chain through `infoFrom()` -- so the conservation law check naturally accounts for all three representations - The `completeBy()` and `completeChangeToLock()` methods automatically work with the overridden balance calculation -- Input outpoints are available in `tx.inputs`, enabling header fetching for receipt/deposit value calculation - -**Note:** This is a preliminary design. The viability of subclassing CCC's `Udt` is an open question to be resolved during Phase 3 (CCC Udt Integration Investigation). See Pitfall 2 in PITFALLS.md for the risks involved. +- Input cells have `outPoint` set (resolved via `CellInput.getCell(client)` in `getInputsInfo`), enabling header fetching for receipt/deposit value calculation within `infoFrom` **3. DAO Capacity Calculation** @@ -382,77 +393,17 @@ await signer.sendTransaction(completedTx); ### Pattern 2: IckbUdt Subclass with Overridden Balance Calculation -**What:** `IckbUdt extends udt.Udt` overriding `getInputsInfo()` and `getOutputsInfo()` to account for the triple-representation value model. +**What:** `IckbUdt extends udt.Udt` overriding `infoFrom()` to account for the triple-representation value model. **When to use:** Whenever iCKB UDT balancing is needed (order creation, deposit completion, any tx involving iCKB tokens). **Trade-offs:** - Pro: All CCC Udt methods (completeBy, completeInputsByBalance, getBalanceBurned) automatically work with iCKB's special value model - Pro: Consistent with CCC ecosystem patterns -- other projects can adopt the same pattern -- Con: Requires header fetching inside getInputsInfo(), which adds async complexity +- Con: Requires header fetching inside infoFrom(), which adds async complexity - Con: `IckbUdt` needs references to `logicScript` and `daoManager` for cell type detection -**Example:** -```typescript -export class IckbUdt extends udt.Udt { - constructor( - code: ccc.OutPointLike, - script: ccc.ScriptLike, - public readonly logicScript: ccc.Script, - public readonly daoManager: DaoManager, - ) { - super(code, script); - } - - override async getInputsInfo( - client: ccc.Client, - txLike: ccc.TransactionLike, - ): Promise { - const tx = ccc.Transaction.from(txLike); - const info = udt.UdtInfo.default(); - - for (const input of tx.inputs) { - const cell = await input.getCell(client); - if (!cell) continue; - - // Standard xUDT - if (this.isUdt(cell)) { - info.addAssign({ - balance: udt.Udt.balanceFromUnsafe(cell.outputData), - capacity: cell.cellOutput.capacity, - count: 1, - }); - continue; - } - - // iCKB Receipt - if (cell.cellOutput.type?.eq(this.logicScript)) { - const header = await client.getHeaderByTxHash(input.previousOutput.txHash); - const { depositQuantity, depositAmount } = ReceiptData.decode(cell.outputData); - info.addAssign({ - balance: ickbValue(depositAmount, header) * BigInt(depositQuantity), - capacity: cell.cellOutput.capacity, - count: 1, - }); - continue; - } - - // iCKB Deposit being withdrawn (negative UDT balance) - if (cell.cellOutput.lock.eq(this.logicScript) && this.daoManager.isDeposit(cell)) { - const header = await client.getHeaderByTxHash(input.previousOutput.txHash); - info.addAssign({ - balance: -ickbValue(cell.capacityFree, header), - capacity: cell.cellOutput.capacity, - count: 1, - }); - continue; - } - } - - return info; - } -} -``` +**Example:** See the `IckbUdt` code example in the "CCC Udt Adoption for iCKB" section above. ### Pattern 3: Explicit Transaction Completion Pipeline @@ -508,7 +459,7 @@ export async function completeIckbTransaction( - The headers map creates shared mutable state between cloned transactions - Header dependencies (`headerDeps`) are already tracked by `ccc.Transaction` -**Do this instead:** Use `client.getHeaderByTxHash()` or `client.getHeaderByNumber()` and rely on CCC's client-side caching. For transaction-specific header operations (like DAO profit calculation), pass headers explicitly. +**Do this instead:** Use `client.getTransactionWithHeader()` or `client.getHeaderByNumber()` and rely on CCC's client-side caching. For transaction-specific header operations (like DAO profit calculation), pass headers explicitly. ### Anti-Pattern 3: Generic UdtHandler Interface in Utils @@ -581,8 +532,8 @@ The dependency graph still applies to the order of operations within each featur ## Sources -- CCC `@ckb-ccc/udt` source code: `/workspaces/stack/ccc-dev/ccc/packages/udt/src/udt/index.ts` (HIGH confidence -- direct code examination) -- CCC `@ckb-ccc/core` Transaction class: `/workspaces/stack/ccc-dev/ccc/packages/core/src/ckb/transaction.ts` (HIGH confidence -- direct code examination) +- CCC `@ckb-ccc/udt` source code: `/workspaces/stack/ccc-fork/ccc/packages/udt/src/udt/index.ts` (HIGH confidence -- direct code examination) +- CCC `@ckb-ccc/core` Transaction class: `/workspaces/stack/ccc-fork/ccc/packages/core/src/ckb/transaction.ts` (HIGH confidence -- direct code examination) - Current SmartTransaction: `/workspaces/stack/packages/utils/src/transaction.ts` (HIGH confidence -- direct code examination) - Current IckbUdtManager: `/workspaces/stack/packages/core/src/udt.ts` (HIGH confidence -- direct code examination) - Current UdtManager/UdtHandler: `/workspaces/stack/packages/utils/src/udt.ts` (HIGH confidence -- direct code examination) diff --git a/.planning/research/FEATURES.md b/.planning/research/FEATURES.md index 6f3ba5a..63030f0 100644 --- a/.planning/research/FEATURES.md +++ b/.planning/research/FEATURES.md @@ -56,7 +56,7 @@ Features that seem good but create problems in this context. Explicitly document | **AF-6: Embedded wallet/signer management** | Some SDKs bundle wallet management (key storage, mnemonic handling) | CCC already provides comprehensive signer abstraction (`ccc.Signer`, `ccc.SignerCkbPrivateKey`, JoyId integration). Duplicating this creates security liability. | Delegate all signing to CCC's signer infrastructure. The SDK accepts `ccc.Signer` or `ccc.Script` -- it never manages keys. | | **AF-7: Database/state persistence layer** | Bot and interface could benefit from persistent state (order history, balance cache) | All state is on-chain. Adding a database creates consistency problems (stale state vs chain state). The current stateless design is a feature, not a limitation. | Continue reading all state from L1 via CCC client. Pool snapshots (D-4) provide efficient state approximation without a database. | | **AF-8: New reference/example apps** | More apps might help adoption | Existing 5 apps (bot, interface, faucet, sampler, tester) already demonstrate all library capabilities. Adding more dilutes maintenance focus. | Polish existing apps. They serve as living documentation. | -| **AF-9: CCC framework fork** | Tempting to fork CCC to get features faster | Forking creates maintenance burden and diverges from ecosystem. Upstream PRs are the correct approach. | Submit PRs upstream (already doing this with 12 merged). Track CCC PR #328 (FeePayer). Use `ccc-dev/` local build for testing changes before they land upstream. | +| **AF-9: CCC framework fork** | Tempting to fork CCC to get features faster | Forking creates maintenance burden and diverges from ecosystem. Upstream PRs are the correct approach. | Submit PRs upstream (already doing this with 12 merged). Track CCC PR #328 (FeePayer). Use `ccc-fork/` local build for testing changes before they land upstream. | | **AF-10: On-chain contract changes** | Protocol improvements seem natural alongside library work | All contracts are deployed with zero-args locks (immutable, non-upgradable). Even if desirable, contract changes are impossible. | Library must match existing on-chain contract behavior exactly. All protocol rules are fixed. | ## Feature Dependencies diff --git a/.planning/research/PITFALLS.md b/.planning/research/PITFALLS.md index 3581de5..c91455f 100644 --- a/.planning/research/PITFALLS.md +++ b/.planning/research/PITFALLS.md @@ -41,7 +41,7 @@ Concretely, `IckbUdtManager.getInputsUdtBalance` currently (a) counts xUDT balan The temptation is to make `IckbUdt extends Udt` so that CCC's generic transaction completion (`completeBy`, `completeInputsByBalance`) "just works" for iCKB. But iCKB's multi-representation value model is fundamentally incompatible with the assumption that UDT balance = sum of `u128 LE` fields in matching type cells. The CCC `Udt` class was designed for standard xUDT tokens, not for protocol-specific tokens with conservation laws spanning multiple cell types. **How to avoid:** -1. The preferred approach (see ARCHITECTURE.md and STACK.md) is to subclass `Udt` as `IckbUdt`, overriding `getInputsInfo()`/`getOutputsInfo()` rather than `infoFrom()`. This avoids the `CellAnyLike` limitation (no outPoint for header fetching) while keeping CCC's completion methods functional. However, this is an **open design question** to be resolved during Phase 3 (CCC Udt Integration Investigation). +1. The preferred approach (confirmed in Phase 3 research) is to subclass `Udt` as `IckbUdt`, overriding `infoFrom()`. Input cells have `outPoint` set (resolved via `CellInput.getCell()`), enabling header fetches for receipt/deposit value calculation. `CellAny` has `capacityFree` for deposit valuation. See 03-RESEARCH.md for the corrected design. 2. If subclassing proves unviable, the fallback is to keep multi-representation accounting in iCKB-specific standalone functions (refactored from `IckbUdtManager`), using CCC's `Udt` only for standard xUDT operations (cell discovery, basic balance reading). 3. Whichever approach is chosen, ensure that CCC's `Udt.completeInputsByBalance()` does not inadvertently add receipt or deposit cells as if they were standard xUDT inputs. Verify that the conservation law (`input_udt + input_receipts = output_udt + input_deposits`) is enforced correctly by the overridden methods. 4. Always add required `headerDeps` explicitly -- CCC's client cache handles header fetching performance, but `headerDeps` must be on the transaction for on-chain validation. @@ -176,7 +176,7 @@ Shortcuts that seem reasonable but create long-term problems. | Keeping SmartTransaction "just for now" while migrating apps | Apps work immediately without library changes | Two transaction models coexist, every new feature must work with both, CCC upgrades become harder | Never -- library refactor must come before app migration | | Passing `SmartTransaction` type through public API boundaries | Avoids rewriting callers | External consumers inherit a dependency on a non-standard Transaction subclass, blocking npm publication | Never for published packages -- internal-only is acceptable during transition | | Skipping codec roundtrip tests | Faster initial development | Silent byte-level bugs that only manifest on-chain | Never -- these tests are cheap to write and prevent catastrophic failures | -| Duplicating CCC utility functions locally instead of adopting upstream | Avoids dependency on specific CCC version | Drift between local and upstream implementations, double maintenance burden | Only if CCC version is not yet released (use `ccc-dev/` local builds to validate, then switch to published version) | +| Duplicating CCC utility functions locally instead of adopting upstream | Avoids dependency on specific CCC version | Drift between local and upstream implementations, double maintenance burden | Only if CCC version is not yet released (use `ccc-fork/` local builds to validate, then switch to published version) | | Migrating bot without parallel Lumos fallback | Cleaner codebase, single transaction path | If CCC-based bot has subtle bugs, no way to fall back; real funds at risk | Never for mainnet -- always keep Lumos bot runnable until CCC bot is validated on testnet | | Removing `@ickb/lumos-utils` and `@ickb/v1-core` from workspace before all apps are migrated | Simpler dependency tree | Breaks unmigrated apps, blocks incremental migration | Only after ALL apps are migrated and verified | @@ -272,7 +272,7 @@ How roadmap phases should address these pitfalls. ## Sources - Direct codebase analysis: `packages/utils/src/transaction.ts` (SmartTransaction, 517 lines), `packages/utils/src/udt.ts` (UdtManager, 393 lines), `packages/core/src/udt.ts` (IckbUdtManager, 213 lines) -- CCC `Udt` class source: `ccc-dev/ccc/packages/udt/src/udt/index.ts` (1798 lines) +- CCC `Udt` class source: `ccc-fork/ccc/packages/udt/src/udt/index.ts` (1798 lines) - On-chain contract source: `reference/contracts/scripts/contracts/ickb_logic/src/entry.rs` (conservation law, exchange rate) - On-chain contract source: `reference/contracts/scripts/contracts/owned_owner/` (owner/owned pairing) - On-chain contract source: `reference/contracts/scripts/contracts/limit_order/` (order/master relationship) diff --git a/.planning/research/STACK.md b/.planning/research/STACK.md index bc89b8d..d70e6df 100644 --- a/.planning/research/STACK.md +++ b/.planning/research/STACK.md @@ -2,7 +2,7 @@ **Domain:** CCC API adoption for iCKB protocol library migration **Researched:** 2026-02-21 -**Confidence:** HIGH (primary source: local CCC source code in `ccc-dev/ccc/`) +**Confidence:** HIGH (primary source: local CCC source code in `ccc-fork/ccc/`) ## Context @@ -67,7 +67,7 @@ This research focuses on the CCC APIs and patterns that should be adopted as par ### CCC `@ckb-ccc/udt` Package -**Version:** Local build from `ccc-dev/ccc/packages/udt/` +**Version:** Local build from `ccc-fork/ccc/packages/udt/` **Key classes:** `Udt`, `UdtInfo`, `UdtConfig`, `ErrorUdtInsufficientCoin` **Depends on:** `@ckb-ccc/core`, `@ckb-ccc/ssri` @@ -111,7 +111,7 @@ CCC's `Client.cache` handles purpose (1) -- all `getHeaderByHash()` and `getHead CCC's `Transaction.getInputsCapacity()` now includes DAO profit via `CellInput.getExtraCapacity()` -> `Cell.getDaoProfit()`. This means SmartTransaction's override of `getInputsCapacity()` is **no longer needed** -- CCC does this natively. -Verified in CCC source (`ccc-dev/ccc/packages/core/src/ckb/transaction.ts` lines 1860-1883): +Verified in CCC source (`ccc-fork/ccc/packages/core/src/ckb/transaction.ts` lines 1860-1883): ```typescript async getInputsCapacity(client: Client): Promise { return ( @@ -140,30 +140,28 @@ Where `getInputsCapacityExtra` sums `getExtraCapacity()` per input, which calls ### Recommended Approach: Subclass `Udt` from `@ckb-ccc/udt` -Create `IckbUdt extends Udt` that overrides `getInputsInfo()` and `getOutputsInfo()` to recognize all three representations. +Create `IckbUdt extends Udt` that overrides `infoFrom()` to recognize all three representations. -**Why `getInputsInfo()`/`getOutputsInfo()` (not `infoFrom()`):** -- The natural candidate would be `infoFrom()` -- it's called by all balance/info methods, so overriding it would propagate everywhere. However, `infoFrom()` receives `CellAnyLike` which lacks `outPoint`, and iCKB receipt/deposit value calculation requires the cell's outPoint to fetch the block header via transaction hash. -- `getInputsInfo()` and `getOutputsInfo()` receive the full `TransactionLike`, so input outpoints are available for header lookups. This is cleaner than trying to thread header information through `infoFrom()`. -- CCC's `completeInputsByBalance()` calls `this.getInputsInfo()`, so overriding it changes balancing behavior correctly. +**Why `infoFrom()` (updated in Phase 3 research):** +- `infoFrom()` is called by all balance/info methods, so overriding it propagates everywhere. Input cells passed via `getInputsInfo()` → `CellInput.getCell()` always have `outPoint` set on the `CellAny`/`Cell` objects, enabling header fetches for receipt/deposit value calculation. Output cells from `tx.outputCells` lack `outPoint`, allowing `infoFrom` to distinguish inputs from outputs. +- `CellAny` has `capacityFree` (transaction.ts:404-405), so deposit cell valuation works directly. Only `DaoManager.isDeposit()` requires constructing a `Cell` from `CellAny`. +- CCC's `completeInputsByBalance()` chains through `getInputsInfo()` → `infoFrom()`, so overriding `infoFrom` changes balancing behavior correctly without duplicating resolution logic. **Why NOT override `completeInputsByBalance()`:** - The base implementation's dual-constraint logic (balance + capacity) is correct for iCKB - The subclass only needs to change HOW balance is calculated from cells, not the input selection strategy -**Implementation sketch:** See ARCHITECTURE.md "Pattern 2: IckbUdt Subclass" for the full code example with `getInputsInfo()` override. +**Implementation sketch:** See 03-RESEARCH.md for the `infoFrom()` override pattern. ARCHITECTURE.md "CCC Udt Adoption for iCKB" section has the same corrected example. -**Note:** This is a preliminary design. The viability of subclassing CCC's `Udt` is an open question to be resolved during Phase 3 (CCC Udt Integration Investigation). See Pitfall 2 in PITFALLS.md for the risks involved. +**Header fetching within `infoFrom()` override:** -**Header fetching within `getInputsInfo()`:** +Since input cells have `outPoint` set (resolved via `CellInput.getCell(client)` in `getInputsInfo`), the `infoFrom` override can fetch headers using: -Since `getInputsInfo()` receives the full transaction, input outpoints are directly available. The implementation can fetch headers using: - -1. **`client.getHeaderByTxHash(input.previousOutput.txHash)`** -- Fetches the block header for the transaction that created the cell. Cached by CCC Client. This is the approach used in the ARCHITECTURE.md code sketch. +1. **`client.getTransactionWithHeader(cell.outPoint.txHash)`** -- Fetches the block header for the transaction that created the cell. Cached by CCC Client. Returns `{ transaction, header? }`. 2. **`client.getTransaction()` + `client.getHeaderByHash()`** -- Alternative two-step approach: get the transaction response (which includes `blockHash`), then fetch the header. Both are cached by CCC Client. -Option 1 is simpler and sufficient for the `getInputsInfo()` override. +Option 1 is simpler and sufficient for the `infoFrom()` override. ## CCC Transaction Completion Pattern @@ -277,11 +275,11 @@ Both are now redundant: PR #328 proposes a `FeePayer` abstraction for CCC that would allow specifying who pays transaction fees. This is relevant because SmartTransaction's fee completion could designate a specific lock for fee payment. -**Current status:** Still open (not merged as of research date). +**Current status (updated):** PR #328 is now integrated into `ccc-fork/ccc` via the pins/record system. FeePayer classes are available at `ccc-fork/ccc/packages/core/src/signer/feePayer/`. The user decided during Phase 3 context that PR #328 is the target architecture -- investigation should design around it. -**Impact on migration:** Do NOT wait for PR #328. CCC's existing `completeFeeChangeToLock(signer, changeLock)` already allows specifying a change destination. The FeePayer abstraction would be a further convenience but is not blocking. +**Impact on migration:** The FeePayer abstraction is available to build against directly. The `infoFrom()` override is compatible with both the current Signer-based completion and the FeePayer-based completion -- cells flow through `getInputsInfo` → `infoFrom` regardless of which completion plumbing is used. -**Recommendation:** Proceed with `completeFeeChangeToLock()` / `completeFeeBy()`. If PR #328 merges later, it can be adopted as an incremental improvement. +**Recommendation:** Design around FeePayer as the target architecture. Use `completeFeeChangeToLock()` / `completeFeeBy()` for current execution while investigating how FeePayer's `completeInputs(tx, filter, accumulator, init)` pattern can improve iCKB's receipt/deposit cell discovery. ## Version Compatibility @@ -308,17 +306,17 @@ PR #328 proposes a `FeePayer` abstraction for CCC that would allow specifying wh # No other new dependencies needed -- all other changes use existing @ckb-ccc/core APIs ``` -**Note:** With `ccc-dev/` local build active, `.pnpmfile.cjs` automatically rewires all `@ckb-ccc/*` dependencies to local packages, so the `@ckb-ccc/udt` package is already available from the local CCC build. +**Note:** With `ccc-fork/` local build active, `.pnpmfile.cjs` automatically rewires all `@ckb-ccc/*` dependencies to local packages, so the `@ckb-ccc/udt` package is already available from the local CCC build. ## Sources -- `ccc-dev/ccc/packages/udt/src/udt/index.ts` -- CCC Udt class, full source (1798 lines) -- HIGH confidence -- `ccc-dev/ccc/packages/core/src/ckb/transaction.ts` -- CCC Transaction class, full source (2537 lines) -- HIGH confidence -- `ccc-dev/ccc/packages/core/src/client/client.ts` -- CCC Client class with caching, cell finding -- HIGH confidence -- `ccc-dev/ccc/packages/core/src/num/index.ts` -- `numMax`, `numMin`, `numFrom` etc. -- HIGH confidence -- `ccc-dev/ccc/packages/core/src/hex/index.ts` -- `isHex`, `hexFrom`, `bytesLen` -- HIGH confidence -- `ccc-dev/ccc/packages/core/src/utils/index.ts` -- `reduce`, `reduceAsync`, `gcd`, `apply`, `sleep` -- HIGH confidence -- `ccc-dev/ccc/packages/core/src/ckb/epoch.ts` -- `Epoch` class (already adopted) -- HIGH confidence +- `ccc-fork/ccc/packages/udt/src/udt/index.ts` -- CCC Udt class, full source (1798 lines) -- HIGH confidence +- `ccc-fork/ccc/packages/core/src/ckb/transaction.ts` -- CCC Transaction class, full source (2537 lines) -- HIGH confidence +- `ccc-fork/ccc/packages/core/src/client/client.ts` -- CCC Client class with caching, cell finding -- HIGH confidence +- `ccc-fork/ccc/packages/core/src/num/index.ts` -- `numMax`, `numMin`, `numFrom` etc. -- HIGH confidence +- `ccc-fork/ccc/packages/core/src/hex/index.ts` -- `isHex`, `hexFrom`, `bytesLen` -- HIGH confidence +- `ccc-fork/ccc/packages/core/src/utils/index.ts` -- `reduce`, `reduceAsync`, `gcd`, `apply`, `sleep` -- HIGH confidence +- `ccc-fork/ccc/packages/core/src/ckb/epoch.ts` -- `Epoch` class (already adopted) -- HIGH confidence - `packages/utils/src/transaction.ts` -- Current SmartTransaction implementation (517 lines) -- HIGH confidence - `packages/utils/src/udt.ts` -- Current UdtManager/UdtHandler implementation (393 lines) -- HIGH confidence - `packages/utils/src/capacity.ts` -- Current CapacityManager implementation (221 lines) -- HIGH confidence diff --git a/.planning/research/SUMMARY.md b/.planning/research/SUMMARY.md index 8b1465e..3b76acf 100644 --- a/.planning/research/SUMMARY.md +++ b/.planning/research/SUMMARY.md @@ -17,11 +17,11 @@ The primary risk is losing implicit behaviors baked into `SmartTransaction` — ### Recommended Stack -The existing TypeScript/pnpm/CCC stack requires no new technology choices. The migration is a CCC API adoption exercise: replace 14 local utilities with CCC equivalents (`ccc.numMax`/`numMin`, `ccc.gcd`, `ccc.isHex`, `Udt.balanceFromUnsafe`, etc.), add `@ckb-ccc/udt` as a dependency to `@ickb/core`, and restructure transaction building around CCC's native completion pipeline. The local `ccc-dev/` build system already makes `@ckb-ccc/udt` available via `.pnpmfile.cjs` rewriting — no additional infrastructure work needed. +The existing TypeScript/pnpm/CCC stack requires no new technology choices. The migration is a CCC API adoption exercise: replace 14 local utilities with CCC equivalents (`ccc.numMax`/`numMin`, `ccc.gcd`, `ccc.isHex`, `Udt.balanceFromUnsafe`, etc.), add `@ckb-ccc/udt` as a dependency to `@ickb/core`, and restructure transaction building around CCC's native completion pipeline. The local `ccc-fork/` build system already makes `@ckb-ccc/udt` available via `.pnpmfile.cjs` rewriting — no additional infrastructure work needed. **Core technologies:** - `@ckb-ccc/core` ^1.12.2: Transaction building, cell queries, signer abstraction — already adopted, native replacement for all SmartTransaction behaviors -- `@ckb-ccc/udt` (local ccc-dev build): UDT lifecycle management (cell finding, balance calculation, input completion, change handling) — replaces local UdtManager/UdtHandler; `IckbUdt` subclasses this +- `@ckb-ccc/udt` (local ccc-fork build): UDT lifecycle management (cell finding, balance calculation, input completion, change handling) — replaces local UdtManager/UdtHandler; `IckbUdt` subclasses this - `ccc.Transaction.completeFeeBy` / `completeFeeChangeToLock`: CKB fee completion — direct SmartTransaction.completeFee replacement for the CKB-change portion - `ccc.Transaction.completeInputsByCapacity`: CKB capacity input collection — replaces CapacityManager's cell-finding role - `ccc.Client.cache`: Transparent header caching — replaces SmartTransaction's `headers` map for performance; header deps must still be added explicitly @@ -63,7 +63,7 @@ The architecture shifts from a God-object transaction (SmartTransaction carrying **Major components:** 1. `@ickb/utils` — async data utilities (`collect`, `unique`, `binarySearch`, `MinHeap`), codec utilities; NO SmartTransaction, NO UdtHandler, NO UdtManager, NO CapacityManager, NO `getHeader()`/`HeaderKey` after refactor 2. `@ickb/dao` + `@ickb/order` — domain managers operating on plain `ccc.Transaction`; add cell deps directly; no UDT awareness -3. `@ickb/core` — `IckbUdt extends udt.Udt` overriding `getInputsInfo()` and `getOutputsInfo()` for triple-representation balance; `LogicManager` and `OwnedOwnerManager` for iCKB protocol operations +3. `@ickb/core` — `IckbUdt extends udt.Udt` overriding `infoFrom()` for triple-representation balance; `LogicManager` and `OwnedOwnerManager` for iCKB protocol operations 4. `@ickb/sdk` — `IckbSdk` facade orchestrating all managers; explicit completion pipeline: `ickbUdt.completeBy(tx, signer)` then `tx.completeFeeBy(signer)` 5. Apps (bot, interface, tester) — consume SDK; no direct manager usage for most operations @@ -76,14 +76,14 @@ await completedTx.completeFeeBy(signer); // CKB capacity + f await signer.sendTransaction(completedTx); ``` -**Key architectural decision — override `getInputsInfo()` not `infoFrom()`:** -`getInputsInfo()` receives the full transaction with input outpoints, enabling header fetching for receipt and deposit cells. `infoFrom()` receives only cells without outpoints and cannot reach the block headers needed for iCKB value calculation. +**Key architectural decision — override `infoFrom()` (corrected in Phase 3 research):** +`infoFrom()` receives `CellAnyLike` objects — input cells (from `getInputsInfo` → `CellInput.getCell()`) always have `outPoint` set, enabling header fetches for receipt/deposit value calculation. Output cells lack `outPoint`, allowing `infoFrom` to distinguish inputs from outputs. See 03-RESEARCH.md for the corrected design. ### Critical Pitfalls 1. **SmartTransaction implicit behaviors lost during removal** — `completeFee` silently iterates all UDT handlers, `getInputsCapacity` adds DAO withdrawal profit, `clone()` shares handler/header maps. A mechanical find-and-replace misses all three. Avoid by: cataloging every SmartTransaction-specific method, writing characterization tests before removing anything, and designing the replacement as explicit utility functions rather than a companion object. -2. **Incorrect CCC Udt subclassing for multi-representation value** — CCC's `Udt.completeInputsByBalance` assumes UDT balance = sum of `u128 LE` fields in matching type cells. iCKB's conservation law spans xUDT cells, receipt cells, and DAO deposit cells. Avoid by: using `IckbUdt` only to override `getInputsInfo()`/`getOutputsInfo()` (which have full transaction context), NOT overriding `infoFrom()` or `balanceFrom()` for iCKB-specific representations. +2. **Incorrect CCC Udt subclassing for multi-representation value** — CCC's `Udt.completeInputsByBalance` assumes UDT balance = sum of `u128 LE` fields in matching type cells. iCKB's conservation law spans xUDT cells, receipt cells, and DAO deposit cells. Avoid by: overriding `infoFrom()` in `IckbUdt` to value all three cell types with correct sign conventions (Phase 3 research confirmed `CellAnyLike` has `outPoint` for input cells, enabling header fetches). Do NOT override `balanceFrom()` for iCKB-specific representations. 3. **Exchange rate divergence between TypeScript and Rust contract** — The `ickbValue()` formula must produce byte-identical results to the on-chain `ickb_logic` script. Integer division order and the soft-cap formula are dangerous. Avoid by: creating cross-validation tests with known Rust contract outputs BEFORE touching any exchange rate code. @@ -144,7 +144,7 @@ Based on research, suggested phase structure: ### Research Flags Needs deeper research during planning: -- **Phase 1d (IckbUdt subclassing):** Confirm that CCC's `Udt.getInputsInfo()` signature provides enough context for header fetching for receipt/deposit cells. Also confirm that overriding `getInputsInfo()` (rather than `infoFrom()`) does not break any CCC internal method chains. +- **Phase 1d (IckbUdt subclassing):** **Resolved in Phase 3 research.** `infoFrom()` is the preferred override point — input cells have `outPoint` for header fetching, `CellAny` has `capacityFree`. See 03-RESEARCH.md. - **Phase 3 (JoyId wallet connector):** Manual testing with actual JoyId hardware required; CCC's wallet connector API differs from Lumos in ways that affect UX flow. Standard patterns (skip research-phase): @@ -156,27 +156,27 @@ Standard patterns (skip research-phase): | Area | Confidence | Notes | |------|------------|-------| -| Stack | HIGH | Primary source is local CCC source code (`ccc-dev/ccc/`); all APIs verified by direct inspection | +| Stack | HIGH | Primary source is local CCC source code (`ccc-fork/ccc/`); all APIs verified by direct inspection | | Features | HIGH | Based on direct codebase analysis + CCC docs + npm ecosystem survey; competitor analysis confirms iCKB has no direct competitors | -| Architecture | HIGH | Build order derived from package dependency graph; key patterns verified against CCC Udt source; override point resolved (`getInputsInfo`/`getOutputsInfo`, not `infoFrom`) | +| Architecture | HIGH | Build order derived from package dependency graph; key patterns verified against CCC Udt source; override point resolved (`infoFrom`, not `getInputsInfo`/`getOutputsInfo` — see Phase 3 research) | | Pitfalls | HIGH | Derived from direct code reading (SmartTransaction 517 lines, IckbUdtManager 213 lines, CCC Udt 1798 lines) and on-chain contract constraints | **Overall confidence:** HIGH ### Gaps to Address -- **Resolved — CCC Udt override point:** Both ARCHITECTURE.md and STACK.md now agree on overriding `getInputsInfo()`/`getOutputsInfo()` (not `infoFrom()`). The `infoFrom()` approach was ruled out because `CellAnyLike` lacks `outPoint`, which is needed for header fetching in receipt/deposit value calculation. Final confirmation of CCC's internal method chains still needed during Phase 3 (CCC Udt Integration Investigation). +- **Resolved — CCC Udt override point:** Phase 3 research (03-RESEARCH.md) determined that `infoFrom()` is the optimal override point. The earlier recommendation to override `getInputsInfo()`/`getOutputsInfo()` was based on the incorrect premise that `CellAnyLike` lacks `outPoint` — it actually has `outPoint?: OutPointLike | null`, and input cells from `getInputsInfo()` → `CellInput.getCell()` always have `outPoint` set. `CellAny` also has `capacityFree`. See 03-RESEARCH.md for the corrected design. - **Resolved — DAO profit in CCC `getInputsCapacity`:** Verified from CCC source (transaction.ts lines 1860-1883) that `Transaction.getInputsCapacity()` handles DAO profit natively via `getInputsCapacityExtra()` → `CellInput.getExtraCapacity()` → `Cell.getDaoProfit()`. No standalone utility needed. SmartTransaction's override of `getInputsCapacity()` can be dropped without replacement. -- **CCC PR #328 (FeePayer) tracking:** Design the SmartTransaction replacement so FeePayer can be adopted as a drop-in improvement if the PR merges. Do not block on it. +- **Resolved — CCC PR #328 (FeePayer):** PR #328 is now integrated into `ccc-fork/ccc` via pins. FeePayer classes are available at `ccc-fork/ccc/packages/core/src/signer/feePayer/`. User decision during Phase 3 context: design around PR #328 as target architecture. - **Bot key logging security:** PITFALLS.md notes the faucet already has a private key logging bug. The bot migration must include an explicit security audit of all logging paths. ## Sources ### Primary (HIGH confidence) -- `ccc-dev/ccc/packages/udt/src/udt/index.ts` — CCC Udt class (1798 lines), complete UDT lifecycle API -- `ccc-dev/ccc/packages/core/src/ckb/transaction.ts` — CCC Transaction class (2537 lines), completeFee/completeInputsByCapacity/getInputsCapacity -- `ccc-dev/ccc/packages/core/src/client/client.ts` — CCC Client with cache, findCells, cell/header fetching -- `packages/utils/src/transaction.ts` — Current SmartTransaction (517 lines), source of truth for replacement requirements +- `ccc-fork/ccc/packages/udt/src/udt/index.ts` — CCC Udt class (1798 lines), complete UDT lifecycle API +- `ccc-fork/ccc/packages/core/src/ckb/transaction.ts` — CCC Transaction class (2537 lines), completeFee/completeInputsByCapacity/getInputsCapacity +- `ccc-fork/ccc/packages/core/src/client/client.ts` — CCC Client with cache, findCells, cell/header fetching +- `packages/utils/src/transaction.ts` — SmartTransaction (deleted in Phase 1), was source of truth for replacement requirements - `packages/utils/src/udt.ts` — Current UdtManager/UdtHandler (393 lines) - `packages/core/src/udt.ts` — Current IckbUdtManager (213 lines), triple-representation balance logic - `reference/contracts/schemas/encoding.mol` — Molecule schema, byte layout ground truth From 4fb9e20a8bbefe198905293d3c1ea8b279c32cc3 Mon Sep 17 00:00:00 2001 From: phroi <90913182+phroi@users.noreply.github.com> Date: Wed, 25 Feb 2026 02:08:30 +0000 Subject: [PATCH 3/3] docs: refresh codebase docs for post-phase-1 state - Update all SmartTransaction/CapacityManager references to note deletion in Phase 1 (ARCHITECTURE, CONCERNS, CONVENTIONS) - Update fork-scripts paths from ccc-fork/*.sh to fork-scripts/*.sh (PROJECT, CONCERNS, CONVENTIONS, STACK, STRUCTURE) - Add fork-scripts/ directory tree and @generated markers (STRUCTURE) - Mark resolved concerns as RESOLVED (CONCERNS) - Update code examples to current APIs: findUdts, isUdt, collect (CONVENTIONS) - Update 64-output DAO limit references to CCC core (CONCERNS) --- .planning/PROJECT.md | 2 +- .planning/codebase/ARCHITECTURE.md | 61 +++++++++-------- .planning/codebase/CONCERNS.md | 48 ++++++-------- .planning/codebase/CONVENTIONS.md | 28 ++++---- .planning/codebase/STACK.md | 43 ++++++------ .planning/codebase/STRUCTURE.md | 101 +++++++++++++++++------------ 6 files changed, 146 insertions(+), 137 deletions(-) diff --git a/.planning/PROJECT.md b/.planning/PROJECT.md index 0e1db24..0520498 100644 --- a/.planning/PROJECT.md +++ b/.planning/PROJECT.md @@ -56,7 +56,7 @@ Clean, CCC-aligned library packages published to npm that frontends can depend o **Migration status:** Library packages are on CCC. Apps split: faucet/sampler already migrated; bot/interface/tester still on legacy Lumos (`@ckb-lumos/*`, `@ickb/lumos-utils@1.4.2`, `@ickb/v1-core@1.4.2`). -**Local CCC dev build:** `ccc-dev/` supports using local CCC builds for testing. `.pnpmfile.cjs` transparently rewires `@ckb-ccc/*` to local packages. `ccc-dev/patch.sh` rewrites exports to `.ts` source. This enables testing upstream changes before they're published. +**Local CCC dev build:** `ccc-fork/` supports using local CCC builds for testing. `.pnpmfile.cjs` transparently rewires `@ckb-ccc/*` to local packages. `fork-scripts/patch.sh` rewrites exports to `.ts` source. This enables testing upstream changes before they're published. ## Constraints diff --git a/.planning/codebase/ARCHITECTURE.md b/.planning/codebase/ARCHITECTURE.md index 77efff8..2fb579e 100644 --- a/.planning/codebase/ARCHITECTURE.md +++ b/.planning/codebase/ARCHITECTURE.md @@ -11,7 +11,7 @@ - Manager-based pattern encapsulating script operations with uniform `ScriptDeps` interface - Async-first cell discovery via generator functions with lazy evaluation - TypeScript ESM modules with strict null checks and type safety -- SmartTransaction builder for automatic fee/change calculation +- CCC-native transaction building with TransactionLike pattern (SmartTransaction deleted in Phase 1) - Cell wrapper abstractions extending raw blockchain data with domain logic **Migration Status:** @@ -21,7 +21,7 @@ - Apps split: `faucet` and `sampler` already use new packages; `bot`, `tester`, `interface` still on legacy Lumos - CCC PRs for UDT and Epoch support have been MERGED upstream -- local Epoch class has been deleted (replaced by `ccc.Epoch`); some local UDT handling may still overlap with CCC's `@ckb-ccc/udt` - Custom `mol.union` codec and deprecated `mol.*` APIs have been replaced with CCC's `mol.union`, `ccc.Entity.Base`, and `@ccc.codec` decorator -- SmartTransaction was ABANDONED as an ecosystem-wide concept (no adoption), but the class itself remains used locally; headers are now cached in CCC Client Cache +- SmartTransaction was DELETED in Phase 1; all managers now accept `ccc.TransactionLike` and return `ccc.Transaction` directly; headers cached by CCC Client Cache ## Protocol Design (from whitepaper) @@ -70,21 +70,22 @@ Receipts convert to UDT; deposits stay as deposits or convert to UDT. No iCKB ca **Foundation: CCC Framework** - Purpose: Provide blockchain primitives and client interface -- Location: `@ckb-ccc/core` (npm or local `ccc-dev/ccc/`) +- Location: `@ckb-ccc/core` (npm or local `ccc-fork/ccc/`) - Contains: CKB RPC clients, transaction builders, signers, Molecule codec, UDT support, Epoch handling - Used by: All packages and applications - Note: CCC now includes UDT and Epoch features contributed by this project's maintainer **Utilities Layer (`packages/utils/src/`)** -- Purpose: Reusable blockchain primitives and transaction helpers -- Key exports: `SmartTransaction`, `CapacityManager`, codec/heap utilities, UDT handlers +- Purpose: Reusable blockchain primitives and UDT handlers +- Key exports: `UdtManager`, `UdtHandler`, codec/heap utilities - Key files: - - `transaction.ts` (517 lines): SmartTransaction builder with fee completion and UDT balancing - - `capacity.ts` (221 lines): CapacityManager for cell discovery and collection - - `udt.ts` (393 lines): UDT token value calculations and handlers - - `utils.ts` (458 lines): General utilities (binary search, collectors, script helpers) + - `udt.ts` (407 lines): UDT token value calculations and handlers + - `utils.ts` (292 lines): General utilities (binary search, collectors, helpers) + - `codec.ts` (21 lines): CheckedInt32LE codec + - `heap.ts` (175 lines): MinHeap implementation - Depends on: `@ckb-ccc/core` - Used by: All domain packages +- Note: `SmartTransaction`, `CapacityManager`, `transaction.ts`, `capacity.ts` were deleted in Phase 1 **Domain Layer - DAO (`packages/dao/src/`)** - Purpose: Abstract Nervos DAO operations (deposit, withdraw, requestWithdrawal) @@ -154,7 +155,7 @@ Receipts convert to UDT; deposits stay as deposits or convert to UDT. No iCKB ca - Location: `apps/faucet/src/main.ts` (88 lines), entry via `apps/faucet/src/index.ts` - Entry: `main()` async function - Pattern: Infinite loop with 2-minute poll interval - - Uses: CCC client, CapacityManager, SmartTransaction + - Uses: CCC client, ccc.Transaction - Flow: Discover faucet funds → transfer to user account → log JSON results **Sampler (Migrated to CCC):** @@ -198,7 +199,7 @@ Receipts convert to UDT; deposits stay as deposits or convert to UDT. No iCKB ca 1. User calls `IckbSdk.request(tx, user, info, amounts)` via app 2. `OrderManager.mint()` creates order cell with `Info` metadata (ratio, direction, fee info) -3. `SmartTransaction` adds cell deps, outputs, UDT handlers +3. Transaction adds cell deps, outputs via `tx.addCellDeps()` / `tx.addOutput()` 4. User signs and broadcasts transaction **Order Discovery and Matching (Bot Flow):** @@ -213,7 +214,7 @@ Receipts convert to UDT; deposits stay as deposits or convert to UDT. No iCKB ca 8. Bot identifies orders where CKB/UDT supply exists for matching 9. Calls `OrderManager.satisfy()` to process matched orders 10. Builds transaction with satisfied order cells and input/output witnesses -11. Completes fees via `SmartTransaction.completeFeeChangeToLock()` +11. Completes fees via `tx.completeFeeChangeToLock()` 12. Signs and broadcasts **Maturity Estimation (Supporting Calculation):** @@ -241,13 +242,13 @@ Receipts convert to UDT; deposits stay as deposits or convert to UDT. No iCKB ca 2. Receipt cell minted containing deposit metadata 3. Bot fetches deposits via `LogicManager.findDeposits()` 4. After maturity, bot calls `LogicManager.withdraw()` to extract funds -5. Change cells automatically added by SmartTransaction +5. Change cells added via CCC fee completion pipeline **State Management:** - L1 state: Immutable snapshots fetched per query with configurable refresh intervals -- Transaction state: Built incrementally via `SmartTransaction.add*` methods -- UDT balances: Tracked per UDT handler in `SmartTransaction.udtHandlers` map +- Transaction state: Built incrementally via `ccc.Transaction` methods (`addInput`, `addOutput`, `addCellDeps`) +- UDT balances: Tracked via `UdtHandler` interface implementations - Maturing CKB: Cumulative array sorted by maturity timestamp ## Key Abstractions @@ -260,7 +261,7 @@ Receipts convert to UDT; deposits stay as deposits or convert to UDT. No iCKB ca **Manager Classes (Pattern):** - Purpose: Encapsulate entity-specific operations -- Pattern: Stateless managers with methods that operate on SmartTransaction +- Pattern: Stateless managers with methods that accept `ccc.TransactionLike` and return `ccc.Transaction` - Examples: - `DaoManager.deposit(tx, capacities, lock)`: Add DAO deposit cells - `OrderManager.mint(tx, user, info, amounts)`: Create order cell @@ -277,16 +278,12 @@ Receipts convert to UDT; deposits stay as deposits or convert to UDT. No iCKB ca - `WithdrawalGroup`: Pairs owned (withdrawal request) + owner cell with value aggregation - Construction: Async factories via `daoCellFrom()`, `ickbDepositCellFrom()`, `receiptCellFrom()` -**SmartTransaction (Builder):** -- Purpose: Extends ccc.Transaction with UDT-aware balancing -- Pattern: Incremental builder with state accumulation -- Key state: `inputs`, `outputs`, `outputsData`, `cellDeps`, `headerDeps`, `witnesses`, `udtHandlers` map -- Automatic operations: - - `completeFeeChangeToLock()`: Calculate and add change cells for CKB - - `addUdtChange()`: Add change cells for each tracked UDT - - `addCellDeps()`: Deduplicate and append cell dependencies -- Used by: All managers to build transactions atomically -- Note: The "SmartTransaction" ecosystem concept was ABANDONED (no CKB ecosystem adoption). However, this class is still actively used throughout all new packages. The name is a vestige. Header caching has moved to CCC's Client Cache. +**Transaction Building (ccc.Transaction):** +- Purpose: All managers use plain `ccc.Transaction` (SmartTransaction was deleted in Phase 1) +- Pattern: Managers accept `ccc.TransactionLike` and return `ccc.Transaction` (TransactionLike pattern) +- Key operations: `tx.addCellDeps()`, `tx.addInput()`, `tx.addOutput()`, `tx.headerDeps.push()` +- Fee completion: CCC-native `completeFeeBy()` / `completeFeeChangeToLock()` with DAO-aware 64-output limit check (contributed to CCC core) +- Header caching: Transparently handled by CCC Client Cache (no explicit header map) **Value Types (Domain Models):** - `ValueComponents`: `{ ckbValue: ccc.FixedPoint; udtValue: ccc.FixedPoint }` @@ -371,14 +368,14 @@ Receipts convert to UDT; deposits stay as deposits or convert to UDT. No iCKB ca - Pattern: CCC signers abstract wallet implementation **Capacity & Fee Management:** -- `CapacityManager`: Discovers and collects cells for capacity -- `SmartTransaction`: Auto-calculates fees from transaction size -- Pattern: Add-then-complete flow (add inputs/outputs, then complete fee) +- CCC-native: `tx.completeFeeBy()` / `tx.completeFeeChangeToLock()` with DAO-aware 64-output limit +- Pattern: Add-then-complete flow (add inputs/outputs, then complete fee via CCC pipeline) +- Note: `CapacityManager` was deleted in Phase 1; capacity discovery uses `client.findCellsOnChain()` directly **UDT Handling:** -- `UdtHandler`: Encapsulates token script + type script pair -- Pattern: Managers create handlers; SmartTransaction tracks per-UDT changes -- Automatic change: `SmartTransaction.addUdtChange()` for balancing +- `UdtHandler`: Interface encapsulating token script + cell deps +- `UdtManager`: Base implementation with `isUdt()`, `findUdts()`, `completeInputsByUdt()` +- Pattern: Managers hold `UdtHandler` reference; cell deps added via `tx.addCellDeps(udtHandler.cellDeps)` --- diff --git a/.planning/codebase/CONCERNS.md b/.planning/codebase/CONCERNS.md index 721e122..c1ae06f 100644 --- a/.planning/codebase/CONCERNS.md +++ b/.planning/codebase/CONCERNS.md @@ -38,27 +38,28 @@ ### Local UDT Handling May Overlap CCC Upstream (Medium) -- Issue: CCC now has a dedicated `@ckb-ccc/udt` package (at `ccc-dev/ccc/packages/udt/`). The local `packages/utils/src/udt.ts` and `packages/core/src/udt.ts` implement custom UDT handling (`UdtHandler` interface, `IckbUdtManager` class). While the local UDT handling is iCKB-specific (custom balance calculation accounting for DAO deposits), the generic UDT operations like `ccc.udtBalanceFrom()` are still being used from CCC upstream in `packages/utils/src/udt.ts` (4 locations). +- Issue: CCC now has a dedicated `@ckb-ccc/udt` package (at `ccc-fork/ccc/packages/udt/`). The local `packages/utils/src/udt.ts` and `packages/core/src/udt.ts` implement custom UDT handling (`UdtHandler` interface, `IckbUdtManager` class). While the local UDT handling is iCKB-specific (custom balance calculation accounting for DAO deposits), the generic UDT operations like `ccc.udtBalanceFrom()` are still being used from CCC upstream in `packages/utils/src/udt.ts` (4 locations). - Files: - `packages/utils/src/udt.ts` - `UdtHandler` interface, `UdtManager` class (~370 lines) - `packages/core/src/udt.ts` - `IckbUdtManager` extending UDT handling for iCKB-specific logic - - `ccc-dev/ccc/packages/udt/src/` - CCC upstream UDT package + - `ccc-fork/ccc/packages/udt/src/` - CCC upstream UDT package - Usage of `ccc.udtBalanceFrom()`: `packages/utils/src/udt.ts` lines 169, 197, 323, 368 - Impact: There may be duplicated utility code for standard UDT operations (finding cells, calculating balances). The iCKB-specific extensions (e.g., `IckbUdtManager` which modifies balance calculations based on DAO deposit/withdrawal state) are domain-specific and unlikely to be in CCC. - Fix approach: Audit the CCC `@ckb-ccc/udt` package to identify which local utilities can be replaced. Keep iCKB-specific extensions but delegate standard UDT operations (cell finding, basic balance) to CCC where possible. ### Fragile CCC Local Override Mechanism (Medium) -- Issue: The `.pnpmfile.cjs` hook and `ccc-dev/record.sh` script create a fragile mechanism for overriding published CCC packages with local builds. The `.pnpmfile.cjs` `readPackage` hook intercepts pnpm's dependency resolution to redirect `@ckb-ccc/*` packages to local paths under `ccc-dev/ccc/packages/*/`. +- Issue: The `.pnpmfile.cjs` hook and `fork-scripts/record.sh` script create a fragile mechanism for overriding published CCC packages with local builds. The `.pnpmfile.cjs` `readPackage` hook intercepts pnpm's dependency resolution to redirect `@ckb-ccc/*` packages to local paths under `ccc-fork/ccc/packages/*/`. - Files: - `.pnpmfile.cjs` - pnpm hook that overrides `@ckb-ccc/*` package resolutions - - `ccc-dev/record.sh` - clones CCC repo, merges refs, and builds it locally - - `pnpm-workspace.yaml` - includes `ccc-dev/ccc/packages/*` in workspace - - `ccc-dev/ccc/` - local CCC checkout (when present) + - `fork-scripts/record.sh` - generic fork record script (clones repo, merges refs, builds locally) + - `ccc-fork/config.json` - CCC fork configuration (upstream URL, refs, workspace config) + - `pnpm-workspace.yaml` - includes `ccc-fork/ccc/packages/*` in workspace (auto-generated section) + - `ccc-fork/ccc/` - local CCC checkout (when present) - Impact: Multiple fragility points: - 1. The local CCC repo at `ccc-dev/ccc/` must be manually cloned and kept in sync with a specific branch/commit. + 1. The local CCC repo at `ccc-fork/ccc/` must be manually cloned and kept in sync with a specific branch/commit. 2. The `readPackage` hook modifies `dependencies` objects at install time, which can silently break if CCC reorganizes its packages. - 3. CI/CD (`ccc-dev/replay.sh`) must run this setup before `pnpm install`, creating an ordering dependency. + 3. CI/CD (`fork-scripts/replay.sh`) must run this setup before `pnpm install`, creating an ordering dependency. 4. The override mechanism is invisible to developers who don't read `.pnpmfile.cjs`, leading to confusion when packages resolve differently than expected from `package.json`. - Fix approach: Now that UDT and Epoch PRs have been merged into CCC upstream, evaluate whether the local overrides are still needed. If CCC publishes releases containing the merged features, switch to published versions and remove the override mechanism. @@ -107,7 +108,7 @@ - `packages/dao/src/dao.ts`, lines 326-334 (called in async generator loop) - `packages/core/src/owned_owner.ts`, lines 229-234 (same pattern) - Cause: Each `daoCellFrom()` call makes 1-2 RPC calls that cannot be parallelized within a single cell construction. -- Improvement path: The `SmartTransaction.headers` cache in `packages/utils/src/transaction.ts` partially mitigates this by caching fetched headers. For batch operations, consider prefetching all needed headers in parallel before constructing DAO cells. +- Improvement path: CCC's Client Cache transparently caches fetched headers (SmartTransaction's header cache was deleted in Phase 1). For batch operations, consider prefetching all needed headers in parallel before constructing DAO cells. ### Duplicated RPC Batching Code in Legacy Apps @@ -128,12 +129,9 @@ ## Fragile Areas -### SmartTransaction Class Extending ccc.Transaction +### SmartTransaction Class Extending ccc.Transaction (RESOLVED) -- Files: `packages/utils/src/transaction.ts` (517 lines) -- Why fragile: `SmartTransaction` extends `ccc.Transaction` and overrides 8 methods: `completeFee`, `getInputsUdtBalance`, `getOutputsUdtBalance`, `getInputsCapacity`, `clone`, `copy`, `from`, `default`, and `fromLumosSkeleton`. Changes to `ccc.Transaction`'s interface or behavior upstream can silently break `SmartTransaction`. -- Safe modification: When updating CCC dependency, review `ccc.Transaction` changelog for breaking changes to overridden methods. The `completeFee` override (lines 63-98) is particularly fragile as it calls `super.completeFee()` and also queries the client for the NervosDAO script to check the 64-output limit. -- Test coverage: No tests for `SmartTransaction`. +- Status: **Resolved in Phase 1** — SmartTransaction class and CapacityManager were fully deleted from `@ickb/utils`. All manager methods now accept `ccc.TransactionLike` and return `ccc.Transaction` directly. Header caching is handled by CCC Client Cache. The 64-output DAO limit check was contributed to CCC core. ### Bot Order Matching Algorithm @@ -154,11 +152,11 @@ ### 64 Output Cell Limit for NervosDAO Transactions - Current capacity: Maximum 64 output cells per transaction containing NervosDAO operations. -- Limit: Enforced by the NervosDAO script itself. Checked in 6 locations throughout the codebase. +- Limit: Enforced by the NervosDAO script itself. Consolidated into CCC core in Phase 1. - Files: - - `packages/dao/src/dao.ts`, lines 99-103, 174-177, 244-248 - - `packages/core/src/owned_owner.ts`, lines 102-106, 144-148 - - `packages/utils/src/transaction.ts`, lines 85-95 + - `ccc-fork/ccc/packages/core/src/ckb/transaction.ts` — `assertDaoOutputLimit` utility + `completeFee` safety net (contributed to CCC core in Phase 1) + - `packages/dao/src/dao.ts` — calls `assertDaoOutputLimit` + - `packages/core/src/owned_owner.ts` — calls `assertDaoOutputLimit` - `apps/bot/src/index.ts`, line 414 (limits to 58 outputs to reserve 6 for change) - Scaling path: Protocol-level constraint. The bot works around it by limiting deposit/withdrawal operations per transaction. Future NervosDAO script updates may relax this. @@ -242,19 +240,13 @@ ## Dead Code -### `fromLumosSkeleton` in SmartTransaction +### `fromLumosSkeleton` in SmartTransaction (RESOLVED) -- Issue: `SmartTransaction.fromLumosSkeleton()` at `packages/utils/src/transaction.ts` line 432 provides Lumos interoperability. Since the new packages do not use Lumos, this method is only needed if external code passes Lumos skeletons to the new packages. -- Files: `packages/utils/src/transaction.ts`, lines 432-436 -- Impact: Low. The method is a thin wrapper delegating to the superclass. -- Fix approach: Remove after all apps are migrated away from Lumos. +- Status: **Resolved in Phase 1** — SmartTransaction class was fully deleted, including `fromLumosSkeleton()`. -### SmartTransaction Name is Misleading (Not Dead) +### SmartTransaction Name is Misleading (RESOLVED) -- Issue: Despite the original "SmartTransaction" concept being abandoned ecosystem-wide, the `SmartTransaction` class in `packages/utils/src/transaction.ts` is actively used throughout the new packages. It extends `ccc.Transaction` with UDT handler management and header caching (which is what replaced the abandoned SmartTransaction headers concept). The name is a vestige but the code is alive and critical. -- Files: `packages/utils/src/transaction.ts` - definition (517 lines). Used in: `packages/sdk/src/sdk.ts`, `packages/order/src/order.ts`, `packages/dao/src/dao.ts`, `packages/core/src/owned_owner.ts`, `packages/core/src/logic.ts`, `apps/faucet/src/main.ts` -- Impact: The name `SmartTransaction` may confuse developers familiar with the abandoned ecosystem concept. -- Fix approach: Consider renaming to `IckbTransaction` or `EnhancedTransaction`. Low priority since the class is internal to the monorepo. +- Status: **Resolved in Phase 1** — SmartTransaction class was fully deleted from `@ickb/utils`. All manager methods now accept `ccc.TransactionLike` directly. --- diff --git a/.planning/codebase/CONVENTIONS.md b/.planning/codebase/CONVENTIONS.md index 160d0b6..1f7795f 100644 --- a/.planning/codebase/CONVENTIONS.md +++ b/.planning/codebase/CONVENTIONS.md @@ -12,8 +12,8 @@ - The `packages/` directory contains the **NEW replacement libraries** built on CCC (ckb-ccc), which will eventually replace the legacy packages in the apps. - All `@ckb-lumos/*` packages are **DEPRECATED** -- Lumos is being replaced by CCC. - CCC PRs for UDT and Epochs have been **MERGED** upstream -- those features now exist in CCC itself. -- `SmartTransaction` was **ABANDONED** in favor of CCC's client cache for header caching. The class still exists in `packages/utils/src/transaction.ts` but should not be extended further. -- CCC is sometimes overridden locally via `ccc-dev/record.sh` and `.pnpmfile.cjs` for testing unpublished changes. +- `SmartTransaction` was **DELETED** in Phase 1 in favor of CCC's client cache for header caching. Headers are now fetched inline via CCC client calls (`client.getTransactionWithHeader()`, `client.getHeaderByNumber()`). All manager method signatures now accept `ccc.TransactionLike` and return `ccc.Transaction` directly. +- CCC is sometimes overridden locally via `fork-scripts/record.sh` and `.pnpmfile.cjs` for testing unpublished changes. **When writing new code:** Use CCC (`@ckb-ccc/core`) types and patterns exclusively in `packages/`. Never introduce new Lumos dependencies. @@ -26,8 +26,8 @@ - Config files at root use dot-prefix convention: `prettier.config.cjs`, `eslint.config.mjs`, `vitest.config.mts` **Functions:** -- Use `camelCase` for all functions: `binarySearch`, `asyncBinarySearch`, `hexFrom`, `isHex`, `getHeader` -- Prefix boolean-returning functions with `is`: `isHex()`, `isDeposit()`, `isCapacity()`, `isReceipt()`, `isCkb2Udt()`, `isMatchable()`, `isFulfilled()` +- Use `camelCase` for all functions: `binarySearch`, `asyncBinarySearch`, `hexFrom`, `isHex`, `collect` +- Prefix boolean-returning functions with `is`: `isHex()`, `isDeposit()`, `isUdt()`, `isReceipt()`, `isCkb2Udt()`, `isMatchable()`, `isFulfilled()` - Use `tryFrom` for fallible constructors that return `undefined` on failure: `OrderCell.tryFrom()`, `OrderGroup.tryFrom()` - Use `mustFrom` for throwing constructors: `OrderCell.mustFrom()` - Use `from` for static factory methods: `Ratio.from()`, `Info.from()`, `Epoch.from()`, `MasterCell.from()` @@ -40,12 +40,12 @@ **Types/Interfaces:** - Use `PascalCase` for types, interfaces, and classes: `Ratio`, `Info`, `OrderData`, `OrderCell`, `Epoch` -- Suffix data-transfer / input interfaces with `Like`: `InfoLike`, `RelativeLike`, `OrderDataLike`, `MasterLike`, `EpochLike`, `SmartTransactionLike` +- Suffix data-transfer / input interfaces with `Like`: `InfoLike`, `RelativeLike`, `OrderDataLike`, `MasterLike`, `EpochLike` - The `Like` type is the "encodable" or input representation; the plain name is the decoded/validated form - Use `ValueComponents` interface for anything with `ckbValue` and `udtValue` properties **Classes:** -- Use `PascalCase`: `MinHeap`, `BufferedGenerator`, `SmartTransaction`, `CapacityManager`, `UdtManager`, `DaoManager`, `LogicManager`, `OrderManager`, `OwnedOwnerManager`, `IckbSdk` +- Use `PascalCase`: `MinHeap`, `BufferedGenerator`, `UdtManager`, `DaoManager`, `LogicManager`, `OrderManager`, `OwnedOwnerManager`, `IckbSdk` - Manager classes implement `ScriptDeps` interface and contain `script` and `cellDeps` properties - Generic type parameters use single capital letters: ``, `` @@ -234,7 +234,7 @@ async *findDeposits( }, ): AsyncGenerator { ... } ``` -- Use variadic args with flat support: `addUdtHandlers(...udtHandlers: (UdtHandler | UdtHandler[])[])` +- Use variadic args: `sum(res: bigint, ...rest: bigint[])`, `isOwner(...locks: ccc.Script[])` **Return Values:** - Use tuples for multi-value returns: `Promise<[number, boolean]>`, `[ccc.FixedPoint, ccc.FixedPoint]` @@ -256,9 +256,7 @@ async *findDeposits( - Example (`packages/utils/src/index.ts`): ```typescript export * from "./codec.js"; -export * from "./capacity.js"; export * from "./heap.js"; -export * from "./transaction.js"; export * from "./udt.js"; export * from "./utils.js"; ``` @@ -333,20 +331,20 @@ let origins: readonly I8Cell[] = Object.freeze([]); Finder methods throughout the library use `async *` generators for lazy iteration: ```typescript -// packages/utils/src/capacity.ts -async *findCapacities( +// packages/utils/src/udt.ts +async *findUdts( client: ccc.Client, locks: ccc.Script[], options?: { onChain?: boolean; limit?: number }, -): AsyncGenerator { +): AsyncGenerator { const limit = options?.limit ?? defaultFindCellsLimit; for (const lock of unique(locks)) { // ... RPC query setup ... for await (const cell of client.findCells(...findCellsArgs)) { - if (!this.isCapacity(cell) || !cell.cellOutput.lock.eq(lock)) { + if (!this.isUdt(cell) || !cell.cellOutput.lock.eq(lock)) { continue; } - yield { cell, ckbValue: cell.cellOutput.capacity, udtValue: 0n, [isCapacitySymbol]: true }; + yield { cell, ckbValue: cell.cellOutput.capacity, udtValue: ccc.udtBalanceFrom(cell.outputData), [isUdtSymbol]: true }; } } } @@ -354,7 +352,7 @@ async *findCapacities( To collect results into an array, use the `collect()` helper from `packages/utils/src/utils.ts`: ```typescript -const capacities = await collect(capacityManager.findCapacities(client, locks)); +const udts = await collect(udtManager.findUdts(client, locks)); ``` ## App Entry Points diff --git a/.planning/codebase/STACK.md b/.planning/codebase/STACK.md index e55b1be..64cf396 100644 --- a/.planning/codebase/STACK.md +++ b/.planning/codebase/STACK.md @@ -11,7 +11,7 @@ - Rust 2021 edition - On-chain CKB smart contracts in `reference/contracts/` reference repo (3 contracts + shared utils, ~1,163 lines). Built with Capsule v0.10.5, `no_std` + alloc-only runtime, targeting RISC-V. Uses `ckb-std 0.15.3` and `primitive_types` crate for C256 safe math. **Secondary:** -- Bash - `ccc-dev/record.sh`, `ccc-dev/replay.sh` for local CCC dev build setup +- Bash - `fork-scripts/record.sh`, `fork-scripts/replay.sh` for local fork dev build setup - JavaScript (CJS) - `.pnpmfile.cjs` for pnpm hook overrides, `prettier.config.cjs` ## Runtime @@ -73,13 +73,15 @@ packages: - packages/* - apps/* - - ccc-dev/ccc/packages/* - - "!ccc-dev/ccc/packages/demo" - - "!ccc-dev/ccc/packages/docs" - - "!ccc-dev/ccc/packages/examples" - - "!ccc-dev/ccc/packages/faucet" - - "!ccc-dev/ccc/packages/playground" - - "!ccc-dev/ccc/packages/tests" + # @generated begin fork-workspaces — auto-generated by fork-scripts/record.sh + - ccc-fork/ccc/packages/* + - "!ccc-fork/ccc/packages/demo" + - "!ccc-fork/ccc/packages/docs" + - "!ccc-fork/ccc/packages/examples" + - "!ccc-fork/ccc/packages/faucet" + - "!ccc-fork/ccc/packages/playground" + - "!ccc-fork/ccc/packages/tests" + # @generated end fork-workspaces catalog: '@ckb-ccc/core': ^1.12.2 @@ -88,6 +90,8 @@ catalog: minimumReleaseAge: 1440 ``` +**Note:** The `fork-workspaces` section between `@generated` markers is auto-generated by `pnpm fork:record` from `*-fork/config.json` files. Manual edits to that section are overwritten on re-record. + **Internal dependency graph (new CCC-based packages):** ``` @ickb/utils <- @ckb-ccc/core @@ -103,20 +107,19 @@ minimumReleaseAge: 1440 ## Local CCC Dev Build Override Mechanism -The repo supports using a local development build of CCC for testing unpublished upstream changes. This is controlled by two files: +The repo supports using a local development build of CCC for testing unpublished upstream changes. This is controlled by the generic fork management framework (`fork-scripts/`) and the CCC-specific configuration (`ccc-fork/config.json`): -**`ccc-dev/record.sh`:** -- Clones the CCC repo (`https://github.com/ckb-devrel/ccc.git`) into `./ccc-dev/ccc/` -- Accepts refs as args: branch names, PR numbers, or commit SHAs -- Merges specified refs onto a `wip` branch (uses AI Coworker CLI for merge conflict resolution) -- Builds CCC locally: `pnpm build:prepare && pnpm build` -- Run via: `pnpm ccc:record` (default invocation: `bash ccc-dev/record.sh releases/next releases/udt`) -- The `ccc-dev/ccc/` directory is gitignored -- Aborts if `ccc-dev/ccc/` has pending work (any changes vs pinned commit, diverged HEAD, or untracked files) +**`fork-scripts/record.sh ccc-fork`:** +- Clones the CCC repo (upstream URL from `ccc-fork/config.json`) into `./ccc-fork/ccc/` +- Merges refs listed in `ccc-fork/config.json` onto a `wip` branch (uses AI Coworker CLI for merge conflict resolution) +- Patches CCC exports for source-level types via `fork-scripts/patch.sh` +- Run via: `pnpm fork:record ccc-fork` +- The `ccc-fork/ccc/` directory is gitignored +- Aborts if `ccc-fork/ccc/` has pending work (any changes vs pinned commit, diverged HEAD, or untracked files) **`.pnpmfile.cjs`:** -- A pnpm `readPackage` hook that auto-discovers all packages in `ccc-dev/ccc/packages/*/package.json` -- When `ccc-dev/ccc/` exists, overrides all `@ckb-ccc/*` dependency versions in the workspace with `workspace:*` (CCC packages are listed in `pnpm-workspace.yaml`, but catalog specifiers resolve to semver ranges before workspace linking, so the hook forces `workspace:*` to ensure local packages are used) +- A pnpm `readPackage` hook that auto-discovers all packages in `ccc-fork/ccc/packages/*/package.json` +- When `ccc-fork/ccc/` exists, overrides all `@ckb-ccc/*` dependency versions in the workspace with `workspace:*` (CCC packages are listed in `pnpm-workspace.yaml`, but catalog specifiers resolve to semver ranges before workspace linking, so the hook forces `workspace:*` to ensure local packages are used) - Applies to `dependencies`, `devDependencies`, and `optionalDependencies` - Effect: all workspace packages transparently use the local CCC build instead of npm versions @@ -187,7 +190,7 @@ The repo supports using a local development build of CCC for testing unpublished ## Abandoned / Superseded Concepts -**SmartTransaction** (`packages/utils/src/transaction.ts`): Extends `ccc.Transaction` with UDT handler management and header caching. The SmartTransaction concept was abandoned as a standalone pattern because nobody else in the CKB ecosystem adopted it. Headers are now cached in the CCC Client Cache instead. However, the class code remains in the codebase and is still actively used by the new packages (`@ickb/utils`, `@ickb/dao`, `@ickb/order`, `@ickb/core`, `@ickb/sdk`). +**SmartTransaction** (deleted in Phase 1): Extended `ccc.Transaction` with UDT handler management and header caching. The class and its companion `CapacityManager` were removed entirely. Headers are now fetched inline via CCC client calls (`client.getTransactionWithHeader()`, `client.getHeaderByNumber()`) with transparent CCC Client Cache. All manager method signatures now accept `ccc.TransactionLike` and return `ccc.Transaction` directly. --- diff --git a/.planning/codebase/STRUCTURE.md b/.planning/codebase/STRUCTURE.md index ab551cc..8a7b47b 100644 --- a/.planning/codebase/STRUCTURE.md +++ b/.planning/codebase/STRUCTURE.md @@ -34,11 +34,9 @@ │ │ └── codec.ts # PoolSnapshot codec (138 lines) │ └── utils/ # Shared blockchain utilities │ └── src/ -│ ├── index.ts # Barrel export: codec, capacity, heap, transaction, udt, utils -│ ├── transaction.ts # SmartTransaction class (517 lines) -│ ├── capacity.ts # CapacityManager class (221 lines) -│ ├── udt.ts # UDT calculations and handlers (393 lines) -│ ├── utils.ts # Binary search, collectors, etc. (458 lines) +│ ├── index.ts # Barrel export: codec, heap, udt, utils +│ ├── udt.ts # UDT calculations and handlers (407 lines) +│ ├── utils.ts # Binary search, collectors, etc. (292 lines) │ ├── codec.ts # CheckedInt32LE codec (21 lines) │ └── heap.ts # Heap implementation (175 lines) ├── apps/ # Applications @@ -69,17 +67,29 @@ │ │ ├── utils.ts # Helper utilities (160 lines) │ │ └── vite-env.d.ts # Vite type definitions │ └── public/ # Static assets -├── ccc-dev/ # Local CCC development (record/replay system) -│ ├── pins/ # Committed: pinned SHAs and conflict resolutions -│ │ ├── REFS # Pinned branch HEADs -│ │ └── resolutions/ # Merge conflict resolutions -│ ├── ccc/ # Gitignored: ephemeral CCC clone (auto-generated) -│ ├── patch.sh # Rewrites CCC exports to .ts source, creates deterministic commit -│ ├── push.sh # Pushes local CCC changes upstream +├── fork-scripts/ # Generic fork management scripts (accept fork dir as argument) +│ ├── lib.sh # Shared shell functions for fork scripts +│ ├── patch.sh # Rewrites fork exports to .ts source, creates deterministic commit +│ ├── push.sh # Pushes local fork changes upstream │ ├── record.sh # Records new pins with AI Coworker conflict resolution -│ ├── replay.sh # Deterministically rebuilds ccc/ from pins -│ ├── status.sh # Checks if ccc-dev/ccc/ has pending custom work -│ └── tsgo-filter.sh # Wrapper around tsgo filtering CCC diagnostics +│ ├── replay.sh # Deterministically rebuilds clone from pins +│ ├── replay-all.sh # Replays all *-fork/ directories +│ ├── save.sh # Captures local work as a patch in pins/ +│ ├── status.sh # Checks if fork clone has pending custom work +│ ├── status-all.sh # Checks status of all *-fork/ directories +│ ├── clean.sh # Removes fork clone (guarded against pending work) +│ ├── clean-all.sh # Cleans all *-fork/ directories +│ ├── reset.sh # Resets fork to published packages (guarded) +│ └── tsgo-filter.sh # Wrapper around tsgo filtering fork diagnostics +├── ccc-fork/ # CCC fork configuration and pins +│ ├── config.json # Upstream URL, fork URL, refs to merge, workspace config +│ ├── pins/ # Committed: manifest + counted resolutions + local patches +│ │ ├── HEAD # Expected final SHA after full replay +│ │ ├── manifest # Base SHA + merge refs (TSV, one per line) +│ │ ├── res-N.resolution # Conflict resolution for merge step N (counted format) +│ │ └── local-*.patch # Local development patches (applied after merges) +│ ├── ccc/ # Gitignored: ephemeral CCC clone (auto-generated) +│ └── README.md # Fork documentation ├── reference/ # Read-only reference repos (git-ignored, clone via `pnpm reference`) │ ├── contracts/ # Rust on-chain contracts │ │ └── scripts/contracts/ @@ -151,9 +161,9 @@ **packages/utils/src/:** - Purpose: Shared blockchain utilities and primitives -- Exports: SmartTransaction, CapacityManager, UdtHandler, CheckedInt32LE, codecs -- Key classes: `SmartTransaction` (transaction builder), `CapacityManager` (cell management) -- Key helpers: `collect()`, `unique()`, `binarySearch()`, `hexFrom()`, `getHeader()` +- Exports: UdtHandler, UdtManager, CheckedInt32LE, TransactionHeader, codecs +- Key classes: `UdtManager` (UDT cell management) +- Key helpers: `collect()`, `unique()`, `binarySearch()` - Dependencies: @ckb-ccc/core **apps/bot/src/:** @@ -188,14 +198,19 @@ - Status: LEGACY (uses @ickb/v1-core, @ckb-lumos/*, deprecated) - Entry: `main.tsx` with `startApp(wallet_chain)` function - Component tree: Connector → App → Form/Dashboard/Action -- Data flow: React Query for L1 state, SmartTransaction for TX building +- Data flow: React Query for L1 state, @ickb/v1-core for TX building - Styling: TailwindCSS with inline classes -**ccc-dev/:** -- Purpose: Local CCC clone for development against unpublished upstream changes -- System: Record/replay mechanism for deterministic builds -- `pins/`: Committed directory with pinned SHAs (REFS, HEAD) and conflict resolutions -- `ccc/`: Gitignored, ephemeral clone auto-generated by `replay.sh` +**fork-scripts/:** +- Purpose: Generic fork management scripts for record/replay mechanism +- All scripts accept a fork directory as their first argument (e.g., `fork-scripts/record.sh ccc-fork`) +- Called via pnpm aliases: `pnpm fork:record ccc-fork`, `pnpm fork:status ccc-fork`, etc. + +**ccc-fork/:** +- Purpose: CCC fork configuration and pins for local development against unpublished upstream changes +- `config.json`: Upstream URL, fork URL, refs to merge, workspace include/exclude +- `pins/`: Committed directory with manifest + counted resolutions + local patches (multi-file format) +- `ccc/`: Gitignored, ephemeral clone auto-generated by `fork-scripts/replay.sh` - Activation: `.pnpmfile.cjs` auto-triggers replay on `pnpm install` and redirects @ckb-ccc/* deps **.planning/codebase/:** @@ -225,8 +240,7 @@ - Deposits: `packages/core/src/logic.ts` → `LogicManager` class (269 lines) - Orders: `packages/order/src/order.ts` → `OrderManager` class (988 lines) - DAO: `packages/dao/src/dao.ts` → `DaoManager` class (412 lines) -- Transactions: `packages/utils/src/transaction.ts` → `SmartTransaction` class (517 lines) -- Capacity: `packages/utils/src/capacity.ts` → `CapacityManager` class (221 lines) +- UDT: `packages/utils/src/udt.ts` → `UdtHandler` interface + `UdtManager` class (407 lines) **Type Definitions:** - Order entities: `packages/order/src/entities.ts` (Info, Ratio, OrderData) — 754 lines @@ -254,7 +268,7 @@ - Types and functions exported at package level via index **Classes and Functions:** -- Classes: PascalCase (e.g., `IckbSdk`, `LogicManager`, `SmartTransaction`, `DaoManager`) +- Classes: PascalCase (e.g., `IckbSdk`, `LogicManager`, `DaoManager`, `OrderManager`) - Manager suffix: Consistently applied to manager classes - Instance methods: camelCase (e.g., `deposit()`, `mint()`, `getL1State()`) - Static methods: camelCase on class (e.g., `IckbSdk.from()`, `IckbSdk.estimate()`) @@ -318,7 +332,7 @@ **Dependencies:** - Internal package: `"@ickb/package": "workspace:*"` in package.json -- Internal CCC (local dev): Automatic via `.pnpmfile.cjs` override when `ccc-dev/ccc/` exists +- Internal CCC (local dev): Automatic via `.pnpmfile.cjs` override when `ccc-fork/ccc/` exists - External package: `pnpm add @vendor/package` from workspace root - Catalog versions: Reference via `"@vendor/package": "catalog:"` in pnpm-workspace.yaml @@ -345,20 +359,25 @@ ## Special Directories -**ccc-dev/:** -- Purpose: Local development environment for CCC upstream PRs -- System: Record/replay mechanism for deterministic, conflict-free builds -- `pins/`: Committed directory containing: - - `REFS`: Pinned branch HEADs for merging - - `resolutions/`: Serialized conflict resolutions with AI Coworker aid +**fork-scripts/:** +- Purpose: Generic fork management framework for deterministic, conflict-free builds +- System: Record/replay mechanism using pins (manifest + counted resolutions + local patches) +- All scripts accept a `-fork` directory as their first argument +- Commands (using `ccc-fork` as example): + - Record: `pnpm fork:record ccc-fork` (requires AI Coworker CLI) + - Status: `pnpm fork:status ccc-fork` (check for pending work in clone) + - Save: `pnpm fork:save ccc-fork [description]` (capture local work as patch in pins/) + - Push: `pnpm fork:push ccc-fork` (cherry-pick commits onto a PR branch) + - Rebuild: `pnpm install` (automatic when pins/ exists but clone does not) + - Clean (re-replay): `pnpm fork:clean ccc-fork && pnpm install` (guarded) + - Reset (published): `pnpm fork:reset ccc-fork && pnpm install` (guarded) + +**ccc-fork/:** +- Purpose: CCC fork configuration for local development against unpublished upstream changes +- `config.json`: Upstream/fork URLs, merge refs, workspace include/exclude +- `pins/`: Committed merge instructions and conflict resolutions - `ccc/`: Generated from pins; auto-deleted and rebuilt on `pnpm install` -- Activation: `.pnpmfile.cjs` hook triggers `replay.sh` and overrides package resolution -- Commands: - - Record: `pnpm ccc:record releases/next releases/udt` (requires AI Coworker CLI) - - Status: `pnpm ccc:status` (check for pending work in `ccc/`) - - Rebuild: `pnpm install` (automatic) - - Clean (re-replay): `pnpm ccc:clean && pnpm install` (guarded) - - Reset (published): `pnpm ccc:reset && pnpm install` (guarded) +- Activation: `.pnpmfile.cjs` hook triggers `fork-scripts/replay.sh` and overrides package resolution **node_modules/:** - Purpose: Installed npm/pnpm dependencies