Conversation
📝 WalkthroughWalkthroughThis PR refactors token action modules to extract instruction-building logic into reusable helpers (e.g., Changes
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>
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>
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 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
⛔ Files ignored due to path filters (3)
Cargo.lockis excluded by!**/*.lockand included by nonesdk-tests/token-client-test/Cargo.tomlis excluded by none and included by nonesdk-tests/token-client-test/tests/test_read_load.rsis excluded by none and included by none
📒 Files selected for processing (14)
sdk-libs/token-client/src/actions/approve.rssdk-libs/token-client/src/actions/create_ata.rssdk-libs/token-client/src/actions/create_mint.rssdk-libs/token-client/src/actions/load.rssdk-libs/token-client/src/actions/mint_to.rssdk-libs/token-client/src/actions/mod.rssdk-libs/token-client/src/actions/revoke.rssdk-libs/token-client/src/actions/transfer.rssdk-libs/token-client/src/actions/transfer_checked.rssdk-libs/token-client/src/actions/transfer_interface.rssdk-libs/token-client/src/actions/unwrap.rssdk-libs/token-client/src/actions/wrap.rssdk-libs/token-client/src/lib.rssdk-libs/token-client/src/read.rs
| 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 | ||
| } |
There was a problem hiding this comment.
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.
| 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) |
There was a problem hiding this comment.
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.
Summary by CodeRabbit
Release Notes
Loadaction for managing light token accounts, including wrapping support and account decompression capabilities.