Skip to content

refactor: compressed token sdk transfer interface builder#2064

Merged
SwenSchaeferjohann merged 2 commits intomainfrom
jorrit/refactor-compressed-token-sdk-transfer-interface-builder
Nov 21, 2025
Merged

refactor: compressed token sdk transfer interface builder#2064
SwenSchaeferjohann merged 2 commits intomainfrom
jorrit/refactor-compressed-token-sdk-transfer-interface-builder

Conversation

@ananas-block
Copy link
Contributor

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

Changes:

  • implement the following pattern for CTokenTransfer and TransferInterface

Pattern:

pub struct OperationName {
      pub account1: Pubkey,
      pub account2: Pubkey,
      pub amount: u64,
      // ... other fields
  }

  impl OperationName {
      pub fn instruction(self) -> Result<Instruction, ProgramError> {
          // Build and return Instruction
      }
  }

pub struct OperationNameAccountInfos<'info> {
      pub account1: AccountInfo<'info>,
      pub account2: AccountInfo<'info>,
      pub amount: u64,
      // ... other fields (matching Data struct)
  }

  impl<'info> OperationNameAccountInfos<'info> {
      pub fn instruction(&self) -> Result<Instruction, ProgramError> {
          OperationName::from(self).instruction()
      }

      pub fn invoke(self) -> Result<(), ProgramError> {
          let instruction = OperationName::from(&self).instruction()?;
          let account_infos = [self.account1, self.account2, ...];
          invoke(&instruction, &account_infos)
      }

      pub fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), ProgramError> {
          let instruction = OperationName::from(&self).instruction()?;
          let account_infos = [self.account1, self.account2, ...];
          invoke_signed(&instruction, &account_infos, signer_seeds)
      }
  }

Summary by CodeRabbit

  • Refactor
    • Updated compressed token transfer API from function-based to struct-based builders
    • Reorganized transfer abstractions for ctoken and SPL token conversion operations
    • Introduced unified transfer interface supporting multiple token conversion paths with optional configuration
    • Centralized instruction construction and invocation patterns across transfer operations

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 20, 2025

Walkthrough

Systematic refactor of the compressed token SDK's transfer instruction APIs, replacing function-based constructors with struct-driven patterns. Updates transfer_ctoken and transfer_interface modules to use new types like TransferCtoken, TransferSplToCtoken, TransferCtokenToSpl, and CtokenToSplTransferAndClose with corresponding AccountInfos structs and method-based invocation patterns.

Changes

Cohort / File(s) Summary
Module re-exports
sdk-libs/compressed-token-sdk/src/instructions/mod.rs
Updated public exports: replaced function exports (transfer_ctoken, transfer_ctoken_signed, create_transfer_spl_to_ctoken_instruction, etc.) with struct/type exports (TransferCtoken, TransferCtokenAccountInfos, TransferSplToCtoken, TransferCtokenToSpl, CtokenToSplTransferAndClose, TransferInterface, SplBridgeConfig); added UPDATE_COMPRESSED_MINT_DISCRIMINATOR export.
Transfer ctoken implementation
sdk-libs/compressed-token-sdk/src/instructions/transfer_ctoken.rs
Replaced free function API with struct-driven design: introduced TransferCtoken and TransferCtokenAccountInfos types with instruction(), invoke(), and invoke_signed() methods; added From conversion from AccountInfos to TransferCtoken; centralized instruction generation and invocation paths.
Transfer interface implementation
sdk-libs/compressed-token-sdk/src/instructions/transfer_interface.rs
Major refactor introducing new struct-based transfer abstractions (TransferSplToCtoken, TransferCtokenToSpl, CtokenToSplTransferAndClose, TransferInterface, SplBridgeConfig) with corresponding AccountInfos variants; added From implementations for ergonomic composition; consolidated branching logic into typed, modular paths with method-based instruction() and invoke patterns; updated account packing and error propagation.
Client-side transfer usage
sdk-libs/token-client/src/actions/transfer2/ctoken_to_spl.rs, sdk-libs/token-client/src/actions/transfer2/spl_to_ctoken.rs
Updated to use new struct-based APIs: replaced create_transfer_ctoken_to_spl_instruction() and create_transfer_spl_to_ctoken_instruction() with TransferCtokenToSpl and TransferSplToCtoken struct construction followed by .instruction() method calls; updated field mappings (authority, payer, spl_token_program).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant TransferStruct as Transfer Struct
    participant AccountInfos as AccountInfos Struct
    participant Instruction as Instruction Builder

    rect rgb(220, 245, 220)
    Note over Client,Instruction: New Struct-Based Flow
    Client->>AccountInfos: new(accounts...)
    Client->>TransferStruct: From(&AccountInfos)
    TransferStruct->>Instruction: instruction()
    Instruction-->>Client: Instruction
    Client->>AccountInfos: invoke(signer_seeds?)
    AccountInfos->>TransferStruct: From(self)
    TransferStruct->>Instruction: instruction()
    Instruction->>Client: Result<(), ProgramError>
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • transfer_interface.rs requires careful attention: introduces multiple new struct types with overlapping responsibilities (TransferSplToCtoken, TransferCtokenToSpl, CtokenToSplTransferAndClose, TransferInterface) and From implementations that must be verified for correct field mapping and conversion logic
  • Refactor consistency: verify that all displaced function-based APIs are correctly replaced by corresponding method flows across transfer_ctoken.rs and client usage files
  • Error handling changes: map_err conversions in instruction() methods need verification to ensure proper ProgramError propagation
  • Account ordering and packing: instruction() implementations build AccountMeta lists—validate against on-chain program expectations, especially for the bridge config variants
  • SplBridgeConfig optional handling: TransferInterface's Option branching logic should be tested for both ctoken-to-ctoken and ctoken↔SPL paths

Possibly related PRs

  • fix: compress and close hotpath #2059: Introduced create_ctoken_to_spl_transfer_and_close_instruction for the ctoken→SPL transfer/close path; this PR replaces that function-based API with the struct-driven CtokenToSplTransferAndClose design.
  • chore: csdk #2020: Previously modified transfer_ctoken and transfer_interface to establish initial patterns; this PR extends and consolidates those patterns across all transfer variants with unified struct-based APIs.

Suggested labels

ai-review

Suggested reviewers

  • sergeytimoshin
  • SwenSchaeferjohann

Poem

🏗️ Functions faded, structs arise,
From methods bloom in struct guise,
Transfer tokens, clean and bright,
Builders dance where calls once might! ✨

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 accurately describes the main change: a refactor of the compressed token SDK's transfer interface to use a builder pattern with new struct-based abstractions.
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-transfer-interface-builder

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

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

♻️ Duplicate comments (2)
sdk-libs/compressed-token-sdk/src/instructions/transfer_interface.rs (2)

187-199: Same critical account mismatch issue.

This invoke() method has the same problem as TransferSplToCtokenAccountInfos::invoke(). The compressed_token_program_authority is in the account_infos array but likely not in the instruction's accounts.


329-341: Same critical account mismatch issue (transfer and close variant).

This invoke() method also includes compressed_token_program_authority in account_infos without corresponding inclusion in the instruction accounts.

📜 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 7560b31 and f5e22b9.

⛔ Files ignored due to path filters (1)
  • program-tests/compressed-token-test/tests/transfer2/spl_ctoken.rs is excluded by none and included by none
📒 Files selected for processing (5)
  • sdk-libs/compressed-token-sdk/src/instructions/mod.rs (1 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/transfer_ctoken.rs (1 hunks)
  • sdk-libs/compressed-token-sdk/src/instructions/transfer_interface.rs (2 hunks)
  • sdk-libs/token-client/src/actions/transfer2/ctoken_to_spl.rs (2 hunks)
  • sdk-libs/token-client/src/actions/transfer2/spl_to_ctoken.rs (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
sdk-libs/compressed-token-sdk/src/instructions/transfer_ctoken.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/transfer_interface.rs (17)
  • instruction (48-50)
  • instruction (100-154)
  • instruction (183-185)
  • instruction (236-296)
  • instruction (325-327)
  • instruction (378-441)
  • from (84-96)
  • from (219-231)
  • from (361-373)
  • invoke (52-65)
  • invoke (187-200)
  • invoke (329-342)
  • invoke (522-586)
  • invoke_signed (67-80)
  • invoke_signed (202-215)
  • invoke_signed (344-357)
  • invoke_signed (592-656)
sdk-libs/token-client/src/actions/transfer2/spl_to_ctoken.rs (2)
sdk-libs/compressed-token-sdk/src/token_pool.rs (1)
  • find_token_pool_pda_with_index (20-28)
program-libs/compressed-account/src/pubkey.rs (1)
  • new_from_array (79-81)
sdk-libs/compressed-token-sdk/src/instructions/mod.rs (1)
sdk-libs/token-client/src/actions/ctoken_transfer.rs (1)
  • transfer_ctoken (20-38)
sdk-libs/token-client/src/actions/transfer2/ctoken_to_spl.rs (2)
sdk-libs/compressed-token-sdk/src/token_pool.rs (1)
  • find_token_pool_pda_with_index (20-28)
program-libs/compressed-account/src/pubkey.rs (1)
  • new_from_array (79-81)
sdk-libs/compressed-token-sdk/src/instructions/transfer_interface.rs (5)
sdk-libs/token-client/src/actions/ctoken_transfer.rs (1)
  • transfer_ctoken (20-38)
sdk-libs/compressed-token-sdk/src/instructions/transfer_ctoken.rs (5)
  • instruction (23-25)
  • instruction (52-67)
  • from (41-48)
  • invoke (27-31)
  • invoke_signed (33-37)
sdk-libs/compressed-token-sdk/src/account2.rs (5)
  • mint (362-364)
  • compress_spl (193-220)
  • decompress_ctoken (224-247)
  • compress_ctoken (169-190)
  • decompress_spl (250-279)
sdk-libs/compressed-token-sdk/src/instructions/transfer2/instruction.rs (1)
  • create_transfer2_instruction (71-140)
sdk-libs/compressed-token-sdk/src/utils.rs (1)
  • is_ctoken_account (22-37)
⏰ 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). (6)
  • GitHub Check: stateless-js-v2
  • GitHub Check: programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test", "cargo test -p light-system-prog...
  • GitHub Check: programs (compressed-token-and-e2e, ["cargo-test-sbf -p compressed-token-test --test v1", "cargo-...
  • 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 (account-compression-and-registry, ["cargo-test-sbf -p account-compression-test", "cargo...
🔇 Additional comments (8)
sdk-libs/compressed-token-sdk/src/instructions/transfer_interface.rs (3)

387-387: Correct mutability for close operation.

Good catch making the authority writable in the close variant. This is correct since the account is being closed and rent is refunded to the authority (as indicated by the comment on line 405: "destination is authority"). This differs appropriately from the non-close variant at line 245 which uses new_readonly.


538-540: Good error handling for missing SPL bridge config.

The error handling correctly catches the case where SPL bridge config is required but not provided. The IncompleteSplBridgeConfig error variant appropriately describes the issue.


119-127: Review contains inaccuracy regarding TODO comment locations.

The review states that hardcoded pool_index: 0 appears at lines 125, 275, and 420 "with TODO comments," but this is partially incorrect:

  • Line 125 (compress_spl): Hardcoded 0, no TODO comment
  • Line 275 (decompress_spl): Hardcoded 0, has "(TODO: make dynamic)" comment
  • Line 420 (decompress_spl): Hardcoded 0, has "(TODO: make dynamic)" comment

The parameter mapping for compress_spl is correct. However, the characterization that all three locations have TODO comments is inaccurate—only the decompress operations flagged this concern. Additionally, in transfer2.rs, compress_spl is called with dynamic pool_index values from input data, suggesting the single-pool design in transfer_interface.rs may be intentional for this code path.

Likely an incorrect or invalid review comment.

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

27-30: Correct account ordering in invoke().

Unlike the transfer_interface variants, this implementation correctly matches the accounts passed to invoke() with the accounts in the instruction (lines 55-59). The three accounts (source, destination, authority) are consistent between both.


60-65: Verify C-token instruction data format (double discriminator).

The instruction is serialized as [3u8, 3u8, <u64_le>] (two 3u8 bytes then amount). Other in-repo usages use a single-byte discriminator (e.g. sdk-libs/token-client/src/actions/ctoken_transfer.rs: vec![3u8] + amount; transfer2/withdraw/claim also use single-byte discriminators). This mismatch suggests the extra data.push(3u8) is likely incorrect — remove it or confirm it is required by the on‑chain C_TOKEN program ABI (or light_ctoken_types) before merging.

Locations: sdk-libs/compressed-token-sdk/src/instructions/transfer_ctoken.rs (60–65) — compare sdk-libs/token-client/src/actions/ctoken_transfer.rs (68–71) and sdk-libs/compressed-token-sdk/src/instructions/transfer2/instruction.rs (128–131).

sdk-libs/token-client/src/actions/transfer2/spl_to_ctoken.rs (1)

37-49: Good adoption of the new struct-based API.

The transition from the old function-based API to the struct-based TransferSplToCtoken builder pattern is clean and consistent. The use of .pubkey() to extract public keys from keypairs is appropriate, and the error handling correctly converts ProgramError to RpcError.

The TODO comment on line 46 about making the SPL token program dynamic is appropriate for tracking future work to support Token-2022.

sdk-libs/token-client/src/actions/transfer2/ctoken_to_spl.rs (1)

26-38: Consistent with the spl_to_ctoken implementation.

This follows the same clean pattern as the SPL-to-ctoken transfer. The struct construction, error handling, and TODO comment are all consistent with the other direction of transfer.

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

50-55: Public API exports correctly updated.

The module exports have been properly updated to expose the new struct-based API:

  • TransferCtoken and TransferCtokenAccountInfos from transfer_ctoken
  • All the new transfer interface types including TransferSplToCtoken, TransferCtokenToSpl, CtokenToSplTransferAndClose, and their corresponding AccountInfos variants
  • The new SplBridgeConfig and TransferInterface orchestrator types

This aligns with the implementation changes in the other files.

Comment on lines +52 to +64
pub fn invoke(self) -> Result<(), ProgramError> {
let instruction = TransferSplToCtoken::from(&self).instruction()?;
let account_infos = [
self.payer,
self.compressed_token_program_authority,
self.mint, // Index 0: Mint
self.destination_ctoken_account, // Index 1: Destination owner
self.authority, // Index 2: Authority (signer)
self.source_spl_token_account, // Index 3: Source SPL token account
self.token_pool_pda, // Index 4: Token pool PDA
self.spl_token_program, // Index 5: SPL Token program
];
invoke(&instruction, &account_infos)
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

🧩 Analysis chain

Critical: Account mismatch between invoke() and instruction().

The account_infos array passed to invoke() includes payer and compressed_token_program_authority at indices 0 and 1, but the instruction created by TransferSplToCtoken::instruction() uses Transfer2AccountsMetaConfig::new_decompressed_accounts_only(self.payer, packed_accounts) which only receives the payer as a separate parameter. The compressed_token_program_authority account doesn't appear to be added to the instruction's accounts by the create_transfer2_instruction path.

This mismatch will cause the instruction to fail at runtime because Solana requires the accounts passed to invoke() to match the accounts in the instruction.

Verify the account ordering by running:


🏁 Script executed:

#!/bin/bash
# Search for Transfer2AccountsMetaConfig implementation to understand account ordering
rg -nP "impl.*Transfer2AccountsMetaConfig" -A 30

# Check how new_decompressed_accounts_only adds accounts to the instruction
rg -nP "fn new_decompressed_accounts_only" -A 20

Length of output: 5713


🏁 Script executed:

#!/bin/bash
# Get full get_transfer2_instruction_account_metas implementation
rg -nP "pub fn get_transfer2_instruction_account_metas" -A 50

# Find TransferSplToCtoken and its instruction() method
rg -nP "impl.*TransferSplToCtoken" -A 5
rg -nP "fn instruction\(" -B 5 -A 15 --type=rs | head -100

Length of output: 8520


🏁 Script executed:

#!/bin/bash
# Get full TransferSplToCtoken::instruction() implementation including packed_accounts
rg -nP "impl TransferSplToCtoken" -A 100 sdk-libs/compressed-token-sdk/src/instructions/transfer_interface.rs | head -150

# Find create_transfer2_instruction to see how instruction is created
rg -nP "create_transfer2_instruction" -B 3 -A 10 sdk-libs/compressed-token-sdk/src/instructions/transfer_interface.rs

Length of output: 6240


🏁 Script executed:

#!/bin/bash
# Get the complete get_transfer2_instruction_account_metas function to see what accounts are added
rg -nP "pub fn get_transfer2_instruction_account_metas" -A 100 sdk-libs/compressed-token-sdk/src/instructions/transfer2/account_metas.rs | tail -60

Length of output: 2582


Critical: Account mismatch between invoke() and instruction() will cause runtime failure.

The instruction created by TransferSplToCtoken::instruction() constructs accounts as [CPI_AUTHORITY_PDA, payer, ...packed_accounts] (via get_transfer2_instruction_account_metas lines 110-122), but TransferSplToCtokenAccountInfos::invoke() passes [payer, compressed_token_program_authority, ...packed_accounts]. The ordering is inverted for the first two accounts, and compressed_token_program_authority is included in the invoke but never added to the instruction, while CPI_AUTHORITY_PDA is in the instruction but not in the invoke array. This mismatch will cause Solana to reject the transaction at runtime.

The same issue exists in TransferCtokenToSplAccountInfos::invoke() (lines 187-199).

🤖 Prompt for AI Agents
In sdk-libs/compressed-token-sdk/src/instructions/transfer_interface.rs around
lines 52 to 64, the account order passed to invoke() doesn't match the account
metas produced by TransferSplToCtoken::instruction(): the instruction expects
CPI_AUTHORITY_PDA as the first account and payer second, but invoke() supplies
payer then compressed_token_program_authority (and omits CPI_AUTHORITY_PDA). Fix
by reordering and aligning the account_infos array to exactly match the
instruction's account metas: replace the first two entries with
CPI_AUTHORITY_PDA then payer (remove compressed_token_program_authority if it is
not part of the instruction), and ensure the rest of the packed_accounts keep
the same indices as the instruction. Apply the same correction in
TransferCtokenToSplAccountInfos::invoke() (around lines 187 to 199) so both
invoke calls match their respective instruction() account order and contents.

Comment on lines +462 to +490
/// # Arguments
/// * `source_account` - Source token account (can be ctoken or SPL)
/// * `destination_account` - Destination token account (can be ctoken or SPL)
/// * `authority` - Authority for the transfer (must be signer)
/// * `amount` - Amount to transfer
/// * `payer` - Payer for the transaction
/// * `compressed_token_program_authority` - Compressed token program authority
/// * `mint` - Optional mint account (required for SPL<->ctoken transfers)
/// * `spl_token_program` - Optional SPL token program (required for SPL<->ctoken transfers)
/// * `compressed_token_pool_pda` - Optional token pool PDA (required for SPL<->ctoken transfers)
/// * `compressed_token_pool_pda_bump` - Optional bump seed for token pool PDA
pub fn new(
source_account: AccountInfo<'info>,
destination_account: AccountInfo<'info>,
authority: AccountInfo<'info>,
amount: u64,
payer: AccountInfo<'info>,
compressed_token_program_authority: AccountInfo<'info>,
) -> Self {
Self {
source_account,
destination_account,
authority,
amount,
signer_seeds,
),

// ctoken -> spl: Requires bridge accounts
(true, false) => {
// Validate all required accounts are provided
let (mint_acct, spl_program, pool_pda, bump) = match (
mint,
spl_token_program,
compressed_token_pool_pda,
compressed_token_pool_pda_bump,
) {
(Some(m), Some(p), Some(pd), Some(b)) => (m, p, pd, b),
_ => {
return Err(ProgramError::Custom(
TokenSdkError::IncompleteSplBridgeConfig.into(),
))
}
};

transfer_ctoken_to_spl_signed(
payer.clone(),
authority.clone(),
source_account.clone(),
destination_account.clone(),
mint_acct.clone(),
spl_program.clone(),
pool_pda.clone(),
bump,
compressed_token_program_authority.clone(),
amount,
signer_seeds,
)
payer,
compressed_token_program_authority,
spl_bridge_config: None,
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add documentation for the TransferInterface API.

The TransferInterface struct and its constructor lack rustdoc comments. This is a public API that dynamically dispatches based on account types, so clear documentation would help users understand:

  • When to use this vs the specific transfer types
  • What happens when spl_bridge_config is None vs Some
  • The runtime account type detection behavior

Add rustdoc comments like:

/// High-level transfer interface that automatically routes between ctoken and SPL transfers.
///
/// This type dynamically determines the transfer path based on the source and destination
/// account types (ctoken vs SPL). For transfers involving SPL accounts, you must provide
/// an `SplBridgeConfig` via `with_spl_bridge()` or `with_spl_bridge_config()`.
///
/// # Example
/// ```ignore
/// let transfer = TransferInterface::new(source, dest, authority, amount, payer, program_auth)
///     .with_spl_bridge(mint, spl_program, pool_pda, bump);
/// transfer.invoke()?;
/// ```
pub struct TransferInterface<'info> {

Comment on lines +522 to +586
pub fn invoke(self) -> Result<(), ProgramError> {
let source_is_ctoken = is_ctoken_account(&self.source_account)
.map_err(|_| ProgramError::InvalidAccountData)?;
let dest_is_ctoken = is_ctoken_account(&self.destination_account)
.map_err(|_| ProgramError::InvalidAccountData)?;

match (source_is_ctoken, dest_is_ctoken) {
(true, true) => TransferCtokenAccountInfos {
source: self.source_account.clone(),
destination: self.destination_account.clone(),
amount: self.amount,
authority: self.authority.clone(),
}
.invoke(),

(true, false) => {
let config = self.spl_bridge_config.ok_or_else(|| {
ProgramError::Custom(TokenSdkError::IncompleteSplBridgeConfig.into())
})?;

TransferCtokenToSplAccountInfos {
source_ctoken_account: self.source_account.clone(),
destination_spl_token_account: self.destination_account.clone(),
amount: self.amount,
authority: self.authority.clone(),
mint: config.mint.clone(),
payer: self.payer.clone(),
token_pool_pda: config.token_pool_pda.clone(),
token_pool_pda_bump: config.token_pool_pda_bump,
spl_token_program: config.spl_token_program.clone(),
compressed_token_program_authority: self
.compressed_token_program_authority
.clone(),
}
};

transfer_spl_to_ctoken_signed(
payer.clone(),
authority.clone(),
source_account.clone(),
destination_account.clone(),
mint_acct.clone(),
spl_program.clone(),
pool_pda.clone(),
bump,
compressed_token_program_authority.clone(),
amount,
signer_seeds,
)
.invoke()
}

(false, true) => {
let config = self.spl_bridge_config.ok_or_else(|| {
ProgramError::Custom(TokenSdkError::IncompleteSplBridgeConfig.into())
})?;

TransferSplToCtokenAccountInfos {
source_spl_token_account: self.source_account.clone(),
to: self.destination_account.clone(),
amount: self.amount,
authority: self.authority.clone(),
mint: config.mint.clone(),
payer: self.payer.clone(),
token_pool_pda: config.token_pool_pda.clone(),
token_pool_pda_bump: config.token_pool_pda_bump,
spl_token_program: config.spl_token_program.clone(),
compressed_token_program_authority: self
.compressed_token_program_authority
.clone(),
destination_ctoken_account: self.destination_account.clone(),
}
.invoke()
}

(false, false) => Err(ProgramError::Custom(
TokenSdkError::UseRegularSplTransfer.into(),
)),
}
}
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

Consider reducing AccountInfo clones.

The pattern of cloning AccountInfo instances throughout the match arms is safe but slightly redundant. While AccountInfo cloning is cheap (it's mostly references), you could construct the account info structs by moving/consuming the fields from self instead of cloning, since invoke() consumes self anyway.

For example:

(true, true) => TransferCtokenAccountInfos {
    source: self.source_account,  // move instead of clone
    destination: self.destination_account,
    amount: self.amount,
    authority: self.authority,
}
.invoke(),

This would apply to all match arms in both invoke() and invoke_signed().

🤖 Prompt for AI Agents
In sdk-libs/compressed-token-sdk/src/instructions/transfer_interface.rs around
lines 522-586, the code clones AccountInfo fields from self for each
Transfer*Infos construction; since invoke() consumes self, change these to move
the fields instead of cloning (e.g., use self.source_account,
self.destination_account, self.authority, self.amount, and take ownership of
self.spl_bridge_config when needed) so you stop performing redundant clones;
ensure you reorder/consume fields only once per match arm and apply the same
moving approach to invoke_signed() as well.

Comment on lines +473 to +480
pub fn new(
source_account: AccountInfo<'info>,
destination_account: AccountInfo<'info>,
authority: AccountInfo<'info>,
amount: u64,
payer: AccountInfo<'info>,
compressed_token_program_authority: AccountInfo<'info>,
) -> Self {
Copy link
Contributor

@SwenSchaeferjohann SwenSchaeferjohann Nov 21, 2025

Choose a reason for hiding this comment

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

@ananas-block
can we have one that lets me pass these as options:
• mint: Option<>
• c_token_pool_pda: Option<>
• c_token_pool_pda_bump: Option<>

this makes it easiest for AMMs because then they just need to forward the options from the instruction accounts struct.

the Default/most common usage of TransferInterface is that the caller programs don't know at compile time which individual bridge param will be Some or None.

we can still keep the params explicit, eg with something like this:

.new(..., mint_option) // i think adding mint_option to new makes sense
.token_pool(c_token_pool_pda_option, c_token_pool_pda_bump_option) // bump none = 0
.source_token_program(source_token_program)
.dest_token_program(destination_token_program) 
.invoke(...)

or

.new(..., mint_option) // i think adding mint_option to new makes sense
.token_pool(c_token_pool_pda_option, c_token_pool_pda_bump_option) // bump none = 0
.token_programs(source_token_program, dest_token_program)
.invoke(...)

(note that later we will enable SPL<>SPL so we must always pass source and dest token programs)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

at first glance sounds good to me.
I am adding tests in #2066 it makes more sense to merge this one and add it there so that I can test it right away.

@SwenSchaeferjohann SwenSchaeferjohann merged commit 3274f97 into main Nov 21, 2025
30 of 39 checks passed
@SwenSchaeferjohann SwenSchaeferjohann deleted the jorrit/refactor-compressed-token-sdk-transfer-interface-builder branch November 21, 2025 02:49
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.

2 participants