Skip to content

chore: update rust interface#2370

Open
SwenSchaeferjohann wants to merge 3 commits intomainfrom
swen/rust-interface
Open

chore: update rust interface#2370
SwenSchaeferjohann wants to merge 3 commits intomainfrom
swen/rust-interface

Conversation

@SwenSchaeferjohann
Copy link
Copy Markdown
Contributor

@SwenSchaeferjohann SwenSchaeferjohann commented Apr 7, 2026

Summary by CodeRabbit

Release Notes

  • New Features
    • Introduced Load action for managing light token accounts, including wrapping support and account decompression capabilities.
    • Added read module providing unified token account state parsing and querying across multiple account types and sources.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 7, 2026

📝 Walkthrough

Walkthrough

This PR refactors token action modules to extract instruction-building logic into reusable helpers (e.g., create_approve_instructions), introduces a new Load action for managing light token accounts, and adds comprehensive read module functionality for parsing and querying token account state across multiple sources.

Changes

Cohort / File(s) Summary
Action Instruction Builders
approve.rs, mint_to.rs, revoke.rs, transfer.rs, transfer_checked.rs
Extract instruction construction into public create_*_instructions helpers and instructions methods. Refactor execute methods to use these builders and pass Vec<Instruction> to transaction sending instead of inline construction.
Async Action Instruction Builders
create_ata.rs, create_mint.rs, transfer_interface.rs, unwrap.rs, wrap.rs
Extract instruction construction into async public create_*_instructions helpers and corresponding instructions methods; refactor execute to delegate to these builders and pass Vec<Instruction> to transaction APIs.
New Load Action
actions/load.rs
New module providing light token account loading with instruction generation for idempotent ATA creation, conditional wrapping of SPL/SPL-2022 sources, and optional decompression of cold compressed accounts. Includes mint-decimals fetching and authority validation.
New Read Module
read.rs
Comprehensive token account parsing and reading logic with data structures for source classification, normalized ATA fields, and multi-source account views. Includes async RPC entrypoints (get_ata_or_none, get_ata, get_ata_view_for_load_or_none), authority filtering, and primary cold account selection with deterministic tie-breaking.
Module Exports
actions/mod.rs, lib.rs
Add load module declaration, re-export new instruction builders alongside action structs, and update crate documentation to include Load action in the supported actions table.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Load as Load Action
    participant Rpc as RPC/Indexer
    participant AccountData as Account Data
    participant Signer as Signers

    Caller->>Load: execute(rpc, payer, authority)
    Load->>Load: instructions(rpc, payer, authority)
    
    Load->>Rpc: Resolve light ATA view
    Rpc->>AccountData: Fetch light ATA account
    AccountData-->>Rpc: ATA state (if exists)
    Rpc-->>Load: ATA view
    
    alt ATA Missing and Wrap/Decompress Needed
        Load->>Rpc: Fetch SPL/SPL-2022 ATAs
        Rpc->>AccountData: Read account balances
        AccountData-->>Rpc: Balances
        Rpc-->>Load: Source account data
        Load->>Load: Build wrap instructions
    end
    
    alt Cold Decompression Needed
        Load->>Rpc: Fetch cold compressed accounts
        Rpc->>AccountData: Fetch validity proof
        AccountData-->>Rpc: Proof data
        Rpc-->>Load: Compressed account proof
        Load->>Load: Build decompress instruction
    end
    
    Load->>Load: Combine all instructions
    Load->>Rpc: create_and_send_transaction(instructions, signers)
    Rpc->>Signer: Sign transaction
    Signer-->>Rpc: Signed tx
    Rpc-->>Load: Signature (if instructions non-empty)
    Load-->>Caller: Option<Signature>
Loading
sequenceDiagram
    participant Caller
    participant Read as Read Module
    participant Rpc as RPC/Indexer
    participant AccountData as Account Data/Parser

    Caller->>Read: get_ata_or_none(rpc, owner, mint)
    
    Read->>Rpc: Fetch light token ATA account
    Rpc->>AccountData: Load hot ATA data
    AccountData-->>Rpc: Hot account state
    Rpc-->>Read: ATA data
    
    Read->>Rpc: Fetch compressed accounts (cold)
    Rpc->>AccountData: Query by owner/mint
    AccountData-->>Rpc: Compressed account list
    Rpc-->>Read: Cold sources
    
    Read->>Read: Select primary cold account<br/>(by amount, then leaf index)
    Read->>Read: Normalize delegate & amounts<br/>(prefer hot, fallback cold)
    Read->>Read: Combine all sources into<br/>TokenInterfaceAccount
    
    Read-->>Caller: Option<TokenInterfaceAccount>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

ai-review

Suggested reviewers

  • sergeytimoshin
  • ananas-block

Poem

🌊 Instructions now surface cleanly,

Load and read with one decree,

Light tokens dance, accounts align,

Refactored flows both sharp and fine—

A token journey, refined. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'chore: update rust interface' is vague and generic; it uses non-descriptive language that obscures the substantial architectural changes made across multiple action modules. Consider a more specific title that captures the primary refactoring effort, such as 'refactor: extract instruction builders to shared helpers' or 'refactor: add instruction() methods and create_*_instructions() helpers to token actions'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 70.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch swen/rust-interface

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@SwenSchaeferjohann SwenSchaeferjohann marked this pull request as ready for review April 14, 2026 10:40
Copy link
Copy Markdown
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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@sdk-libs/token-client/src/read.rs`:
- Around line 328-354: The current filtering in sorted_primary_cold_candidates
(and the other cold-account collection path around the block noted 591-615)
excludes non-light-token owners by requiring account.account.owner ==
LIGHT_TOKEN_PROGRAM_ID, which drops SplCold and Token2022Cold sources; change
the predicate to accept compressed token accounts owned by
LIGHT_TOKEN_PROGRAM_ID OR the SPL token program OR the Token-2022 program (e.g.,
replace the equality check with a membership test against
LIGHT_TOKEN_PROGRAM_ID, spl_token::id(), and token_2022::id() or their local
constants), keeping the existing checks for non-empty data and amount > 0 so
that get_ata_or_none() and get_ata_view_for_load_or_none() will materialize
SplCold and Token2022Cold accounts as equivalent to Light token accounts.
- Around line 390-410: The filtered view currently includes each matching
Source's full amount, overstating delegate spendable balance; update the
filtering to map matching sources so the Source.amount is capped to the
delegate's allowance (min(source.amount, source.delegated_amount)) before
cloning/collecting, so when build_load_account_view(view.address, view.owner,
view.mint, filtered_sources) runs it reflects the delegate's actual spendable
balance; adjust the construction of filtered_sources (the iterator over
view.sources) rather than downstream LoadAccountView assembly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 19d3df7b-17e7-4c05-8aa2-96a711476c3d

📥 Commits

Reviewing files that changed from the base of the PR and between dfd7df5 and 4628bfc.

⛔ Files ignored due to path filters (3)
  • Cargo.lock is excluded by !**/*.lock and included by none
  • sdk-tests/token-client-test/Cargo.toml is excluded by none and included by none
  • sdk-tests/token-client-test/tests/test_read_load.rs is excluded by none and included by none
📒 Files selected for processing (14)
  • sdk-libs/token-client/src/actions/approve.rs
  • sdk-libs/token-client/src/actions/create_ata.rs
  • sdk-libs/token-client/src/actions/create_mint.rs
  • sdk-libs/token-client/src/actions/load.rs
  • sdk-libs/token-client/src/actions/mint_to.rs
  • sdk-libs/token-client/src/actions/mod.rs
  • sdk-libs/token-client/src/actions/revoke.rs
  • sdk-libs/token-client/src/actions/transfer.rs
  • sdk-libs/token-client/src/actions/transfer_checked.rs
  • sdk-libs/token-client/src/actions/transfer_interface.rs
  • sdk-libs/token-client/src/actions/unwrap.rs
  • sdk-libs/token-client/src/actions/wrap.rs
  • sdk-libs/token-client/src/lib.rs
  • sdk-libs/token-client/src/read.rs

Comment on lines +328 to +354
fn sorted_primary_cold_candidates(
accounts: &[CompressedTokenAccount],
) -> Vec<CompressedTokenAccount> {
let mut candidates = accounts
.iter()
.filter(|account| {
account.account.owner == LIGHT_TOKEN_PROGRAM_ID
&& account
.account
.data
.as_ref()
.is_some_and(|data| !data.data.is_empty())
&& account.token.amount > 0
})
.cloned()
.collect::<Vec<_>>();

candidates.sort_by(|left, right| {
let amount_cmp = right.token.amount.cmp(&left.token.amount);
if amount_cmp != Ordering::Equal {
return amount_cmp;
}
right.account.leaf_index.cmp(&left.account.leaf_index)
});

candidates
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't discard cold SPL / Token-2022 sources.

Both cold-account collection paths hard-filter account.account.owner == LIGHT_TOKEN_PROGRAM_ID, so SplCold and Token2022Cold are never materialized even though the public model advertises them. Any balance that exists only as compressed SPL/Token-2022 state will be omitted from get_ata_or_none() and get_ata_view_for_load_or_none(), which can make Load underreport funds or return None incorrectly.

Based on learnings: Light Token Accounts must be functionally equivalent to SPL token accounts.

Also applies to: 591-615

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk-libs/token-client/src/read.rs` around lines 328 - 354, The current
filtering in sorted_primary_cold_candidates (and the other cold-account
collection path around the block noted 591-615) excludes non-light-token owners
by requiring account.account.owner == LIGHT_TOKEN_PROGRAM_ID, which drops
SplCold and Token2022Cold sources; change the predicate to accept compressed
token accounts owned by LIGHT_TOKEN_PROGRAM_ID OR the SPL token program OR the
Token-2022 program (e.g., replace the equality check with a membership test
against LIGHT_TOKEN_PROGRAM_ID, spl_token::id(), and token_2022::id() or their
local constants), keeping the existing checks for non-empty data and amount > 0
so that get_ata_or_none() and get_ata_view_for_load_or_none() will materialize
SplCold and Token2022Cold accounts as equivalent to Light token accounts.

Comment on lines +390 to +410
let filtered_sources = view
.sources
.iter()
.filter(|source| source.delegate == Some(*authority))
.cloned()
.collect::<Vec<_>>();

if filtered_sources.is_empty() {
return LoadAccountView {
address: view.address,
owner: view.owner,
mint: view.mint,
amount: 0,
delegate: view.delegate,
delegated_amount: 0,
any_frozen: view.any_frozen,
sources: Vec::new(),
};
}

build_load_account_view(view.address, view.owner, view.mint, filtered_sources)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Delegate-filtered views currently overstate spendable balance.

filter_account_for_authority() keeps each matching source's full amount and then rebuilds the view from those raw balances. For a partially delegated source, the returned LoadAccountView.amount becomes the owner's full balance instead of the delegate's capped spendable amount.

Suggested fix
     let filtered_sources = view
         .sources
         .iter()
         .filter(|source| source.delegate == Some(*authority))
         .cloned()
+        .map(|mut source| {
+            let spendable = delegated_contribution(&source);
+            source.amount = spendable;
+            source.delegated_amount = spendable;
+            source
+        })
         .collect::<Vec<_>>();

Based on learnings: Light Token Accounts must be functionally equivalent to SPL token accounts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk-libs/token-client/src/read.rs` around lines 390 - 410, The filtered view
currently includes each matching Source's full amount, overstating delegate
spendable balance; update the filtering to map matching sources so the
Source.amount is capped to the delegate's allowance (min(source.amount,
source.delegated_amount)) before cloning/collecting, so when
build_load_account_view(view.address, view.owner, view.mint, filtered_sources)
runs it reflects the delegate's actual spendable balance; adjust the
construction of filtered_sources (the iterator over view.sources) rather than
downstream LoadAccountView assembly.

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.

1 participant