Skip to content

Conversation

@ananas-block
Copy link
Contributor

@ananas-block ananas-block commented Nov 19, 2025

Summary by CodeRabbit

  • New Features

    • Builder-style APIs and a CTokenInstruction interface for composing mint-related instructions; CPI-context attachable to instruction data.
    • New TokenPool and recipient helpers plus lighter pubkey conversions for easier key handling.
  • Refactor

    • Unified builder-based instruction assembly with clearer account-meta generation and distinct execute vs. CPI-write flows.
    • CPI account parsing consolidated into a config-driven model.
  • Removed

    • Legacy SPL-mint convenience helpers and older mint-action wrappers.
  • Tests

    • Tests updated for config-driven CPI account parsing.

✏️ Tip: You can customize this high-level summary in your review settings.

…ionData

Add fluent builder pattern to MintActionCompressedInstructionData following
the light-compressed-account pattern.

Changes:
- Add builder.rs with InstructionDiscriminator and LightInstructionData traits
- Add two constructors: new() for existing mints, new_mint() for creating new mints
- Add 8 with_* builder methods for actions (mint_to_compressed, mint_to_ctoken,
  update authorities, metadata operations)
- Add CPI context methods (with_cpi_context, write_to_cpi_context_first/set)
- Use CompressedMintWithContext to bundle merkle context parameters
- new() sets create_mint to None (for existing mints only)
- new_mint() requires proof and automatically sets create_mint, leaf_index=0,
  prove_by_index=false
- All builder methods use #[must_use] attribute for safety

This is part 1 of refactoring compressed token SDK to use trait-based builders.
- Create general CTokenInstruction trait for all compressed token instructions
- Replace instruction-specific InvokeMintActionInstruction with CTokenInstruction
- Use LightInstructionData::data() for serialization with discriminator
- Add associated types for ExecuteAccounts and CpiWriteAccounts
- MintActionCompressedInstructionData now implements CTokenInstruction

This provides a consistent pattern that other compressed token instructions
(transfer, burn, etc.) can follow.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 19, 2025

Walkthrough

Refactors mint-action flows into a builder-and-trait pattern, adds CPI-write / CPI-context support and meta-config types, introduces CTokenInstruction and TokenPool, expands account-meta configuration, adds pubkey helpers, updates Cargo features, and removes legacy create_spl_mint helpers and client paths.

Changes

Cohort / File(s) Summary
Feature & dependency updates
program-libs/account-checks/Cargo.toml, programs/compressed-token/program/Cargo.toml
Adds std feature to account-checks' solana feature and enables msg feature for light-account-checks in compressed-token program dependency.
ctoken-types — builder API
program-libs/ctoken-types/src/instructions/mint_action/builder.rs, program-libs/ctoken-types/src/instructions/mint_action/mod.rs
Adds builder API, constructors, CPI-context helpers and a public discriminator for MintActionCompressedInstructionData; registers new private builder submodule.
Compressed-token SDK — trait & token-pool
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs, sdk-libs/compressed-token-sdk/src/token_pool.rs, sdk-libs/compressed-token-sdk/src/lib.rs
Introduces public CTokenInstruction trait, new TokenPool struct, and re-exports CTokenInstruction and TokenPool.
Mint-action account metas & CPI write
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs
Expands MintActionMetaConfig fields/constructors, adds MintActionMetaConfigCpiWrite and get_mint_action_instruction_account_metas_cpi_write, and adjusts account-meta assembly for CPI/non-CPI flows.
Mint-action instruction implementation
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs, sdk-libs/compressed-token-sdk/src/instructions/mint_action/mod.rs
Implements CTokenInstruction for MintActionCompressedInstructionData, adds instruction / CPI-write entry points, centralizes serialization/CPI-write assembly, and removes many legacy public builders/types and re-exports.
Instruction modules — create/update/mint-to flows
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs, .../mint_to_compressed/instruction.rs, .../update_compressed_mint/instruction.rs
Rewrites create/mint/update flows to use builder, allow attaching CPI context to instruction data, compute account metas via MintActionMetaConfig, and construct Instructions directly using LightInstructionData.
Removed SDK create_spl_mint paths
sdk-libs/compressed-token-sdk/src/instructions/create_spl_mint.rs
Deletes SDK helpers, types, and constants for create_spl_mint.
Token-client: action & instruction changes
sdk-libs/token-client/src/instructions/mint_action.rs, sdk-libs/token-client/src/instructions/mod.rs, sdk-libs/token-client/src/actions/mod.rs, sdk-libs/token-client/src/actions/create_spl_mint.rs, sdk-libs/token-client/src/instructions/create_spl_mint.rs
Adapts token-client to builder-based mint-action flow, adds compatibility types (NewMint, MintActionType, MintToRecipient), removes create_spl_mint module/functions and RPC helper, and adds a debug print in mint_action_comprehensive.
CPI accounts parsing & config
sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs, sdk-libs/compressed-token-sdk/tests/mint_action_cpi_accounts_tests.rs
Adds MintActionCpiAccountsConfig to drive parsing behavior, replaces specialized constructors with config-driven parsing, updates tests.
Pubkey helpers
program-libs/compressed-account/src/pubkey.rs
Adds to_light_pubkey() to AsPubkey trait and implements it for Pubkey (and related types).
ctoken-types — mint_to_compressed helpers
program-libs/ctoken-types/src/instructions/mint_action/mint_to_compressed.rs
Adds Recipient::new and MintToCompressedAction::new constructors accepting impl AsPubkey.
Errors
sdk-libs/compressed-token-sdk/src/error.rs
Adds CreateMintDataRequired, CreateMintMustBeNone, and CpiContextRequired error variants and numeric mappings.
Minor edits / removals
sdk-libs/compressed-token-sdk/src/instructions/* (approve, batch_compress, transfer2), various mod re-exports
Small comment removals, multiple public re-export adjustments (removal of create_spl_mint exports, reworked mint_action re-exports), and other tidy-ups.

Sequence Diagram(s)

sequenceDiagram
    participant Client as SDK / Client
    participant Builder as MintActionCompressedInstructionData (builder)
    participant Meta as MintActionMetaConfig / CpiWriteConfig
    participant Serializer as LightInstructionData
    participant Program as CompressedTokenProgram

    Client->>Builder: new(...) / new_mint(...) and chain with_*() calls
    Builder->>Meta: build MintActionMetaConfig (new / new_create_mint / new_cpi_context)
    Meta->>Meta: analyze_actions() → account meta list
    Builder->>Serializer: serialize via LightInstructionData
    Serializer->>Program: assemble Instruction(program_id, accounts, data)
    Program-->>Client: Instruction ready
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas to focus review effort:

  • Trait generics, lifetimes, and account-info bounds in CTokenInstruction and its implementations.
  • Validation and invariants in MintActionMetaConfig constructors (CPI vs non-CPI, mandatory fee_payer change).
  • Builder invariants for new-mint vs existing-mint paths and CPI-context write semantics (including new_mint_write_to_cpi_context).
  • CPI-write serialization and account-metas composition, especially indexing of ctoken_accounts.
  • Removal of create_spl_mint helpers — search for residual references in SDK/client code.

Possibly related PRs

Suggested labels

ai-review

Suggested reviewers

  • sergeytimoshin
  • SwenSchaeferjohann

Poem

✨ Builder bricks align, instructions take flight,

CPI contexts stitched, account metas right.
Old helpers retired, new traits lead the way,
Token pools stand ready for the next play.
A tidy refactor — ship it on its way.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly reflects the main refactoring work: transitioning the compressed token SDK's mint action from an input-based API to a builder-pattern API for MintActionCompressedInstructionData.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 70.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jorrit/refactor-compressed-token-sdk-builder

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a1d359 and 5b1811f.

📒 Files selected for processing (3)
  • program-libs/compressed-account/src/pubkey.rs (2 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (5 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/mod.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (4)
sdk-libs/token-client/src/actions/mint_action.rs (1)
  • mint_action (24-59)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (4)
  • get_mint_action_instruction_account_metas (172-274)
  • get_mint_action_instruction_account_metas_cpi_write (288-318)
  • new_cpi_context (104-136)
  • new_create_mint (27-64)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (2)
  • new_mint (44-63)
  • new_mint_write_to_cpi_context (65-84)
program-libs/compressed-account/src/pubkey.rs (1)
  • new_from_array (79-81)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: programs (system-cpi-test-v2-functional-read-only, ["cargo-test-sbf -p system-cpi-v2-test -- func...
  • GitHub Check: programs (system-cpi-test-v2-functional-account-infos, ["cargo-test-sbf -p system-cpi-v2-test -- ...
  • GitHub Check: programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test", "cargo test -p light-system-prog...
  • GitHub Check: programs (compressed-token-batched-tree, ["cargo-test-sbf -p compressed-token-test -- test_transf...
  • GitHub Check: programs (compressed-token-and-e2e, ["cargo-test-sbf -p compressed-token-test --test v1", "cargo-...
  • GitHub Check: programs (account-compression-and-registry, ["cargo-test-sbf -p account-compression-test", "cargo...
  • GitHub Check: stateless-js-v2
  • GitHub Check: stateless-js-v1
  • GitHub Check: Forester e2e test
  • GitHub Check: Test program-libs-slow
  • GitHub Check: Test batched-merkle-tree-simulate
  • GitHub Check: Test program-libs-fast
🔇 Additional comments (3)
program-libs/compressed-account/src/pubkey.rs (1)

213-215: Excellent refactor – trait default with optimized override.

The addition of to_light_pubkey with a default trait implementation elegantly provides the conversion for all AsPubkey implementors while allowing the Pubkey type itself to override with a direct copy (*self), avoiding the unnecessary to_bytes().into() round-trip. This properly addresses the previous review feedback about reducing duplication.

The pattern is clean: all other implementations (anchor_lang::prelude::Pubkey, solana_pubkey::Pubkey, [u8; 32]) correctly inherit the default behavior, ensuring consistent conversions across the codebase.

Also applies to: 226-228

sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (2)

49-83: Builder-based construction of mint-action instruction data looks solid.

The flow here is coherent: you assemble CompressedMintInstructionData, wrap it in CompressedMintWithContext, then use MintActionCompressedInstructionData::new_mint(...) plus .with_cpi_context(...) when provided. This keeps the create‑mint path aligned with the generic mint‑action builder and centralizes serialization via LightInstructionData::data(), which is a nice clean‑up compared to the older ad‑hoc CPI helpers.


144-186: CPI‑write path configuration matches the meta helper’s expectations.

The create_compressed_mint_cpi_write flow looks consistent:

  • You validate the CpiContext flags up front.
  • Build CompressedMintInstructionData identically to the non‑CPI‑write path.
  • Use MintActionCompressedInstructionData::new_mint_write_to_cpi_context (no proof, as expected for a write‑only step).
  • Populate MintActionMetaConfigCpiWrite with fee_payer, mint_signer, authority, cpi_context, and mint_needs_to_sign = true, which lines up with get_mint_action_instruction_account_metas_cpi_write.

Overall, this path is well‑factored and reads clearly once the CPI‑context create‑mint config issue above is addressed.


Comment @coderabbitai help to get the list of available commands and usage tips.

@ananas-block ananas-block changed the title refactor: compressed token sdk builder wip refactor: compressed token sdk mint action builder Nov 19, 2025
@ananas-block ananas-block marked this pull request as ready for review November 19, 2025 18:34
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
sdk-libs/token-client/src/actions/mint_action.rs (1)

137-146: Prefer structured logging over println! in library code

Using println!("params {:?}", params); in mint_action_comprehensive will emit unstructured logs for every call, with no way for downstream users to control verbosity or target. That’s fine for local debugging but undesirable in an SDK.

Consider switching to your standard logging/tracing macro (e.g. tracing::debug! / log::debug!) and, if needed, gating behind a feature flag so this trace can be enabled without impacting production noise:

-    println!("params {:?}", params);
+    tracing::debug!(?params, "mint_action params");

(or equivalent in whatever logging stack you use).

sdk-libs/token-client/src/instructions/mint_action.rs (1)

124-131: Avoid unwrap() when deserializing the compressed mint account

compressed_mint_account.data.unwrap() will panic if the RPC response contains an account without data (or the field is unexpectedly None). This is a hard crash instead of a controlled RpcError.

Consider mapping the missing data into an error instead of panicking:

-        let compressed_mint: CompressedMint = BorshDeserialize::deserialize(
-            &mut compressed_mint_account.data.unwrap().data.as_slice(),
-        )
+        let account_data = compressed_mint_account.data.ok_or_else(|| {
+            RpcError::CustomError(
+                format!("Compressed mint account {:?} has no data", params.compressed_mint_address),
+            )
+        })?;
+
+        let compressed_mint: CompressedMint =
+            BorshDeserialize::deserialize(&mut account_data.data.as_slice())

This keeps the client resilient to unexpected or malformed RPC responses.
-->

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5839134 and 631710a.

⛔ Files ignored due to path filters (9)
  • program-tests/utils/src/assert_mint_action.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/src/ctoken_pda/mint.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/src/mint_compressed_tokens_cpi_write.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/src/pda_ctoken/mint.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/src/pda_ctoken/processor.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/tests/compress_and_close_cpi.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/tests/ctoken_pda.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/tests/decompress_full_cpi.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/tests/pda_ctoken.rs is excluded by none and included by none
📒 Files selected for processing (21)
  • program-libs/account-checks/Cargo.toml (1 hunks)
  • program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1 hunks)
  • program-libs/ctoken-types/src/instructions/mint_action/mod.rs (1 hunks)
  • programs/compressed-token/program/Cargo.toml (1 hunks)
  • sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (5 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/create_spl_mint.rs (0 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (2 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (1 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mint_action/mod.rs (1 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mint_to_compressed/instruction.rs (4 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mod.rs (1 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/update_compressed_mint/instruction.rs (4 hunks)
  • sdk-libs/compressed-token-sdk/src/lib.rs (2 hunks)
  • sdk-libs/compressed-token-sdk/src/token_pool.rs (1 hunks)
  • sdk-libs/token-client/src/actions/create_spl_mint.rs (0 hunks)
  • sdk-libs/token-client/src/actions/mint_action.rs (1 hunks)
  • sdk-libs/token-client/src/actions/mod.rs (0 hunks)
  • sdk-libs/token-client/src/instructions/create_spl_mint.rs (0 hunks)
  • sdk-libs/token-client/src/instructions/mint_action.rs (4 hunks)
  • sdk-libs/token-client/src/instructions/mod.rs (0 hunks)
💤 Files with no reviewable changes (5)
  • sdk-libs/token-client/src/instructions/create_spl_mint.rs
  • sdk-libs/compressed-token-sdk/src/instructions/create_spl_mint.rs
  • sdk-libs/token-client/src/instructions/mod.rs
  • sdk-libs/token-client/src/actions/mod.rs
  • sdk-libs/token-client/src/actions/create_spl_mint.rs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-14T00:36:53.191Z
Learnt from: ananas-block
Repo: Lightprotocol/light-protocol PR: 1909
File: program-libs/zero-copy/src/init_mut.rs:241-249
Timestamp: 2025-08-14T00:36:53.191Z
Learning: In the light-protocol zero-copy crate, performance is prioritized over safety checks for edge cases like Vec lengths exceeding u32::MAX, even when there might be wire format inconsistencies.

Applied to files:

  • programs/compressed-token/program/Cargo.toml
🧬 Code graph analysis (9)
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (3)
  • instruction (22-45)
  • instruction_write_to_cpi_context_first (47-63)
  • instruction_write_to_cpi_context_set (65-81)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (3)
sdk-libs/token-client/src/actions/mint_action.rs (1)
  • mint_action (25-60)
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (3)
  • instruction (31-34)
  • instruction_write_to_cpi_context_first (46-49)
  • instruction_write_to_cpi_context_set (60-63)
program-libs/ctoken-types/src/instructions/mint_action/cpi_context.rs (1)
  • default (25-37)
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (3)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (4)
  • get_mint_action_instruction_account_metas (247-385)
  • get_mint_action_instruction_account_metas_cpi_write (399-438)
  • new_cpi_context (160-199)
  • new_cpi_create_mint (120-137)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
  • new_mint (88-107)
program-libs/compressed-account/src/pubkey.rs (1)
  • new_from_array (79-81)
sdk-libs/compressed-token-sdk/src/instructions/update_compressed_mint/instruction.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (4)
  • get_mint_action_instruction_account_metas (247-385)
  • get_mint_action_instruction_account_metas_cpi_write (399-438)
  • new (73-117)
  • new_cpi (140-157)
sdk-libs/compressed-token-sdk/src/instructions/mint_to_compressed/instruction.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (4)
  • get_mint_action_instruction_account_metas (247-385)
  • new (73-117)
  • new_cpi_context (160-199)
  • new_cpi (140-157)
sdk-libs/compressed-token-sdk/src/instructions/mod.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (2)
  • get_mint_action_instruction_account_metas (247-385)
  • get_mint_action_instruction_account_metas_cpi_write (399-438)
sdk-libs/token-client/src/instructions/mint_action.rs (4)
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (2)
  • derive_compressed_mint_address (205-214)
  • find_spl_mint_address (224-229)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (3)
  • get_mint_action_instruction_account_metas (247-385)
  • new (73-117)
  • new_create_mint (32-70)
sdk-libs/token-client/src/actions/mint_action.rs (1)
  • mint_action (25-60)
sdk-libs/program-test/src/logging/formatter.rs (1)
  • format (249-288)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
sdk-libs/token-client/src/actions/mint_action.rs (1)
  • mint_action (25-60)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (1)
sdk-libs/token-client/src/actions/mint_action.rs (1)
  • mint_action (25-60)
🪛 GitHub Actions: lint
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs

[warning] 1-1: Diff in formatting: added line breaks and wrapping in use statements.


[warning] 1-1: Diff in get_mint_action_instruction_account_metas import: reordered imports.


[warning] 1-1: Diff in program_id construction: wrapping Pubkey::new_from_array call across lines.

sdk-libs/compressed-token-sdk/src/instructions/update_compressed_mint/instruction.rs

[warning] 1-1: Diff in import blocks and builder pattern formatting within MintActionCompressedInstructionData initialization.

sdk-libs/compressed-token-sdk/src/instructions/mint_to_compressed/instruction.rs

[warning] 1-1: Diff in import: pub use vs use ordering for MintAction-related items.

🪛 GitHub Check: lint
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs

[warning] 1-1:
Diff in /home/runner/work/light-protocol/light-protocol/sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs


[warning] 116-116:
Diff in /home/runner/work/light-protocol/light-protocol/sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs


[warning] 106-106:
Diff in /home/runner/work/light-protocol/light-protocol/sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs


[warning] 191-191:
Diff in /home/runner/work/light-protocol/light-protocol/sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs


[warning] 184-184:
Diff in /home/runner/work/light-protocol/light-protocol/sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs

sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs

[warning] 95-95:
Diff in /home/runner/work/light-protocol/light-protocol/sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Forester e2e test
  • GitHub Check: Test program-libs-slow
  • GitHub Check: Test batched-merkle-tree-simulate
  • GitHub Check: Test program-libs-fast
  • GitHub Check: stateless-js-v2
🔇 Additional comments (8)
program-libs/account-checks/Cargo.toml (1)

18-18: Consolidate std requirement within solana feature — idiomatic and sound.

Making "std" an explicit dependency of the "solana" feature is the right call here. This ensures that whenever solana-related code paths are enabled, the standard library is available, which aligns with the broader builder and account-meta enhancements described in the PR summary.

The pattern mirrors the existing "test-only" feature (line 22), maintaining consistency across the crate's feature design. Since feature flags compose idempotently, this poses no compatibility risk for users who already enable "std" in their defaults.

programs/compressed-token/program/Cargo.toml (1)

75-79: Rationale for feature flag differences between dependencies.

The choice to include "msg" in the main dependency (line 55) but not in dev-dependencies (lines 75-79) appears intentional—production code needs messaging support for CPI flows, while tests use the "test-only" feature. However, please confirm this design is deliberate and document if needed.

program-libs/ctoken-types/src/instructions/mint_action/mod.rs (1)

16-16: Let me verify that MintActionCompressedInstructionData is publicly exported:


The builder pattern is properly exposed through public methods on MintActionCompressedInstructionData. The module doesn't need explicit pub use re-exports.

The builder API is accessible to consumers through the public impl methods: MintActionCompressedInstructionData::new(), new_mint(), with_mint_to_compressed(), etc. The builder module is an internal organizational detail—its visibility doesn't affect API accessibility. This follows standard Rust patterns: keep module structure private and expose only the public API surface (the methods).

Likely an incorrect or invalid review comment.

program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)

88-107: new_mint defaulting create_mint and requiring a proof matches the documented constraints

The new_mint constructor correctly enforces new‑mint semantics:

  • leaf_index = 0 and prove_by_index = false, so it can’t be (mis)used for existing mints.
  • proof is required and stored as Some(proof), matching the “REQUIRED for new mint” constraint.
  • create_mint: Some(CreateMint::default()) clearly distinguishes this path from new().

This wiring looks consistent with the comments and separation between new vs. existing mints.

sdk-libs/compressed-token-sdk/src/lib.rs (1)

4-22: Re-export of CTokenInstruction and TokenPool aligns the public SDK surface

Adding pub mod ctoken_instruction; plus pub use ctoken_instruction::CTokenInstruction; and pub use token_pool::TokenPool; makes the key traits/types available from the crate root, which is exactly what downstream users expect for core SDK concepts.

This looks like the right level of exposure for the new instruction abstraction and token pool type.

sdk-libs/compressed-token-sdk/src/token_pool.rs (1)

5-13: TokenPool struct and derives look appropriate for PDA helpers and serialization

Defining TokenPool { pubkey, bump, index } with #[derive(Debug, Clone, AnchorDeserialize, AnchorSerialize, PartialEq)] matches how the PDA helpers at the bottom of the file operate (they already compute (pubkey, bump, index)).

This gives callers a simple, serializable handle to pool metadata, and the reuse of the crate-level AnchorDeserialize / AnchorSerialize aliases keeps it consistent with the rest of the SDK’s serialization story.

sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1)

1-64: CTokenInstruction trait cleanly separates execute vs CPI-write instruction construction

The trait design is solid:

  • Associated ExecuteAccounts<'info, A> / CpiWriteAccounts<'info, A> let each instruction define its own typed account layouts while staying generic over the AccountInfoTrait implementation.
  • The three methods consume self to build an Instruction, which fits a builder-style API and prevents accidental reuse of mutated instruction data.
  • Having dedicated instruction_write_to_cpi_context_first / set methods makes CPI batching semantics explicit at the type level instead of burying flags in opaque data.

This provides a clear, reusable contract for other compressed-token instructions to implement.

sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (1)

15-45: CTokenInstruction impl wiring looks sound

The CTokenInstruction implementation for MintActionCompressedInstructionData aligns well with the new model:

  • Correctly gates instruction() against CPI-write modes by inspecting cpi_context.
  • Uses LightInstructionData::data() for serialization.
  • Delegates account meta generation to MintActionCpiAccounts::to_account_metas(false) and anchors program_id to COMPRESSED_TOKEN_PROGRAM_ID.

This keeps the instruction-building surface consistent with other instructions in the SDK and centralizes CPI concerns into the dedicated write helpers.
-->

Comment on lines 91 to 105
&instruction_data,
input.mint_authority,
input.payer,
cpi_context_pubkey.unwrap(),
)?
} else {
// Regular CPI mode
MintActionMetaConfig::new_cpi_create_mint(
&instruction_data,
input.mint_authority,
input.payer,
input.mint_signer,
input.address_tree_pubkey,
input.output_queue,
)?
};

create_mint_action_cpi(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Set CPI-context meta config fields before serializing accounts.

When cpi_context_pubkey is present we call MintActionMetaConfig::new_cpi_context(...) and immediately collect account metas. That constructor intentionally leaves tree_pubkey, output_queue, and mint_signer as placeholders that must be filled via the chainable setters (see the comments in MintActionMetaConfig::new_cpi_context). Because we never populate them, get_mint_action_instruction_account_metas emits the default Pubkey::default() for the state tree/output queue and skips the mint signer entirely, so the on-chain processor will reject this instruction. Please set these fields before deriving the metas (e.g. chain .with_mint_signer(input.mint_signer).with_tree_pubkey(input.address_tree_pubkey).with_output_queue(input.output_queue) or an equivalent sequence).

🤖 Prompt for AI Agents
In
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs
around lines 88 to 105, the MintActionMetaConfig created via
new_cpi_context(...) leaves tree_pubkey, output_queue, and mint_signer as
placeholders; before collecting account metas you must populate those fields
using the chainable setters (for example call
.with_mint_signer(input.mint_signer).with_tree_pubkey(input.address_tree_pubkey).with_output_queue(input.output_queue)
or an equivalent ordering) so the serialized account metas contain the real
Pubkeys and the mint signer is included.

Comment on lines 219 to 242
/// Helper to analyze actions and extract info
fn analyze_actions(
actions: &[light_ctoken_types::instructions::mint_action::Action],
) -> (bool, Vec<Pubkey>) {
let mut has_mint_to_actions = false;
let ctoken_accounts = Vec::new();

for action in actions {
match action {
light_ctoken_types::instructions::mint_action::Action::MintToCompressed(_) => {
has_mint_to_actions = true;
}
light_ctoken_types::instructions::mint_action::Action::MintToCToken(_) => {
// MintToCToken also requires tokens_out_queue (matches on-chain logic)
has_mint_to_actions = true;
// Extract account from action - but we can't because it's an index
// So ctoken_accounts must be provided separately by user
}
_ => {}
}
}

(has_mint_to_actions, ctoken_accounts)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Document the expectation to supply ctoken_accounts explicitly

analyze_actions intentionally returns an empty ctoken_accounts vector even when MintToCToken actions are present:

fn analyze_actions(
    actions: &[light_ctoken_types::instructions::mint_action::Action],
) -> (bool, Vec<Pubkey>) {
    let mut has_mint_to_actions = false;
    let ctoken_accounts = Vec::new();

    for action in actions {
        match action {
            Action::MintToCompressed(_) => has_mint_to_actions = true,
            Action::MintToCToken(_) => {
                has_mint_to_actions = true;
                // ... cannot extract accounts because action uses indices
            }
            _ => {}
        }
    }

    (has_mint_to_actions, ctoken_accounts)
}

and with_ctoken_accounts is the only API that actually populates ctoken_accounts:

pub fn with_ctoken_accounts(mut self, accounts: Vec<Pubkey>) -> Self {
    self.ctoken_accounts = accounts;
    self
}

This is correct given that on-chain actions only store indices, but it’s easy for callers to assume ctoken_accounts is derived automatically from actions.

I’d suggest:

  • Making it explicit in the with_ctoken_accounts doc comment (and/or in the MintActionMetaConfig struct comment) that callers must supply decompressed token accounts whenever MintToCToken actions are present, otherwise get_mint_action_instruction_account_metas will emit no extra accounts and the on-chain program will fail.
  • Optionally asserting (in higher-level helpers) that when has_mint_to_actions and any MintToCToken are present, ctoken_accounts is non-empty before constructing metas.

This will save users from subtle runtime failures when wiring MintToCToken flows.
-->

Also applies to: 213-217

🤖 Prompt for AI Agents
In sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs
around lines 213-242, callers can mistakenly expect ctoken_accounts to be
derived from Action::MintToCToken but analyze_actions intentionally returns an
empty vector; update the library by adding an explicit doc comment to
with_ctoken_accounts (and/or the MintActionMetaConfig struct) stating that
callers MUST supply decompressed token Pubkeys whenever MintToCToken actions are
present, and add a runtime check before building account metas that if
has_mint_to_actions and any MintToCToken actions exist then ctoken_accounts is
non-empty (return an Err or panic with a clear message) so we fail fast instead
of producing invalid account metas.

Comment on lines 84 to 167
/// Helper function for building CPI write instructions
#[inline(always)]
fn build_cpi_write_instruction<A: light_account_checks::AccountInfoTrait + Clone>(
instruction_data: MintActionCompressedInstructionData,
accounts: &MintActionCpiWriteAccounts<A>,
) -> Result<Instruction> {
// Convert high-level actions to program-level actions
let mut program_actions = Vec::new();
let create_mint = if input.create_mint {
Some(CreateMint::default())
} else {
None
};

// Check for lamports, decompressed status, and mint actions before moving
let with_lamports = false;
let spl_mint_initialized = input
.actions
.iter()
.any(|action| matches!(action, MintActionType::CreateSplMint { .. }))
|| input
.compressed_mint_inputs
.mint
.metadata
.spl_mint_initialized;
let has_mint_to_actions = input.actions.iter().any(|action| {
matches!(
action,
MintActionType::MintTo { .. } | MintActionType::MintToCToken { .. }
)
});
// Match onchain logic: with_mint_signer = create_mint() | has_CreateSplMint_action
let with_mint_signer = create_mint.is_some()
|| input
.actions
.iter()
.any(|action| matches!(action, MintActionType::CreateSplMint { .. }));

// Only require mint to sign when creating a new compressed mint
let mint_needs_to_sign = create_mint.is_some();

// Collect decompressed accounts for account index mapping
let mut decompressed_accounts: Vec<Pubkey> = Vec::new();
let mut decompressed_account_index = 0u8;

for action in input.actions {
// Check that we don't have actions that require proof or multiple accounts
// CPI write mode is limited to simple operations
for action in &instruction_data.actions {
match action {
MintActionType::CreateSplMint { mint_bump: bump } => {
program_actions.push(Action::CreateSplMint(CreateSplMintAction {
mint_bump: bump,
}));
}
MintActionType::MintTo {
recipients,
token_account_version,
} => {
// TODO: cleanup once lamports are removed.
let program_recipients: Vec<_> = recipients
.into_iter()
.map(|r| Recipient {
recipient: r.recipient.to_bytes().into(),
amount: r.amount,
})
.collect();

program_actions.push(Action::MintToCompressed(MintToCompressedAction {
token_account_version,
recipients: program_recipients,
}));
}
MintActionType::UpdateMintAuthority { new_authority } => {
program_actions.push(Action::UpdateMintAuthority(UpdateAuthority {
new_authority: new_authority.map(|auth| auth.to_bytes().into()),
}));
}
MintActionType::UpdateFreezeAuthority { new_authority } => {
program_actions.push(Action::UpdateFreezeAuthority(UpdateAuthority {
new_authority: new_authority.map(|auth| auth.to_bytes().into()),
}));
}
MintActionType::MintToCToken { account, amount } => {
use light_ctoken_types::instructions::mint_action::MintToCTokenAction;

// Add account to decompressed accounts list and get its index
decompressed_accounts.push(account);
let current_index = decompressed_account_index;
decompressed_account_index += 1;

program_actions.push(Action::MintToCToken(MintToCTokenAction {
account_index: current_index,
amount,
}));
}
MintActionType::UpdateMetadataField {
extension_index,
field_type,
key,
value,
} => {
program_actions.push(Action::UpdateMetadataField(UpdateMetadataFieldAction {
extension_index,
field_type,
key,
value,
}));
}
MintActionType::UpdateMetadataAuthority {
extension_index,
new_authority,
} => {
program_actions.push(Action::UpdateMetadataAuthority(
UpdateMetadataAuthorityAction {
extension_index,
new_authority: new_authority.to_bytes().into(),
},
));
Action::CreateSplMint(_) => {
msg!("CreateSplMint not supported in CPI write mode");
return Err(TokenSdkError::CannotMintWithDecompressedInCpiWrite);
}
MintActionType::RemoveMetadataKey {
extension_index,
key,
idempotent,
} => {
program_actions.push(Action::RemoveMetadataKey(RemoveMetadataKeyAction {
extension_index,
key,
idempotent,
}));
Action::MintToCToken(_) => {
// MintToCToken is allowed but needs recipient accounts
}
_ => {} // Other actions are OK
}
}

// Create account meta config first (before moving compressed_mint_inputs)
let meta_config = MintActionMetaConfig {
fee_payer: Some(input.payer),
mint_signer: if with_mint_signer {
Some(input.mint_seed)
} else {
None
},
authority: input.authority,
tree_pubkey: input.address_tree_pubkey,
input_queue: input.input_queue,
output_queue: input.output_queue,
tokens_out_queue: input.tokens_out_queue,
with_lamports,
spl_mint_initialized,
has_mint_to_actions,
with_cpi_context: cpi_context_pubkey,
create_mint: create_mint.is_some(),
with_mint_signer,
mint_needs_to_sign,
ctoken_accounts: decompressed_accounts,
};

// Get account metas (before moving compressed_mint_inputs)
let accounts =
get_mint_action_instruction_account_metas(meta_config, &input.compressed_mint_inputs);
msg!("account metas {:?}", accounts);
let instruction_data = MintActionCompressedInstructionData {
create_mint,
leaf_index: input.compressed_mint_inputs.leaf_index,
prove_by_index: input.compressed_mint_inputs.prove_by_index,
root_index: input.compressed_mint_inputs.root_index,
compressed_address: input.compressed_mint_inputs.address,
mint: input.compressed_mint_inputs.mint,
token_pool_bump: input.token_pool.as_ref().map_or(0, |tp| tp.bump),
token_pool_index: input.token_pool.as_ref().map_or(0, |tp| tp.index),
actions: program_actions,
proof: input.proof,
cpi_context,
};

// Serialize instruction data
let data_vec = instruction_data
.try_to_vec()
// Serialize instruction data with discriminator using LightInstructionData trait
let data = instruction_data
.data()
.map_err(|_| TokenSdkError::SerializationError)?;

// Build instruction
Ok(Instruction {
program_id: Pubkey::new_from_array(light_ctoken_types::COMPRESSED_TOKEN_PROGRAM_ID),
accounts,
data: [vec![MINT_ACTION_DISCRIMINATOR], data_vec].concat(),
})
}

/// Creates a mint action instruction without CPI context
pub fn create_mint_action(input: MintActionInputs) -> Result<Instruction> {
create_mint_action_cpi(input, None, None)
}

/// Input struct for creating a mint action CPI write instruction
#[derive(Debug, Clone, AnchorDeserialize, AnchorSerialize, PartialEq)]
pub struct MintActionInputsCpiWrite {
pub compressed_mint_inputs:
light_ctoken_types::instructions::mint_action::CompressedMintWithContext,
pub mint_seed: Option<Pubkey>, // Optional - only when creating mint and when creating SPL mint
pub mint_bump: Option<u8>, // Bump seed for creating SPL mint
pub create_mint: bool, // Whether we're creating a new mint
pub authority: Pubkey,
pub payer: Pubkey,
pub actions: Vec<MintActionType>,
//pub input_queue: Option<Pubkey>, // Input queue for existing compressed mint operations
pub cpi_context: light_ctoken_types::instructions::mint_action::CpiContext,
pub cpi_context_pubkey: Pubkey,
}

/// Input parameters for creating a new mint in CPI write mode
#[derive(Debug, Clone)]
pub struct CreateMintCpiWriteInputs {
pub compressed_mint_inputs:
light_ctoken_types::instructions::mint_action::CompressedMintWithContext,
pub mint_seed: Pubkey,
pub authority: Pubkey,
pub payer: Pubkey,
pub cpi_context_pubkey: Pubkey,
pub first_set_context: bool,
pub address_tree_index: u8,
pub output_queue_index: u8,
pub assigned_account_index: u8,
}

/// Input parameters for working with an existing mint in CPI write mode
#[derive(Debug, Clone)]
pub struct WithMintCpiWriteInputs {
pub compressed_mint_inputs:
light_ctoken_types::instructions::mint_action::CompressedMintWithContext,
pub authority: Pubkey,
pub payer: Pubkey,
pub cpi_context_pubkey: Pubkey,
pub first_set_context: bool,
pub state_tree_index: u8,
pub input_queue_index: u8,
pub output_queue_index: u8,
pub assigned_account_index: u8,
}

impl MintActionInputsCpiWrite {
/// Create a new compressed mint for CPI write (starting point for new mints)
pub fn new_create_mint(inputs: CreateMintCpiWriteInputs) -> Self {
let cpi_context = light_ctoken_types::instructions::mint_action::CpiContext {
set_context: false, // For CPI write, we're reading from context
first_set_context: inputs.first_set_context,
in_tree_index: inputs.address_tree_index, // For create_mint, this is the address tree
in_queue_index: 0, // Not used for create_mint
out_queue_index: inputs.output_queue_index,
token_out_queue_index: 0, // Set when adding MintTo action
assigned_account_index: inputs.assigned_account_index,
read_only_address_trees: [0; 4],
address_tree_pubkey: light_ctoken_types::CMINT_ADDRESS_TREE,
};

Self {
compressed_mint_inputs: inputs.compressed_mint_inputs,
mint_seed: Some(inputs.mint_seed),
mint_bump: None,
create_mint: true,
authority: inputs.authority,
payer: inputs.payer,
actions: Vec::new(),
cpi_context,
cpi_context_pubkey: inputs.cpi_context_pubkey,
}
}

/// Start with an existing mint for CPI write (starting point for existing mints)
pub fn new_with_mint(inputs: WithMintCpiWriteInputs) -> Self {
let cpi_context = light_ctoken_types::instructions::mint_action::CpiContext {
set_context: false, // For CPI write, we're reading from context
first_set_context: inputs.first_set_context,
in_tree_index: inputs.state_tree_index,
in_queue_index: inputs.input_queue_index,
out_queue_index: inputs.output_queue_index,
token_out_queue_index: 0, // Set when adding MintTo action
assigned_account_index: inputs.assigned_account_index,
..Default::default()
};

Self {
compressed_mint_inputs: inputs.compressed_mint_inputs,
mint_seed: None,
mint_bump: None,
create_mint: false,
authority: inputs.authority,
payer: inputs.payer,
actions: Vec::new(),
cpi_context,
cpi_context_pubkey: inputs.cpi_context_pubkey,
}
}

/// Add MintTo action - mint tokens to compressed token accounts
/// Returns error if mint is decompressed (no SPL mint modifications in CPI write)
pub fn add_mint_to(
mut self,
recipients: Vec<MintToRecipient>,
token_account_version: u8,
token_out_queue_index: u8, // Index for token output queue
) -> Result<Self> {
// Cannot mint if the mint is decompressed
// In CPI write, we cannot modify SPL mint supply
if self
.compressed_mint_inputs
.mint
.metadata
.spl_mint_initialized
{
return Err(TokenSdkError::CannotMintWithDecompressedInCpiWrite);
}

// Set token_out_queue_index for the MintTo action
self.cpi_context.token_out_queue_index = token_out_queue_index;

self.actions.push(MintActionType::MintTo {
recipients,
token_account_version,
});
Ok(self)
}

/// Add UpdateMintAuthority action
pub fn add_update_mint_authority(mut self, new_authority: Option<Pubkey>) -> Self {
self.actions
.push(MintActionType::UpdateMintAuthority { new_authority });
self
}

/// Add UpdateFreezeAuthority action
pub fn add_update_freeze_authority(mut self, new_authority: Option<Pubkey>) -> Self {
self.actions
.push(MintActionType::UpdateFreezeAuthority { new_authority });
self
}

/// Add UpdateMetadataField action
pub fn add_update_metadata_field(
mut self,
extension_index: u8,
field_type: u8,
key: Vec<u8>,
value: Vec<u8>,
) -> Self {
self.actions.push(MintActionType::UpdateMetadataField {
extension_index,
field_type,
key,
value,
});
self
}

/// Add UpdateMetadataAuthority action
pub fn add_update_metadata_authority(
mut self,
extension_index: u8,
new_authority: Pubkey,
) -> Self {
self.actions.push(MintActionType::UpdateMetadataAuthority {
extension_index,
new_authority,
});
self
}

/// Add RemoveMetadataKey action
pub fn add_remove_metadata_key(
mut self,
extension_index: u8,
key: Vec<u8>,
idempotent: u8,
) -> Self {
self.actions.push(MintActionType::RemoveMetadataKey {
extension_index,
key,
idempotent,
});
self
}
}

/// Creates a mint action CPI write instruction (for use in CPI context)
pub fn mint_action_cpi_write(input: MintActionInputsCpiWrite) -> Result<Instruction> {
use light_ctoken_types::instructions::mint_action::MintActionCompressedInstructionData;
if input
.compressed_mint_inputs
.mint
.metadata
.spl_mint_initialized
|| input
.actions
.iter()
.any(|action| matches!(action, MintActionType::CreateSplMint { .. }))
{
return Err(TokenSdkError::CannotMintWithDecompressedInCpiWrite);
}
// Validate CPI context
if !input.cpi_context.first_set_context && !input.cpi_context.set_context {
return Err(TokenSdkError::InvalidAccountData);
}

// Convert high-level actions to program-level actions
let mut program_actions = Vec::new();
let create_mint = if input.create_mint {
Some(CreateMint::default())
} else {
None
};

let with_mint_signer = create_mint.is_some();

// Only require mint to sign when creating a new compressed mint
let mint_needs_to_sign = create_mint.is_some();

for action in input.actions {
match action {
MintActionType::MintTo {
recipients,
token_account_version,
} => {
let program_recipients: Vec<_> = recipients
.into_iter()
.map(
|r| light_ctoken_types::instructions::mint_action::Recipient {
recipient: r.recipient.to_bytes().into(),
amount: r.amount,
},
)
.collect();

program_actions.push(
light_ctoken_types::instructions::mint_action::Action::MintToCompressed(
light_ctoken_types::instructions::mint_action::MintToCompressedAction {
token_account_version,
recipients: program_recipients,
},
),
);
}
MintActionType::UpdateMintAuthority { new_authority } => {
program_actions.push(
light_ctoken_types::instructions::mint_action::Action::UpdateMintAuthority(
light_ctoken_types::instructions::mint_action::UpdateAuthority {
new_authority: new_authority.map(|auth| auth.to_bytes().into()),
},
),
);
}
MintActionType::UpdateFreezeAuthority { new_authority } => {
program_actions.push(
light_ctoken_types::instructions::mint_action::Action::UpdateFreezeAuthority(
light_ctoken_types::instructions::mint_action::UpdateAuthority {
new_authority: new_authority.map(|auth| auth.to_bytes().into()),
},
),
);
}
MintActionType::UpdateMetadataField {
extension_index,
field_type,
key,
value,
} => {
program_actions.push(
light_ctoken_types::instructions::mint_action::Action::UpdateMetadataField(
UpdateMetadataFieldAction {
extension_index,
field_type,
key,
value,
},
),
);
program_id: COMPRESSED_TOKEN_PROGRAM_ID.into(),
accounts: {
let mut account_metas = Vec::with_capacity(
6 + accounts.recipient_token_accounts.len()
+ if accounts.mint_signer.is_some() { 1 } else { 0 },
);

account_metas.push(solana_instruction::AccountMeta {
pubkey: accounts.light_system_program.key().into(),
is_writable: false,
is_signer: false,
});

if let Some(mint_signer) = accounts.mint_signer {
account_metas.push(solana_instruction::AccountMeta {
pubkey: mint_signer.key().into(),
is_writable: false,
is_signer: true,
});
}
MintActionType::UpdateMetadataAuthority {
extension_index,
new_authority,
} => {
program_actions.push(
light_ctoken_types::instructions::mint_action::Action::UpdateMetadataAuthority(
UpdateMetadataAuthorityAction {
extension_index,
new_authority: new_authority.to_bytes().into(),
},
),
);
}
MintActionType::RemoveMetadataKey {
extension_index,
key,
idempotent,
} => {
program_actions.push(
light_ctoken_types::instructions::mint_action::Action::RemoveMetadataKey(
RemoveMetadataKeyAction {
extension_index,
key,
idempotent,
},
),
);
}
_ => return Err(TokenSdkError::CannotMintWithDecompressedInCpiWrite),
}
}

let instruction_data = MintActionCompressedInstructionData {
create_mint,
leaf_index: input.compressed_mint_inputs.leaf_index,
prove_by_index: input.compressed_mint_inputs.prove_by_index,
root_index: input.compressed_mint_inputs.root_index,
compressed_address: input.compressed_mint_inputs.address,
mint: input.compressed_mint_inputs.mint,
token_pool_bump: 0, // Not used in CPI write context
token_pool_index: 0, // Not used in CPI write context
actions: program_actions,
proof: None, // No proof for CPI write context
cpi_context: Some(input.cpi_context),
};
account_metas.push(solana_instruction::AccountMeta {
pubkey: accounts.authority.key().into(),
is_writable: false,
is_signer: true,
});

account_metas.push(solana_instruction::AccountMeta {
pubkey: accounts.fee_payer.key().into(),
is_writable: true,
is_signer: true,
});

account_metas.push(solana_instruction::AccountMeta {
pubkey: accounts.cpi_authority_pda.key().into(),
is_writable: false,
is_signer: false,
});

account_metas.push(solana_instruction::AccountMeta {
pubkey: accounts.cpi_context.key().into(),
is_writable: true,
is_signer: false,
});

for acc in &accounts.recipient_token_accounts {
account_metas.push(solana_instruction::AccountMeta {
pubkey: acc.key().into(),
is_writable: true,
is_signer: false,
});
}

// Create account meta config for CPI write
let meta_config = MintActionMetaConfigCpiWrite {
fee_payer: input.payer,
mint_signer: if with_mint_signer {
input.mint_seed
} else {
None
account_metas
},
authority: input.authority,
cpi_context: input.cpi_context_pubkey,
mint_needs_to_sign,
};

// Get account metas
let accounts = get_mint_action_instruction_account_metas_cpi_write(meta_config);

// Serialize instruction data
let data_vec = instruction_data
.try_to_vec()
.map_err(|_| TokenSdkError::SerializationError)?;

Ok(Instruction {
program_id: Pubkey::new_from_array(light_ctoken_types::COMPRESSED_TOKEN_PROGRAM_ID),
accounts,
data: [vec![MINT_ACTION_DISCRIMINATOR], data_vec].concat(),
data,
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Tighten CPI-write invariants for MintToCToken and mint creation

In build_cpi_write_instruction you already reject Action::CreateSplMint in CPI-write mode, which is good. Two additional invariants would make this API harder to misuse:

  1. Ensure MintToCToken has recipient accounts in CPI-write calls

Right now Action::MintToCToken(_) is allowed with only a comment noting it requires recipient accounts:

Action::MintToCToken(_) => {
    // MintToCToken is allowed but needs recipient accounts
}

If CPI-write calls with MintToCToken but an empty recipient_token_accounts vector are invalid, it’s better to fail on the client:

    for action in &instruction_data.actions {
        match action {
            Action::CreateSplMint(_) => {
                msg!("CreateSplMint not supported in CPI write mode");
                return Err(TokenSdkError::CannotMintWithDecompressedInCpiWrite);
            }
-           Action::MintToCToken(_) => {
-               // MintToCToken is allowed but needs recipient accounts
-           }
+           Action::MintToCToken(_) => {
+               if accounts.recipient_token_accounts.is_empty() {
+                   msg!("MintToCToken requires at least one recipient token account in CPI write mode");
+                   return Err(TokenSdkError::InvalidAccountData);
+               }
+           }
            _ => {}
        }
    }
  1. (Optional) Enforce mint_signer presence when creating a compressed mint in CPI-write mode

If instruction_data.create_mint.is_some() in CPI-write mode implies a required mint_signer account, adding an explicit check before building metas would surface misconfigurations earlier, rather than relying on on-chain failures.

These checks keep CPI-write usage aligned with on-chain expectations and make mis-wiring more obvious during integration.

-->

🤖 Prompt for AI Agents
In sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs
around lines 84 to 167, add proactive validation before serializing/building the
Instruction: if any Action::MintToCToken exists in instruction_data.actions,
return an error when accounts.recipient_token_accounts.is_empty(); additionally,
if instruction_data.create_mint.is_some() then require
accounts.mint_signer.is_some() and return an appropriate TokenSdkError when
missing. Place these checks after the action loop (or immediately before
serializing data) so you fail early with clear errors instead of producing an
invalid CPI write instruction.

Comment on lines +48 to 50
/// Required if any action is creating a mint
pub new_mint: Option<NewMint>,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Clarify new_mint / create_spl_mint semantics in the helper API

MintActionParams::new_mint is documented as:

/// Required if any action is creating a mint
pub new_mint: Option<NewMint>,

while create_comprehensive_mint_action_instruction keeps a create_spl_mint: bool parameter but now immediately errors if it is true:

if create_spl_mint {
    return Err(RpcError::CustomError(
        "CreateSplMint is no longer supported".to_string(),
    ));
}

Given that:

  • new_mint is what actually drives the “create compressed mint” flow in create_mint_action_instruction, and
  • CreateSplMint has been explicitly dropped from this helper,

the current comments/parameter naming are a bit misleading for callers migrating off the old API.

I’d recommend:

  • Updating the doc comment on new_mint to explicitly say it is what enables compressed mint creation (and that SPL mint creation is no longer supported through this helper).
  • Optionally deprecating or clearly documenting create_spl_mint as a no-op flag that always errors, so downstream users know to stop setting it and rely solely on new_mint.

This will make the new behaviour obvious to SDK consumers and avoid confusion around CreateSplMint.
-->

Also applies to: 299-301, 310-313

Comment on lines 171 to 203
let mut ctoken_accounts = Vec::new();
let mut ctoken_account_index = 0u8;

for action in params.actions {
instruction_data = match action {
MintActionType::MintTo {
recipients,
token_account_version,
} => {
// Convert MintToRecipient (solana_sdk::Pubkey) to Recipient ([u8; 32])
let ctoken_recipients: Vec<Recipient> = recipients
.into_iter()
.map(|r| Recipient {
recipient: r.recipient.to_bytes().into(),
amount: r.amount,
})
.collect();
instruction_data.with_mint_to_compressed(MintToCompressedAction {
token_account_version,
recipients: ctoken_recipients,
})
}
MintActionType::MintToCToken { account, amount } => {
// Add account to the list and use its index
ctoken_accounts.push(account);
let current_index = ctoken_account_index;
ctoken_account_index += 1;

let token_pool = if needs_token_pool {
let mint = find_spl_mint_address(&params.mint_seed).0;
Some(derive_token_pool(&mint, 0))
instruction_data.with_mint_to_ctoken(MintToCTokenAction {
account_index: current_index,
amount,
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard against ctoken_account_index u8 overflow when many MintToCToken actions are present

ctoken_account_index is a u8 that is incremented for every MintToCToken action:

let mut ctoken_account_index = 0u8;
// ...
ctoken_accounts.push(account);
let current_index = ctoken_account_index;
ctoken_account_index += 1;

If a caller ever supplies >255 MintToCToken actions in a single instruction, this will wrap around in release builds and produce incorrect indices, corrupting the mapping between actions and ctoken_accounts.

You can make this safe and simpler by deriving the index from the vector length with a checked conversion:

-            MintActionType::MintToCToken { account, amount } => {
-                // Add account to the list and use its index
-                ctoken_accounts.push(account);
-                let current_index = ctoken_account_index;
-                ctoken_account_index += 1;
-
-                instruction_data.with_mint_to_ctoken(MintToCTokenAction {
-                    account_index: current_index,
-                    amount,
-                })
-            }
+            MintActionType::MintToCToken { account, amount } => {
+                // Add account to the list and use its index (bounded by u8::MAX)
+                let current_index = u8::try_from(ctoken_accounts.len())
+                    .map_err(|_| {
+                        RpcError::CustomError(
+                            "Too many MintToCToken accounts in a single instruction (max 255)"
+                                .to_string(),
+                        )
+                    })?;
+                ctoken_accounts.push(account);
+
+                instruction_data.with_mint_to_ctoken(MintToCTokenAction {
+                    account_index: current_index,
+                    amount,
+                })
+            }

This fails fast with a clear client-side error instead of silently generating bogus indices.
-->

🤖 Prompt for AI Agents
In sdk-libs/token-client/src/instructions/mint_action.rs around lines 171 to
203, the code uses a u8 counter ctoken_account_index that increments for each
MintToCToken action and can overflow silently; replace that manual counter by
deriving the index from ctoken_accounts.len() with a checked conversion to u8
(e.g. use u8::try_from(ctoken_accounts.len()) or similar) before pushing the new
account, return or propagate a clear error if the conversion fails (meaning >255
entries), then push the account and use the computed index for the
MintToCTokenAction; remove the manual incrementing variable to avoid wraparound.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
program-libs/compressed-account/src/pubkey.rs (4)

209-214: Adding a required method to AsPubkey is a breaking change; consider a default impl for to_light_pubkey.

Introducing fn to_light_pubkey(&self) -> Pubkey; as a required method on the public AsPubkey trait will break any external crates that implement AsPubkey but don’t yet define this method.

You can make this extension backward‑compatible and also remove duplication in the impls by providing a default body that reuses to_pubkey_bytes:

 pub trait AsPubkey {
     fn to_pubkey_bytes(&self) -> [u8; 32];
     #[cfg(feature = "anchor")]
     fn to_anchor_pubkey(&self) -> anchor_lang::prelude::Pubkey;
-    fn to_light_pubkey(&self) -> Pubkey;
+    fn to_light_pubkey(&self) -> Pubkey {
+        Pubkey::from(self.to_pubkey_bytes())
+    }
 }

With this, existing downstream impls keep compiling, and most of the explicit to_light_pubkey impls in this file become redundant unless you want to micro‑opt specific cases.


216-227: AsPubkey for Pubkey impl is correct; can be simplified if a default is added.

to_light_pubkey returning *self is correct and efficient since Pubkey is Copy. If you adopt a default implementation on the trait (as suggested above), this method becomes unnecessary boilerplate and can be safely dropped for clarity.


229-242: Anchor Pubkey conversion is semantically correct and consistent.

to_light_pubkey for anchor_lang::prelude::Pubkey using self.to_bytes().into() correctly funnels through the existing [u8; 32] -> Pubkey conversion and keeps the semantics aligned with to_pubkey_bytes. If you add a default to_light_pubkey on the trait, this explicit impl becomes redundant but is still fine to keep for readability.


254-265: [u8; 32] implementation is correct and leverages existing From impls.

to_light_pubkey delegating to self.into() is correct, since From<&[u8; 32]> for Pubkey is defined above. If you move to a default to_light_pubkey based on to_pubkey_bytes, this method can be removed entirely without changing semantics.

sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (1)

8-25: Strengthened config invariants; double‑check spl_mint_initialized handling for existing mints

Making fee_payer a required Pubkey and dropping Default on MintActionMetaConfig is a solid move: it forces all constructors to supply a real payer and avoids “partially initialized” configs being used by accident.

One thing to verify: MintActionMetaConfig::new always sets spl_mint_initialized: false, ignoring instruction_data.mint.metadata.spl_mint_initialized, whereas new_create_mint forwards that flag from the instruction data. If new is ever used for decompressed SPL‑mint flows, you probably want to propagate the metadata flag similarly so that:

if config.spl_mint_initialized {
    // add SPL mint + token pool + spl_token_2022 program metas
}

behaves consistently. If new is strictly for “pure compressed” flows, a short doc comment on new (or on spl_mint_initialized) stating that expectation would make this clearer to future callers.

Also applies to: 69-107

♻️ Duplicate comments (5)
sdk-libs/token-client/src/instructions/mint_action.rs (2)

171-199: Guard against ctoken_account_index u8 overflow for many MintToCToken actions

ctoken_account_index is a u8 that you increment per MintToCToken action; with >255 such actions in one instruction this will wrap and corrupt the mapping between account_index and ctoken_accounts. Deriving the index from the current vector length with a hard cap avoids silent wraparound:

-    // Convert and add actions using builder pattern
-    // Collect decompressed token accounts for MintToCToken actions
-    let mut ctoken_accounts = Vec::new();
-    let mut ctoken_account_index = 0u8;
+    // Convert and add actions using builder pattern
+    // Collect decompressed token accounts for MintToCToken actions
+    let mut ctoken_accounts = Vec::new();
@@
-            MintActionType::MintToCToken { account, amount } => {
-                // Add account to the list and use its index
-                ctoken_accounts.push(account);
-                let current_index = ctoken_account_index;
-                ctoken_account_index += 1;
-
-                instruction_data.with_mint_to_ctoken(MintToCTokenAction {
-                    account_index: current_index,
-                    amount,
-                })
-            }
+            MintActionType::MintToCToken { account, amount } => {
+                // Add account to the list and use its index, bounded by u8::MAX
+                if ctoken_accounts.len() >= u8::MAX as usize {
+                    return Err(RpcError::CustomError(
+                        "Too many MintToCToken accounts in a single instruction (max 255)"
+                            .to_string(),
+                    ));
+                }
+                let current_index = ctoken_accounts.len() as u8;
+                ctoken_accounts.push(account);
+
+                instruction_data.with_mint_to_ctoken(MintToCTokenAction {
+                    account_index: current_index,
+                    amount,
+                })
+            }

This fails fast with a clear client error instead of emitting an instruction with bogus indices.


48-50: Clarify new_mint vs create_spl_mint semantics in the public API

With the new builder flow, new_mint is what actually drives compressed mint creation, while create_spl_mint in create_comprehensive_mint_action_instruction is now an always-unsupported flag that immediately errors. The current doc on new_mint (“Required if any action is creating a mint”) plus the lingering create_spl_mint parameter are confusing for SDK consumers migrating from the old API; I’d suggest explicitly documenting new_mint as “required when creating a compressed mint via this helper (SPL mint creation is no longer supported here)” and either deprecating or clearly documenting create_spl_mint as a legacy parameter that always errors so callers know to rely solely on new_mint.

Also applies to: 295-308

sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (1)

47-85: Consider centralizing CPI-write invariants alongside build_cpi_write_instruction

Right now build_cpi_write_instruction blindly serializes instruction_data and uses only the CPI-write account set, without checking that the actions are compatible with CPI-write (e.g., no disallowed variants, and recipient_token_accounts present when MintToCToken actions are included). If those invariants aren’t enforced elsewhere anymore, it’s worth reintroducing lightweight validation here—right before serialization—so obviously invalid CPI-write combinations fail fast on the client instead of reaching the on-chain program.

Also applies to: 88-153

sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (2)

109-143: CPI context ctor leaves tree_pubkey/output_queue at Pubkey::default() with no safe way to set them

new_cpi_context currently initializes:

tree_pubkey: Pubkey::default(),  // Must be set with chainable method
input_queue: None,               // Set with chainable method if not create_mint
output_queue: Pubkey::default(), // Must be set with chainable method

but the only chainable methods on MintActionMetaConfig are with_tokens_out_queue and with_ctoken_accounts. In practice, callers either have to:

  • Mutate the public fields directly, or
  • Risk calling get_mint_action_instruction_account_metas with tree_pubkey/output_queue left as all‑zero keys, which will produce structurally valid but semantically bogus metas.

Since tree_pubkey and output_queue are always consumed later, this is an easy footgun in CPI flows.

I’d strongly recommend:

  • Adding explicit setters like:
pub fn with_tree_pubkey(mut self, tree: Pubkey) -> Self { self.tree_pubkey = tree; self }
pub fn with_input_queue(mut self, q: Pubkey) -> Self { self.input_queue = Some(q); self }
pub fn with_output_queue(mut self, q: Pubkey) -> Self { self.output_queue = q; self }

and updating these comments to point to them, or

  • If you prefer field mutation, change the comments to say “must be set by caller before building metas” and add a guard in get_mint_action_instruction_account_metas (e.g. assert tree_pubkey != Pubkey::default()/output_queue != Pubkey::default()).

Failing fast here is much easier to debug than chasing runtime program failures caused by zero pubkeys.


145-155: Make the MintToCToken account requirements explicit and fail fast when ctoken_accounts is missing

analyze_actions intentionally cannot populate ctoken_accounts for MintToCToken because the on‑chain action only stores indices:

let (has_mint_to_actions, ctoken_accounts) = Self::analyze_actions(&instruction_data.actions);
// ...
for token_account in &config.ctoken_accounts {
    metas.push(AccountMeta::new(*token_account, false));
}

Given ctoken_accounts is always initialized as an empty Vec here, any MintToCToken flow requires the caller to invoke with_ctoken_accounts (or otherwise fill ctoken_accounts) before building metas. If they forget, the instruction will still be built, but without the decompressed token accounts the program expects, leading to opaque on‑chain failures.

To make this safer and more self‑documenting:

  • Update the docs on with_ctoken_accounts (and/or on MintActionMetaConfig itself) to clearly state that whenever MintToCToken actions are present, callers MUST provide the decompressed token pubkeys via ctoken_accounts; they are not derived from actions.
  • Optionally (recommended), add a guard before emitting metas, e.g.:
if config.has_mint_to_actions
    && instruction_data.actions.iter().any(|a| matches!(a, Action::MintToCToken(_)))
    && config.ctoken_accounts.is_empty()
{
    panic!("MintToCToken actions present but no ctoken_accounts supplied");
    // or return a Result from the meta builder if you prefer not to panic
}

so that missing accounts surface as an immediate, descriptive failure rather than a mysterious program error.

Also applies to: 157-180, 315-318

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 467c7e6 and c5d0080.

⛔ Files ignored due to path filters (11)
  • program-tests/compressed-token-test/tests/mint/failing.rs is excluded by none and included by none
  • program-tests/compressed-token-test/tests/transfer2/compress_failing.rs is excluded by none and included by none
  • program-tests/compressed-token-test/tests/transfer2/decompress_failing.rs is excluded by none and included by none
  • program-tests/compressed-token-test/tests/transfer2/no_system_program_cpi_failing.rs is excluded by none and included by none
  • program-tests/compressed-token-test/tests/transfer2/transfer_failing.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/src/pda_ctoken/mint.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/tests/compress_and_close_cpi.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/tests/ctoken_pda.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/tests/decompress_full_cpi.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/tests/pda_ctoken.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs is excluded by none and included by none
📒 Files selected for processing (7)
  • program-libs/compressed-account/src/pubkey.rs (4 hunks)
  • sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (3 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs (9 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (1 hunks)
  • sdk-libs/compressed-token-sdk/tests/mint_action_cpi_accounts_tests.rs (8 hunks)
  • sdk-libs/token-client/src/instructions/mint_action.rs (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
sdk-libs/compressed-token-sdk/tests/mint_action_cpi_accounts_tests.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs (3)
  • try_from_account_infos (202-204)
  • try_from_account_infos_full (78-197)
  • create_mint (22-28)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (2)
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (3)
  • instruction (30-33)
  • instruction_write_to_cpi_context_first (45-48)
  • instruction_write_to_cpi_context_set (59-62)
program-libs/ctoken-types/src/instructions/mint_action/cpi_context.rs (1)
  • default (25-37)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (2)
sdk-libs/token-client/src/actions/mint_action.rs (1)
  • mint_action (25-60)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs (1)
  • create_mint (22-28)
sdk-libs/token-client/src/instructions/mint_action.rs (5)
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (2)
  • derive_compressed_mint_address (217-226)
  • find_spl_mint_address (236-241)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (3)
  • get_mint_action_instruction_account_metas (185-321)
  • new (70-107)
  • new_create_mint (29-67)
sdk-libs/token-client/src/actions/mint_action.rs (1)
  • mint_action (25-60)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (2)
  • new_mint (88-107)
  • new (43-60)
program-libs/ctoken-types/src/instructions/mint_action/mint_to_compressed.rs (2)
  • new (14-19)
  • new (30-35)
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (3)
  • instruction (22-45)
  • instruction_write_to_cpi_context_first (47-65)
  • instruction_write_to_cpi_context_set (67-85)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: stateless-js-v1
  • GitHub Check: Forester e2e test
  • GitHub Check: stateless-js-v2
  • GitHub Check: programs (compressed-token-and-e2e, ["cargo-test-sbf -p compressed-token-test --test v1", "cargo-...
  • GitHub Check: programs (light-system-program-address, ["cargo-test-sbf -p system-test -- test_with_address", "c...
  • GitHub Check: programs (account-compression-and-registry, ["cargo-test-sbf -p account-compression-test", "cargo...
  • GitHub Check: programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test", "cargo test -p light-system-prog...
  • GitHub Check: programs (system-cpi-test-v2-functional-account-infos, ["cargo-test-sbf -p system-cpi-v2-test -- ...
  • GitHub Check: programs (system-cpi-test-v2-functional-read-only, ["cargo-test-sbf -p system-cpi-v2-test -- func...
  • GitHub Check: Test program-libs-slow
  • GitHub Check: Test program-libs-fast
  • GitHub Check: Test batched-merkle-tree-simulate
🔇 Additional comments (2)
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1)

4-62: Well-factored CTokenInstruction trait abstraction

The trait cleanly separates execute vs CPI-write account sets via GATs and gives a uniform API for instruction construction; the bounds and documentation line up with how MintActionCompressedInstructionData implements it, and I don’t see any correctness or ergonomics issues here.

sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (1)

15-45: CTokenInstruction implementation for mint_action is consistent and defensive

The implementation correctly wires MintActionCompressedInstructionData into the generic CTokenInstruction surface, and the explicit guard that rejects pre-set CPI-write flags in instruction() before serializing helps catch misuse early on the client side. Account metas are delegated to MintActionCpiAccounts::to_account_metas, keeping execute-mode wiring centralized.

Comment on lines +14 to +37
#[derive(Debug, Clone, Default, Copy)]
pub struct MintActionCpiAccountsConfig {
pub with_cpi_context: bool,
pub create_mint: bool, // true = address tree, false = state tree
pub mint_to_compressed: bool, // true = tokens_out_queue required
}

impl MintActionCpiAccountsConfig {
pub fn create_mint() -> Self {
Self {
with_cpi_context: false,
create_mint: true,
mint_to_compressed: false,
}
}

pub fn mint_to_compressed(self) -> Self {
Self {
with_cpi_context: self.with_cpi_context,
create_mint: self.create_mint,
mint_to_compressed: true,
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Config-driven CPI account parsing is solid; next step is deriving config from instruction data

MintActionCpiAccountsConfig cleanly controls which optional accounts are expected (mint_signer, cpi_context, in_output_queue, tokens_out_queue), and the updated try_from_account_infos_full / to_account_metas paths line up with that model and with the tests. To reduce the chance of config callsites drifting from what a given MintActionCompressedInstructionData actually requires, a natural follow-up would be a small helper that derives the appropriate MintActionCpiAccountsConfig from the instruction data (closing the existing TODO and keeping the parsing rules centralized).

Also applies to: 79-197, 199-205, 278-380

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
sdk-libs/compressed-token-sdk/tests/mint_action_cpi_accounts_tests.rs (1)

245-261: Config‑driven CPI account tests are thorough; tighten error assertions

The new tests for “all options”, create_mint(), explicit update config, invalid queue ownership, and helper methods exercise the main branches of MintActionCpiAccountsConfig and the helper APIs well. The only nit is the repeated pattern:

assert!(result.is_err());
assert!(result.is_err());

in several failure tests, which is redundant and could instead assert on the specific error type or message to better lock in expected behaviour.

Also applies to: 337-345, 426-439, 854-861, 930-952

sdk-libs/token-client/src/instructions/mint_action.rs (1)

155-171: Avoid panics from unwrap() when deserialising existing compressed mints

Both unwrap() calls in the existing mint branch (lines 166 and 185) will panic if RPC returns unexpected data:

  • Line 166: compressed_mint_account.data.unwrap() panics if the account lacks data
  • Line 185: compressed_mint.try_into().unwrap() panics if the type conversion fails

This is inconsistent with your error handling pattern elsewhere in the file (e.g., line 169's map_err() for deserialization). Since this is client-facing SDK code, panics crash the caller's application when the RPC layer returns malformed responses.

Use proper error propagation instead:

let account_data = compressed_mint_account
    .data
    .as_ref()
    .ok_or_else(|| RpcError::CustomError("Compressed mint account missing data".to_string()))?;

let mut data_slice = account_data.data.as_slice();
let compressed_mint: CompressedMint =
    BorshDeserialize::deserialize(&mut data_slice).map_err(|e| {
        RpcError::CustomError(format!("Failed to deserialize compressed mint: {}", e))
    })?;

let mint = compressed_mint
    .try_into()
    .map_err(|e| RpcError::CustomError(format!("Failed to convert compressed mint: {:?}", e)))?;

let compressed_mint_inputs = CompressedMintWithContext {
    prove_by_index: rpc_proof_result.accounts[0].root_index.proof_by_index(),
    leaf_index: compressed_mint_account.leaf_index,
    root_index: rpc_proof_result.accounts[0]
        .root_index
        .root_index()
        .unwrap_or_default(),
    address: params.compressed_mint_address,
    mint,
};

Also applies to: lines 177–187

♻️ Duplicate comments (5)
sdk-libs/token-client/src/instructions/mint_action.rs (2)

88-90: Clarify new_mint (compressed mint) semantics and de‑emphasise create_spl_mint

MintActionParams::new_mint is still documented only as “Required if any action is creating a mint”, while create_comprehensive_mint_action_instruction now rejects create_spl_mint = true outright and effectively only supports compressed mint creation through new_mint.

To make this unambiguous for callers:

  • Update the new_mint doc to say it controls compressed mint creation and that SPL mint creation is no longer supported here.
  • Adjust or deprecate the create_spl_mint flag and its doc comment (which still implies it’s the driver for mint creation) so it’s clearly a legacy parameter that always errors.

This prevents confusion for clients migrating from the old CreateSplMint path.

Also applies to: 323-336, 345-348


210-237: Guard against u8 overflow in MintToCToken account indices

ctoken_account_index is a u8 incremented for every MintToCToken action. With >255 actions, this will wrap in release builds and silently produce incorrect account_index values, breaking the mapping between MintToCTokenAction.account_index and ctoken_accounts.

Derive the index from the vector length with a checked conversion and drop the manual counter:

-    // Collect decompressed token accounts for MintToCToken actions
-    let mut ctoken_accounts = Vec::new();
-    let mut ctoken_account_index = 0u8;
+    // Collect decompressed token accounts for MintToCToken actions
+    let mut ctoken_accounts = Vec::new();
@@
-            MintActionType::MintToCToken { account, amount } => {
-                // Add account to the list and use its index
-                ctoken_accounts.push(account);
-                let current_index = ctoken_account_index;
-                ctoken_account_index += 1;
-
-                instruction_data.with_mint_to_ctoken(MintToCTokenAction {
-                    account_index: current_index,
-                    amount,
-                })
-            }
+            MintActionType::MintToCToken { account, amount } => {
+                // Derive the index from current length; enforce u8::MAX bound
+                let current_index = u8::try_from(ctoken_accounts.len())
+                    .map_err(|_| {
+                        RpcError::CustomError(
+                            "Too many MintToCToken accounts in a single instruction (max 255)"
+                                .to_string(),
+                        )
+                    })?;
+
+                ctoken_accounts.push(account);
+
+                instruction_data.with_mint_to_ctoken(MintToCTokenAction {
+                    account_index: current_index,
+                    amount,
+                })
+            }

This fails fast with a clear client‑side error instead of silently corrupting indices.

sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (1)

47-85: Tighten CPI‑write invariants and reuse base meta construction

The CPI‑write helpers correctly flip cpi_context.{first_set_context,set_context} and build_cpi_write_instruction emits metas in the same order as get_mint_action_instruction_account_metas_cpi_write, which is good. What’s missing is any validation that the actions in instruction_data are compatible with the CPI‑write accounts you pass:

  • If instruction_data.actions contains MintToCToken but accounts.recipient_token_accounts is empty, you’ll still build an instruction that only fails on‑chain.
  • If instruction_data.create_mint.is_some() (or a CreateSplMint action exists) but accounts.mint_signer is None, you again emit an instruction that can’t possibly succeed.

To make this API safer and restore the invariants you previously had, consider:

  • Before serializing instruction_data, loop over instruction_data.actions and:

    • Reject Action::MintToCToken(_) when accounts.recipient_token_accounts.is_empty() with a clear TokenSdkError::InvalidAccountData (or similar).
    • If you still intend to forbid Action::CreateSplMint in CPI‑write, explicitly error out with TokenSdkError::CannotMintWithDecompressedInCpiWrite; otherwise, assert the required mint_signer is present.
  • Add a separate check like:

    if instruction_data.create_mint.is_some() && accounts.mint_signer.is_none() {
        msg!("create_mint in CPI write mode requires mint_signer account");
        return Err(ProgramError::from(TokenSdkError::InvalidAccountData));
    }
  • (Optional) To avoid drift, factor the common [light_system_program, mint_signer, authority, fee_payer, cpi_authority_pda, cpi_context] metas into a shared helper used both here and in get_mint_action_instruction_account_metas_cpi_write, then append recipient_token_accounts on top.

This way mis‑wired CPI‑write calls fail early on the client instead of only at runtime on‑chain, and your two CPI‑write meta builders stay in lock‑step.

Also applies to: 90-152

sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (2)

109-143: CPI‑context config leaves critical fields as default Pubkey::default() with no setters

new_cpi_context initializes:

  • tree_pubkey to Pubkey::default()
  • input_queue to None
  • output_queue to Pubkey::default()
  • tokens_out_queue to None

with comments like “Must be set with chainable method”, but there are currently no chainable setters for tree_pubkey, input_queue, or output_queue. That makes it easy to accidentally build metas with 0 pubkeys, and the comments don’t match the API surface.

I’d suggest one of:

  • Add explicit builders:

    pub fn with_tree_pubkey(mut self, tree: Pubkey) -> Self {
        self.tree_pubkey = tree;
        self
    }
    
    pub fn with_input_queue(mut self, queue: Pubkey) -> Self {
        self.input_queue = Some(queue);
        self
    }
    
    pub fn with_output_queue(mut self, queue: Pubkey) -> Self {
        self.output_queue = queue;
        self
    }

    and update the comments to point at these methods; or

  • If you prefer direct field mutation, change the comments to say callers must set these public fields before calling get_mint_action_instruction_account_metas, and add a runtime check there (e.g. error if tree_pubkey == Pubkey::default() or output_queue == Pubkey::default() when with_cpi_context.is_some()).

This makes CPI‑context usage much harder to get wrong.


151-180: Make MintToCToken / ctoken_accounts contract explicit and fail fast

analyze_actions intentionally returns an empty ctoken_accounts vector even when Action::MintToCToken is present, and with_ctoken_accounts is the only way to populate it. That’s correct given that the on‑chain action only stores indices, but:

  • Callers can easily assume ctoken_accounts is derived from actions.
  • If there are MintToCToken actions but ctoken_accounts is left empty, get_mint_action_instruction_account_metas will emit no extra accounts (lines 315‑318) and the on‑chain program will fail in a surprising way.

Two improvements would make this API clearer and safer:

  1. Document the requirement

    Expand the with_ctoken_accounts doc comment (and/or the MintActionMetaConfig struct docs) to explicitly state that callers must supply decompressed token Pubkeys whenever MintToCToken actions are present, otherwise the constructed metas will be invalid.

  2. Optionally add a runtime check

    Consider extending analyze_actions to also return a has_mint_to_ctoken flag, then, before building metas, assert that when that flag is true, ctoken_accounts is non‑empty, e.g.:

    if has_mint_to_ctoken && config.ctoken_accounts.is_empty() {
        return Err(TokenSdkError::InvalidAccountData.into());
    }

    or at least debug_assert! with a clear message in internal helpers.

This will save users from subtle runtime failures in MintToCToken flows.

Also applies to: 315-318

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 467c7e6 and 7889290.

⛔ Files ignored due to path filters (15)
  • program-tests/compressed-token-test/tests/mint/edge_cases.rs is excluded by none and included by none
  • program-tests/compressed-token-test/tests/mint/failing.rs is excluded by none and included by none
  • program-tests/compressed-token-test/tests/mint/random.rs is excluded by none and included by none
  • program-tests/compressed-token-test/tests/transfer2/compress_failing.rs is excluded by none and included by none
  • program-tests/compressed-token-test/tests/transfer2/decompress_failing.rs is excluded by none and included by none
  • program-tests/compressed-token-test/tests/transfer2/no_system_program_cpi_failing.rs is excluded by none and included by none
  • program-tests/compressed-token-test/tests/transfer2/shared.rs is excluded by none and included by none
  • program-tests/compressed-token-test/tests/transfer2/transfer_failing.rs is excluded by none and included by none
  • program-tests/utils/src/assert_mint_action.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/src/pda_ctoken/mint.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/tests/compress_and_close_cpi.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/tests/ctoken_pda.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/tests/decompress_full_cpi.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/tests/pda_ctoken.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs is excluded by none and included by none
📒 Files selected for processing (11)
  • program-libs/compressed-account/src/pubkey.rs (4 hunks)
  • sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1 hunks)
  • sdk-libs/compressed-token-sdk/src/error.rs (2 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (3 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs (9 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (1 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mint_action/mod.rs (0 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mod.rs (1 hunks)
  • sdk-libs/compressed-token-sdk/tests/mint_action_cpi_accounts_tests.rs (8 hunks)
  • sdk-libs/token-client/src/actions/mint_action.rs (2 hunks)
  • sdk-libs/token-client/src/instructions/mint_action.rs (4 hunks)
💤 Files with no reviewable changes (1)
  • sdk-libs/compressed-token-sdk/src/instructions/mint_action/mod.rs
🧰 Additional context used
🧬 Code graph analysis (7)
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (3)
  • instruction (22-45)
  • instruction_write_to_cpi_context_first (47-65)
  • instruction_write_to_cpi_context_set (67-85)
sdk-libs/token-client/src/actions/mint_action.rs (2)
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (1)
  • derive_compressed_mint_address (217-226)
sdk-libs/token-client/src/instructions/mint_action.rs (1)
  • create_mint_action_instruction (93-321)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (3)
sdk-libs/token-client/src/actions/mint_action.rs (1)
  • mint_action (24-59)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
  • new (43-60)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs (1)
  • create_mint (22-28)
sdk-libs/compressed-token-sdk/src/instructions/mod.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (2)
  • get_mint_action_instruction_account_metas (185-321)
  • get_mint_action_instruction_account_metas_cpi_write (335-374)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (2)
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (3)
  • instruction (30-33)
  • instruction_write_to_cpi_context_first (45-48)
  • instruction_write_to_cpi_context_set (59-62)
program-libs/ctoken-types/src/instructions/mint_action/cpi_context.rs (1)
  • default (25-37)
sdk-libs/compressed-token-sdk/tests/mint_action_cpi_accounts_tests.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs (3)
  • try_from_account_infos (202-204)
  • try_from_account_infos_full (78-197)
  • create_mint (22-28)
sdk-libs/token-client/src/instructions/mint_action.rs (4)
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (2)
  • derive_compressed_mint_address (217-226)
  • find_spl_mint_address (236-241)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (3)
  • get_mint_action_instruction_account_metas (185-321)
  • new (70-107)
  • new_create_mint (29-67)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (2)
  • new_mint (88-107)
  • new (43-60)
program-libs/ctoken-types/src/instructions/mint_action/mint_to_compressed.rs (2)
  • new (14-19)
  • new (30-35)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Test batched-merkle-tree-simulate
  • GitHub Check: Test program-libs-fast
  • GitHub Check: Test program-libs-slow
  • GitHub Check: Forester e2e test
  • GitHub Check: programs (system-cpi-test-v2-functional-account-infos, ["cargo-test-sbf -p system-cpi-v2-test -- ...
  • GitHub Check: programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test", "cargo test -p light-system-prog...
  • GitHub Check: programs (system-cpi-test-v2-functional-read-only, ["cargo-test-sbf -p system-cpi-v2-test -- func...
  • GitHub Check: programs (account-compression-and-registry, ["cargo-test-sbf -p account-compression-test", "cargo...
🔇 Additional comments (15)
sdk-libs/compressed-token-sdk/src/error.rs (2)

60-63: LGTM! Clear validation error variants.

The new error variants appropriately distinguish between creating a new mint (which requires create_mint data) and using an existing mint (where create_mint must be None). The error messages reference specific method names, making debugging straightforward.


116-117: LGTM! Proper sequential error code assignment.

The error codes 17024 and 17025 follow the existing sequence correctly, and the mappings are complete.

sdk-libs/token-client/src/actions/mint_action.rs (2)

5-5: Import consolidation looks good.

The import path update aligns with the refactored SDK structure, making the API surface cleaner.


12-14: Clean import consolidation.

Grouping related imports improves readability and follows idiomatic Rust style.

program-libs/compressed-account/src/pubkey.rs (5)

224-226: LGTM: Optimal implementation for Copy type.

Returning *self is the most efficient approach since Pubkey implements Copy.


239-241: LGTM: Correct conversion through bytes.

The conversion chain to_bytes() -> [u8; 32] -> Pubkey leverages the existing From<[u8; 32]> implementation and is correctly feature-gated.


249-251: LGTM: Consistent conversion pattern.

Uses the same conversion approach as the Anchor implementation, appropriately gated for the non-Anchor Solana use case.


262-264: LGTM: Clean conversion from byte array.

The .into() call correctly uses the From<&[u8; 32]> implementation defined at lines 164-168.


209-214: All AsPubkey implementations updated—concern addressed.

Verification confirms the new required method to_light_pubkey() has been added to all four AsPubkey trait implementations in the codebase:

  • Pubkey (line 224)
  • anchor_lang::prelude::Pubkey (line 239)
  • solana_pubkey::Pubkey (line 249)
  • [u8; 32] (line 262)

The method is actively used (e.g., ctoken-types/src/instructions/mint_action/mint_to_compressed.rs:16). No broken implementations remain within the workspace.

While adding a required method is technically a breaking change for any external crates implementing AsPubkey, the PR has properly handled all implementations in scope here. If this is a public API, the team should follow semantic versioning practices separately.

sdk-libs/compressed-token-sdk/src/instructions/mod.rs (1)

42-48: Mint action re-exports match the new builder/config API surface

The updated mint_action and mint_to_compressed re-exports cleanly expose the new meta/config types and helpers while dropping legacy inputs; this module now reflects the intended public API without leaks of the old path.

sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1)

4-63: CTokenInstruction trait cleanly separates instruction data from invocation

The trait design (separate execute/CPI account types, three explicit build modes, self-by-value semantics) matches the new builder pattern and keeps instruction construction decoupled from how/where it’s invoked. No issues from a correctness or ergonomics standpoint.

sdk-libs/compressed-token-sdk/tests/mint_action_cpi_accounts_tests.rs (1)

4-148: Minimal CPI accounts test correctly tracks new default config behaviour

Pulling in MintActionCpiAccountsConfig and extending the “minimal” fixture with in_output_queue plus the parsed.in_output_queue.is_some() assertion lines everything up with the default (create_mint = false) parsing rules in try_from_account_infos_full. This is a solid guard against regressions in the base config.

sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs (1)

14-204: Config‑based MintActionCpiAccounts parsing and metas look consistent and robust

MintActionCpiAccountsConfig plus the refactored try_from_account_infos_full, to_account_infos, and to_account_metas give a clear, extensible way to control optional accounts (mint_signer, CPI context, in_output_queue, tokens_out_queue) without hard‑coding booleans. The account order and writability/signature flags in to_account_metas match the expectations exercised in tests and the CPI write helpers, and the default config in try_from_account_infos cleanly represents the non‑create‑mint, no‑CPI‑context case.

Also applies to: 223-380

sdk-libs/token-client/src/instructions/mint_action.rs (1)

95-153: Builder‑based mint action construction and meta config wiring look solid

The overall flow—branching on new_mint for create vs update, fetching the appropriate proof, building MintActionCompressedInstructionData via .new_mint/.new and the .with_* methods, then feeding that into MintActionMetaConfig::new_create_mint / ::new and get_mint_action_instruction_account_metas—is coherent and matches the on‑chain account/meta expectations surfaced in the SDK. Aside from the specific issues called out above, this is a clean migration away from the older MintActionInputs path.

Also applies to: 195-207, 279-321

sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (1)

22-45: Non‑CPI instruction() guard on CPI context looks solid

The explicit check that rejects cpi_context with set_context / first_set_context in the generic instruction() path keeps CPI‑write usage on the dedicated helpers and avoids silently mis‑tagged instructions. Serialization via LightInstructionData and accounts.to_account_metas() wiring look consistent with the rest of the SDK.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (1)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)

65-84: CPI-context builder helpers still duplicate flag logic from the CTokenInstruction impl

new_mint_write_to_cpi_context, write_to_cpi_context_first, and write_to_cpi_context_set here manipulate CpiContext.first_set_context / set_context in essentially the same way as the instruction_write_to_cpi_context_first / instruction_write_to_cpi_context_set methods on MintActionCompressedInstructionData in sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs. That gives you two places that must stay in sync if CPI semantics evolve.

It’s not wrong, but you might want to centralize the flag-toggling into a small helper (or rely on just one of these paths) to avoid future drift between client-side builder usage and the trait implementation.

Also applies to: 134-160

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7889290 and 8a1d359.

⛔ Files ignored due to path filters (2)
  • sdk-tests/sdk-compressible-test/src/instructions/create_user_record_and_game_session.rs is excluded by none and included by none
  • sdk-tests/sdk-token-test/src/ctoken_pda/mint.rs is excluded by none and included by none
📒 Files selected for processing (14)
  • program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1 hunks)
  • sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1 hunks)
  • sdk-libs/compressed-token-sdk/src/error.rs (2 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/approve/instruction.rs (0 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/batch_compress/instruction.rs (0 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (5 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (3 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs (3 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (1 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mint_action/mod.rs (0 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/mint_to_compressed/instruction.rs (4 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/transfer2/instruction.rs (0 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/update_compressed_mint/instruction.rs (4 hunks)
  • sdk-libs/compressed-token-sdk/src/token_pool.rs (1 hunks)
💤 Files with no reviewable changes (4)
  • sdk-libs/compressed-token-sdk/src/instructions/batch_compress/instruction.rs
  • sdk-libs/compressed-token-sdk/src/instructions/approve/instruction.rs
  • sdk-libs/compressed-token-sdk/src/instructions/transfer2/instruction.rs
  • sdk-libs/compressed-token-sdk/src/instructions/mint_action/mod.rs
🧰 Additional context used
🧬 Code graph analysis (8)
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (3)
  • instruction (21-41)
  • instruction_write_to_cpi_context_first (43-60)
  • instruction_write_to_cpi_context_set (62-79)
sdk-libs/compressed-token-sdk/src/instructions/update_compressed_mint/instruction.rs (2)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (3)
  • get_mint_action_instruction_account_metas (172-274)
  • get_mint_action_instruction_account_metas_cpi_write (288-318)
  • new (66-102)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
  • new (25-42)
sdk-libs/compressed-token-sdk/src/instructions/mint_to_compressed/instruction.rs (3)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (3)
  • get_mint_action_instruction_account_metas (172-274)
  • new (66-102)
  • new_cpi_context (104-136)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
  • new (25-42)
program-libs/ctoken-types/src/instructions/mint_action/mint_to_compressed.rs (2)
  • new (14-19)
  • new (30-35)
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (2)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (4)
  • get_mint_action_instruction_account_metas (172-274)
  • get_mint_action_instruction_account_metas_cpi_write (288-318)
  • new_cpi_context (104-136)
  • new_create_mint (27-64)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (2)
  • new_mint (44-63)
  • new_mint_write_to_cpi_context (65-84)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs (2)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
  • new (25-42)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (1)
  • new (66-102)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (3)
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (3)
  • instruction (9-12)
  • instruction_write_to_cpi_context_first (14-17)
  • instruction_write_to_cpi_context_set (19-22)
sdk-libs/compressed-token-sdk/src/error.rs (3)
  • from (81-83)
  • from (87-89)
  • from (93-128)
program-libs/ctoken-types/src/instructions/mint_action/cpi_context.rs (1)
  • default (25-37)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (4)
sdk-libs/token-client/src/actions/mint_action.rs (1)
  • mint_action (24-59)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
  • new (25-42)
sdk-libs/compressed-token-sdk/src/token_pool.rs (1)
  • find_token_pool_pda_with_index (20-28)
sdk-libs/compressed-token-sdk/src/error.rs (3)
  • from (81-83)
  • from (87-89)
  • from (93-128)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (1)
  • new (66-102)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: programs (light-system-program-address, ["cargo-test-sbf -p system-test -- test_with_address", "c...
  • GitHub Check: programs (system-cpi-test-v2-functional-account-infos, ["cargo-test-sbf -p system-cpi-v2-test -- ...
  • GitHub Check: Test batched-merkle-tree-simulate
  • GitHub Check: Test program-libs-fast
🔇 Additional comments (2)
sdk-libs/compressed-token-sdk/src/token_pool.rs (1)

5-12: TokenPool struct wiring matches PDA helpers

TokenPool { pubkey, bump, index } lines up cleanly with find_token_pool_pda_with_index and derive_token_pool, and adding AnchorDeserialize/AnchorSerialize + PartialEq should make this easy to pass around in Anchor-compatible contexts. I don’t see correctness issues here; this is a straightforward and sensible extraction of pool metadata.

sdk-libs/compressed-token-sdk/src/error.rs (1)

60-65: New MintActionMetaConfig / CPI errors and codes are consistent

The added TokenSdkError variants and their numeric mappings (17024–17026) are coherent with the existing range and clearly express the builder precondition failures around create_mint and cpi_context. Just make sure any external consumers that rely on concrete error codes (e.g., SDKs, dashboards) are updated to understand these new values.

Also applies to: 118-120

Comment on lines +1 to +23
use solana_instruction::Instruction;
use solana_program_error::ProgramError;

pub trait CTokenInstruction: Sized {
type ExecuteAccounts<'info, A: light_account_checks::AccountInfoTrait + Clone + 'info>;

type CpiWriteAccounts<'info, A: light_account_checks::AccountInfoTrait + Clone + 'info>;

fn instruction<A: light_account_checks::AccountInfoTrait + Clone>(
self,
accounts: &Self::ExecuteAccounts<'_, A>,
) -> Result<Instruction, ProgramError>;

fn instruction_write_to_cpi_context_first<A: light_account_checks::AccountInfoTrait + Clone>(
self,
accounts: &Self::CpiWriteAccounts<'_, A>,
) -> Result<Instruction, ProgramError>;

fn instruction_write_to_cpi_context_set<A: light_account_checks::AccountInfoTrait + Clone>(
self,
accounts: &Self::CpiWriteAccounts<'_, A>,
) -> Result<Instruction, ProgramError>;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

CTokenInstruction trait shape is solid; consider whether methods should consume self

Using GATs for ExecuteAccounts / CpiWriteAccounts gives you nice type-level coupling between instruction data and its account layouts, and returning Result<Instruction, ProgramError> fits Solana conventions.

One design question: all three methods take self by value, which means callers must clone the instruction data if they want to build multiple instructions (e.g., execute vs different CPI-write variants) from the same base state. If that’s intentional to enforce a “build once, send once” pattern, this is fine; otherwise, you might consider &self receivers and cloning internally in the impls for a bit more ergonomic reuse.

Comment on lines +86 to 118
let meta_config = if cpi_context_pubkey.is_some() {
MintActionMetaConfig::new_cpi_context(
&instruction_data,
input.mint_authority,
input.payer,
cpi_context_pubkey.unwrap(),
)?
} else {
MintActionMetaConfig::new_create_mint(
&instruction_data,
input.mint_authority,
input.mint_signer,
input.payer,
input.address_tree_pubkey,
input.output_queue,
)?
};

create_mint_action_cpi(
mint_action_inputs,
mint_action_cpi_context,
cpi_context_pubkey,
)
let account_metas =
get_mint_action_instruction_account_metas(meta_config, &compressed_mint_with_context);

let data = instruction_data
.data()
.map_err(|_| TokenSdkError::SerializationError)?;

Ok(Instruction {
program_id: solana_pubkey::Pubkey::new_from_array(
light_ctoken_types::COMPRESSED_TOKEN_PROGRAM_ID,
),
accounts: account_metas,
data,
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

CPI-context branch still emits placeholder accounts
MintActionMetaConfig::new_cpi_context intentionally seeds tree_pubkey, output_queue, tokens_out_queue, and mint_signer with placeholders, expecting the caller to fill them in. Here we take that config and immediately derive metas, so the instruction ships with all-zero tree/output queue pubkeys and omits the required mint signer altogether. On-chain this trips the first account ownership check. Please populate the config before you call get_mint_action_instruction_account_metas (e.g., assign the real mint_signer, set tree_pubkey/output_queue to the provided inputs, and reapply tokens_out_queue when has_mint_to_actions is true).

🤖 Prompt for AI Agents
In
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs
around lines 86 to 118, the CPI-context branch uses
MintActionMetaConfig::new_cpi_context which returns a config seeded with
placeholder pubkeys and a missing mint_signer; you must fill that config with
the real values from the inputs before deriving account metas: set
config.mint_signer = input.mint_signer, set config.tree_pubkey =
input.address_tree_pubkey, set config.output_queue = input.output_queue, and if
meta_config.has_mint_to_actions is true reassign config.tokens_out_queue to the
expected tokens_out_queue value; after updating those fields call
get_mint_action_instruction_account_metas(meta_config,
&compressed_mint_with_context) so the produced Instruction contains the real
accounts rather than zeroed placeholders.

@@ -1,3 +1,4 @@
use light_compressed_account::instruction_data::traits::LightInstructionData;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

MintAction builder usage looks correct; minor CPI-branch ergonomics

The construction

let mut instruction_data =
    MintActionCompressedInstructionData::new(compressed_mint_inputs.clone(), proof)
        .with_mint_to_compressed(mint_to_action);

followed by optionally attaching cpi_context before feeding instruction_data into MintActionMetaConfig::new / new_cpi_context is logically consistent and lines up with the new error conditions (CreateMintMustBeNone, CpiContextRequired). The subsequent call to instruction_data.data() via LightInstructionData is also appropriate for building the final Instruction payload.

One small ergonomic improvement in the CPI branch:

let meta_config = if cpi_context_pubkey.is_some() {
    MintActionMetaConfig::new_cpi_context(
        &instruction_data,
        mint_authority,
        payer,
        cpi_context_pubkey.unwrap(),
    )?
} else { ... };

You can avoid the is_some + unwrap pair and keep the pubkey and branch coupled more clearly with:

let meta_config = if let Some(cpi_context_pubkey) = cpi_context_pubkey {
    MintActionMetaConfig::new_cpi_context(
        &instruction_data,
        mint_authority,
        payer,
        cpi_context_pubkey,
    )?
} else {
    MintActionMetaConfig::new(
        &instruction_data,
        mint_authority,
        payer,
        state_merkle_tree,
        input_queue,
        output_queue_cmint,
    )?
    .with_tokens_out_queue(output_queue_tokens)
};

Functionally identical, but a bit harder to misuse.

Also applies to: 59-107

Comment on lines +13 to 14
TokenPool,
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

token_pool input is unused but documented as required

The new token_pool: Option<TokenPool> field is documented as “Required if the mint is decompressed” but is immediately destructured as token_pool: _ in create_mint_to_compressed_instruction and never influences the instruction data or account metas.

If decompressed-mint flows are supposed to depend on a specific token-pool address, this requirement currently isn’t enforced or reflected in the constructed instruction. Either:

  • Wire token_pool into the relevant instruction data / meta-config, or
  • Relax or clarify the doc comment to indicate it’s reserved for future use to avoid misleading callers.

As written, it’s harmless but confusing for API consumers.

Also applies to: 35-35, 56-57

Comment on lines +63 to +88
if let Some(ctx) = cpi_context {
instruction_data = instruction_data.with_cpi_context(ctx);
}

let meta_config = MintActionMetaConfig::new(
&instruction_data,
input.authority,
input.payer,
input.in_merkle_tree,
input.in_output_queue,
input.out_output_queue,
)?;

create_mint_action_cpi(mint_action_inputs, mint_action_cpi_context, None)
let account_metas =
get_mint_action_instruction_account_metas(meta_config, &input.compressed_mint_inputs);

let data = instruction_data
.data()
.map_err(|_| TokenSdkError::SerializationError)?;

Ok(Instruction {
program_id: solana_pubkey::Pubkey::new_from_array(
light_ctoken_types::COMPRESSED_TOKEN_PROGRAM_ID,
),
accounts: account_metas,
data,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

CPI context account never reaches the metas
When cpi_context is Some, we call with_cpi_context on the instruction data, but we still build the metas via MintActionMetaConfig::new(...). That constructor hardcodes with_cpi_context = None, so get_mint_action_instruction_account_metas never appends the CPI context account. The runtime then errors out with a missing-account failure as soon as the on-chain processor reads the CPI context. Please plumb the CPI context account Pubkey into the config before deriving metas (e.g., extend UpdateCompressedMintInputs with the CPI-context account key and set meta_config.with_cpi_context = Some(pubkey) ahead of the metas call).

@ananas-block ananas-block merged commit 7560b31 into main Nov 20, 2025
36 of 40 checks passed
@ananas-block ananas-block deleted the jorrit/refactor-compressed-token-sdk-builder branch November 20, 2025 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants