fix: add delegate to packed accounts in decompress, chunk proofs#2284
fix: add delegate to packed accounts in decompress, chunk proofs#2284SwenSchaeferjohann wants to merge 22 commits intomainfrom
Conversation
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (14)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThe documentation file Changes
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~2 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
ctoken_for_payments.md (1)
268-311:⚠️ Potential issue | 🟡 MinorFix the misleading "parallel" comment and surface the new decimals requirement.
The example has three issues worth addressing:
Misleading parallel execution: The comment says "in parallel, if any" but the code uses sequential
for-loop withawaiton each iteration. Either execute truly in parallel usingPromise.all()or correct the comment to reflect sequential execution.New
decimalsparameter is required: ThecreateUnwrapInstructionnow requires the mint's decimals (fortransfer_checkedvalidation). The example correctly fetches this viagetMint, but this change should be called out since it differs from previous API versions.Missing error handling: In production, both the load batches loop and the unwrap transaction should be wrapped in try-catch blocks with appropriate failure handling (retries, user notifications, etc.).
Example: Fix parallel execution or comment
For truly parallel execution:
-// Send load batches first (in parallel, if any), then unwrap tx -for (const batch of loadBatches) { - await sendAndConfirmTransaction(rpc, new Transaction().add(...batch), [ - payer, - owner, - ]); -} +// Send load batches in parallel (if any), then unwrap tx +if (loadBatches.length > 0) { + await Promise.all( + loadBatches.map(batch => + sendAndConfirmTransaction(rpc, new Transaction().add(...batch), [payer, owner]) + ) + ); +}Or, for sequential (fix comment):
-// Send load batches first (in parallel, if any), then unwrap tx +// Send load batches first (if any), then unwrap tx for (const batch of loadBatches) {
🤖 Fix all issues with AI agents
In `@ctoken_for_payments.md`:
- Around line 144-159: Add a cross-reference to the official
js/compressed-token/docs/payment-integration.md and a brief note next to the
createTransferInterfaceInstructions example: mention the hot/cold sender pattern
and the rule "For a hot sender or <=8 cold inputs, the result is a
single-element array", and instruct readers to run loadBatches (the rest
returned by sliceLast) in parallel and then submit transferBatch sequentially;
reference the symbols createTransferInterfaceInstructions, sliceLast,
loadBatches, and transferBatch and add a link to the payment-integration.md
which contains the full usage example and the table describing contents of each
transaction.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@ctoken_for_payments.md`:
- Around line 312-318: The comment says "Send load batches first (in
parallel...)" but the for-loop over loadBatches runs sequentially; update to
actually send in parallel by mapping loadBatches to an array of
sendAndConfirmTransaction promises using sendAndConfirmTransaction(rpc, new
Transaction().add(...batch), [payer, owner]) and await Promise.all(...) (or,
alternatively, change the comment to remove "in parallel" if sequential behavior
is intended). Ensure you reference loadBatches, sendAndConfirmTransaction, rpc,
Transaction, payer, and owner when making the change so the behavior and intent
remain clear.
…on-aware proof chunking
03c7dc7 to
32849ea
Compare
|
V1 proof chunk sizes: docs are missing size 3. Code in export const VALID_V1_PROOF_SIZES = [8, 4, 3, 2, 1] as const;
Both docs list
|
| rentPayment: 16, | ||
| writeTopUp: 766, |
There was a problem hiding this comment.
do we have constants for these default values?
Legacy "CToken" Naming (
|
| Export | File | Line |
|---|---|---|
getAssociatedCTokenAddressAndBump |
v3/derivation.ts |
46 |
getAssociatedCTokenAddress |
v3/derivation.ts |
57 |
parseCTokenHot |
v3/get-account-interface.ts |
159 |
parseCTokenCold |
v3/get-account-interface.ts |
179 |
createAssociatedCTokenAccount |
v3/actions/create-associated-ctoken.ts |
33 |
createAssociatedCTokenAccountIdempotent |
v3/actions/create-associated-ctoken.ts |
80 |
createAssociatedCTokenAccountInstruction |
v3/instructions/create-associated-ctoken.ts |
143 |
createAssociatedCTokenAccountIdempotentInstruction |
v3/instructions/create-associated-ctoken.ts |
215 |
mintTo as mintToCToken (export alias) |
index.ts |
98 |
createDecompressCtoken |
v3/layout/layout-transfer2.ts |
481 |
createCompressCtoken |
v3/layout/layout-transfer2.ts |
509 |
createMintToCompressedInstruction |
v3/instructions/mint-to-compressed.ts |
118 |
mintToCompressed |
v3/actions/mint-to-compressed.ts |
34 |
deriveCMintAddress |
v3/derivation.ts |
12 |
Exported Types/Interfaces
| Export | File | Line |
|---|---|---|
CTokenConfig |
v3/instructions/create-ata-interface.ts |
22 |
CreateAssociatedCTokenAccountParams |
v3/instructions/create-associated-ctoken.ts |
51 |
CreateAssociatedCTokenAccountInstructionParams |
v3/instructions/create-associated-ctoken.ts |
123 |
MintToCTokenActionLayout |
v3/layout/layout-mint-action.ts |
40 |
MintToCTokenAction |
v3/layout/layout-mint-action.ts |
189 |
CompressAndCloseCMintActionLayout |
v3/layout/layout-mint-action.ts |
68 |
CompressAndCloseCMintAction |
v3/layout/layout-mint-action.ts |
217 |
MintToCompressedAction / MintToCompressedActionLayout |
v3/layout/layout-mint-action.ts |
31, 180 |
Exported Enums/Constants
| Export | File | Line |
|---|---|---|
TokenAccountSourceType.CTokenHot = 'ctoken-hot' |
v3/get-account-interface.ts |
32 |
TokenAccountSourceType.CTokenCold = 'ctoken-cold' |
v3/get-account-interface.ts |
33 |
COMPRESSIBLE_CTOKEN_ACCOUNT_SIZE |
constants.ts |
89 |
COMPRESSIBLE_CTOKEN_RENT_PER_EPOCH |
constants.ts |
103 |
Exported TokenPool Backward-Compat Wrappers
| Export | File | Line |
|---|---|---|
TokenPoolInfo type |
utils/get-token-pool-infos.ts |
54 |
toTokenPoolInfo() |
utils/get-token-pool-infos.ts |
102 |
deriveTokenPoolInfo() |
utils/get-token-pool-infos.ts |
367 |
checkTokenPoolInfo() |
utils/get-token-pool-infos.ts |
379 |
getTokenPoolInfos() |
utils/get-token-pool-infos.ts |
389 |
selectTokenPoolInfo() |
utils/get-token-pool-infos.ts |
401 |
selectTokenPoolInfosForDecompression() |
utils/get-token-pool-infos.ts |
410 |
TokenPoolActivity type alias |
utils/get-token-pool-infos.ts |
362 |
CreateTokenPoolParams type alias |
program.ts |
395 |
AddTokenPoolParams type alias |
program.ts |
419 |
deriveTokenPoolPda() |
program.ts |
688 |
findTokenPoolIndexAndBump() |
program.ts |
720 |
deriveTokenPoolPdaWithIndex() |
program.ts |
760 |
createTokenPool() |
program.ts |
875 |
addTokenPool() |
program.ts |
893 |
CREATE_TOKEN_POOL_DISCRIMINATOR |
constants.ts |
33 |
ADD_TOKEN_POOL_DISCRIMINATOR |
constants.ts |
55 |
isSingleTokenPoolInfo |
types.ts |
96 |
Beyond the public API, there are ~200+ internal occurrences of ctoken/CToken naming in variable names, parameters, internal helper functions, string literals, and comments across the v3/ source files. Key files: get-account-interface.ts (~35), load-ata.ts (~56), create-associated-ctoken.ts (~20), ata-utils.ts (~11), get-or-create-ata-interface.ts (~11).
Correctness review:
|
| Field | JS parser reads at | Actual SPL offset |
|---|---|---|
| delegate option | 1 byte at 72 | 4 bytes at 72-75 |
| delegate pubkey | 73-104 | 76-107 |
| state | byte 105 | byte 108 |
When delegate is None (common case), byte 72 happens to be 0 in both formats so delegate = null is accidentally correct. But state reads from byte 105 (zero padding) instead of byte 108, making isInitialized = false for all hot c-token accounts. Hot accounts should use unpackAccountSPL from @solana/spl-token instead, which correctly parses all fields including delegatedAmount at offset 121.
3. Missing fields: closeAuthority, isNative
Both are hardcoded (null and false). For cold accounts this is correct (compressed TokenData doesn't have these fields). For hot accounts this is a consequence of issue 2 -- using unpackAccountSPL would fix these as well.
4. No test coverage for delegatedAmount
No test verifies delegatedAmount is correctly populated for either hot or cold accounts. The e2e tests pass because they primarily verify amount (same offset in both layouts) and don't check isInitialized/isFrozen for hot c-tokens.
- Add MAX_TOP_UP (65535) in constants.ts; use in all instruction builders - mintTo action: default maxTopUp to MAX_TOP_UP when omitted - wrap/unwrap: optional maxTopUp on instruction and action - decompressMint: maxTopUp in DecompressMintParams and DecompressMintInstructionParams - createDecompressInterfaceInstruction, createMintInstruction, createMintToCompressedInstruction, update-mint, update-metadata: optional maxTopUp - Non-breaking: all new params optional, default no cap Co-authored-by: Cursor <cursoragent@cursor.com>
|
ctokenHotSource at load-ata.ts:867 not filtered by frozen flag. When a frozen c-token hot account exists with unfrozen cold accounts, decompress targets the frozen ATA and fails on-chain. Fix: add |
|
unwrap.ts:119 -- totalBalance includes frozen amounts, causing "unwrap all" to attempt unwrapping more than the loadable unfrozen balance. Proposed fix: const unfrozenBalance = (accountInterface._sources ?? [])
.filter(s => !s.parsed.isFrozen)
.reduce((sum, s) => sum + s.amount, BigInt(0));
const unwrapAmount = amount != null
? BigInt(amount.toString())
: unfrozenBalance;Note: using |
Inconsistent frozen error handling between loadAta and transferInterfaceDescription: Recommendation: |
effectiveHotAfterSetup over-counts when splInterfaceInfo is missingDescription: Recommendation: } catch (e) {
if (splBalance > BigInt(0) || t22Balance > BigInt(0)) {
throw e;
}
// No SPL interface and no SPL/T22 balance -- safe to ignore.
} |
list of changes:
Breaking Changes
Renames
CTOKEN_PROGRAM_ID: Deprecated. UseLIGHT_TOKEN_PROGRAM_ID(re-exported from@lightprotocol/stateless.js).createCTokenTransferInstruction: Renamed tocreateLightTokenTransferInstruction. Instruction data layout changed (see below).createTransferInterfaceInstruction(multi-program dispatcher): Deprecated. UsecreateLightTokenTransferInstructionfor Light token transfers, or SPL'screateTransferCheckedInstructionfor SPL/T22 transfers.transferInterface(action)destinationparameter changed from ATA address to wallet public key. The function now derives the recipient ATA internally and creates it idempotently (no extra RPC fetch). Callers that previously passed a pre-derived ATA address must now pass the recipient's wallet public key instead.programIddefault changed fromCTOKEN_PROGRAM_IDtoLIGHT_TOKEN_PROGRAM_ID. Parameter order unchanged:amount, programId?, confirmOptions?, options?, wrap?.Multi-transaction support: For >8 compressed inputs, the action now sends parallel load transactions before the final transfer transaction. Previously, all instructions were packed into a single transaction (which could exceed limits).
createTransferInterfaceInstructions(replaces createTransferInterfaceInstruction)New function replacing the old monolithic
transferInterfaceinternals. Takesrecipientas a wallet public key (not ATA). ReturnsTransactionInstruction[][]where each inner array is one transaction. The last element is always the transfer transaction; all preceding elements are load transactions that can be sent in parallel.Options include
ensureRecipientAta(default:true) which prepends an idempotent ATA creation instruction to the transfer transaction, andprogramIdwhich dispatches to SPLtransferCheckedforTOKEN_PROGRAM_ID/TOKEN_2022_PROGRAM_ID.createLoadAtaInstructionsReturn type changed from
TransactionInstruction[](flat) toTransactionInstruction[][](batched). Each inner array is one transaction. For >8 compressed inputs, multiple transactions are needed because each decompress proof can handle at most 8 inputs.createLightTokenTransferInstruction(instruction-level)Instruction data layout changed: Old format was 10 bytes (discriminator + padding + u64 LE at offset 2). New format is 9 bytes (discriminator + u64 LE at offset 1, no padding).
Account keys changed: Now always includes
system_program(index 3) andfee_payer(index 4) for compressible extension rent top-ups. Old format had 3 required accounts (source, destination, owner) with optional payer. New format has 5 required accounts.owneris now writable (for rent top-ups via compressible extension).createDecompressInterfaceInstructionNew required parameter:
decimals: numberadded aftersplInterfaceInfo. Required for SPL destination decompression.Delegate handling: Now includes delegate pubkeys from input compressed accounts in the packed accounts list.
Program instruction: createTokenPool → createSplInterface
CompressedTokenProgram.createTokenPool: Deprecated. UseCompressedTokenProgram.createSplInterfacewith the same call signature (feePayer,mint,tokenProgramId?). The high-level actioncreateSplInterface()now calls the new instruction helper; the deprecated action aliascreateTokenPoolstill works but points tocreateSplInterface.CompressedTokenProgram.createMintnow usescreateSplInterfaceinternally for the third instruction.Added
createTransferInterfaceInstructions: Instruction builder for transfers with multi-transaction batching, frozen account pre-checks, zero-amount rejection, andprogramId-based dispatch (Light token vs SPLtransferChecked).sliceLasthelper: Splits instruction batches into{ rest, last }for parallel-then-sequential sending.TransferOptionsinterface:wrap,programId,ensureRecipientAta, extendsInterfaceOptions.assertUniqueInputHashes: Runtime enforcement that no compressed account hash appears in more than one parallel batch.chunkAccountsByTreeVersion: Exported utility for splitting compressed accounts by tree version into prover-compatible groups._buildLoadBatchesskips frozen sources.createTransferInterfaceInstructionsthrows early if hot account is frozen, reports frozen balance in insufficient-balance errors.loadAtaaction: Now sends all load batches in parallel (previously sequential single-tx).createUnwrapInstructions: New instruction builder for unwrapping c-tokens to SPL/T22. ReturnsTransactionInstruction[][](load batches, if any, then one unwrap batch). Same loop pattern ascreateLoadAtaInstructionsandcreateTransferInterfaceInstructions. Theunwrapaction now uses it internally. Use this when you need instruction-level control or to handle multi-batch load + unwrap in one go.LightTokenProgram: Export alias forCompressedTokenProgramfor clearer naming in docs and examples.createMintInterfaceand the create-mint instruction now decompress the mint in the same transaction. The mint is available on-chain (CMint account created) immediately after creation; a separatedecompressMint()call is no longer required before creating ATAs or minting.decompressMint()remains supported and is idempotent: if the mint was already decompressed (e.g. viacreateMintInterface), it returns successfully without sending a transaction.