Conversation
…ctions to test utils
📝 WalkthroughWalkthroughThis PR refactors the light-token client to adopt an action-based API pattern, where operations are encapsulated in struct types (CreateMint, Transfer, Approve, etc.) with async Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~28 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 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: 6
🤖 Fix all issues with AI agents
In `@sdk-libs/token-client/CLAUDE.md`:
- Around line 8-10: The Markdown has linting issues: fix the table spacing
around the header and rows for the `CreateMint` table (ensure pipes and spaces
align: "| Struct | Fields | `execute` signers |" and matching row spacing for
"`CreateMint`"), replace the closing/incorrect code-fence blocks with properly
language-tagged fences (use ```text for the example block around src/ tree) and
make sure all code fences are balanced, and add a trailing newline at EOF of
CLAUDE.md so the file ends with a single newline character.
- Around line 1-65: The CLAUDE.md is missing the required crate-level sections;
add a Summary (2–5 bullets describing purpose and key structs like CreateMint,
CreateAta, MintTo), a "Used in" list of consumers, a Navigation guide explaining
top-level docs and where to find actions, and expand "Source Code Structure" to
document SDK-specific categories (Client Functions, Instruction Builders, Types)
mapping to src/lib.rs and the actions/* modules (e.g., CreateMint,
TokenMetadata, create_ata.rs, mint_to.rs, transfer*.rs, approve.rs, revoke.rs,
wrap.rs, unwrap.rs); ensure each listed file/module is described under the
appropriate category and include brief notes on trait bounds (e.g.,
CreateMint::execute requires R: Rpc + Indexer).
In `@sdk-libs/token-client/src/actions/create_mint.rs`:
- Around line 139-146: The code currently accesses
rpc_result.addresses[0].root_index which will panic if the RPC returns an empty
addresses list; change this to safely extract the first address using
rpc_result.addresses.get(0).ok_or_else(...) and return a RpcError::CustomError
(e.g., "At least one address is required for create_mint") if missing, then use
that value for address_merkle_tree_root_index when building
CreateMintInstructionParams (keep the existing proof handling as-is).
- Around line 122-130: The code is forcing a default update authority
(mint_authority) instead of preserving metadata.update_authority = None; change
the assignment inside TokenMetadataInstructionData so it preserves the Option by
mapping Some values to bytes and leaving None as None (e.g., use
metadata.update_authority.map(|k| k.to_bytes().into()) rather than
unwrap_or_else(...)); update the field in create_mint.rs where update_authority
is set so TokenMetadataInstructionData.update_authority reflects the original
Option semantics.
In `@sdk-libs/token-client/src/actions/revoke.rs`:
- Around line 89-105: The execute_with_owner method currently ignores self.owner
and accepts any provided owner Keypair; update execute_with_owner in revoke.rs
to validate that if self.owner is Some(pubkey) it equals the supplied
owner.pubkey(), mirroring the guard used in Approve (return a
RpcError/CustomError when mismatched). Locate the execute_with_owner function
and add the same ownership check (compare self.owner.unwrap() to owner.pubkey())
before building the instruction and signer list so mismatched callers get a
clear error instead of silent behavior.
In `@sdk-libs/token-client/src/actions/transfer_interface.rs`:
- Around line 82-97: Add validation before constructing SplInterface: check
source_account.owner and destination_account.owner for SPL_TOKEN_PROGRAM_ID or
SPL_TOKEN_2022_PROGRAM_ID and require self.spl_token_program be Some when either
owner is an SPL owner; verify self.spl_token_program equals the matching owner
program and that it is one of the supported constants (SPL_TOKEN_PROGRAM_ID or
SPL_TOKEN_2022_PROGRAM_ID) before calling find_spl_interface_pda_with_index and
creating SplInterface; if validation fails return an appropriate error. Use the
same guard pattern as wrap.rs (lines referenced) and the light_token::constants
to compare program IDs, referencing spl_token_program, source_account.owner,
destination_account.owner, SplInterface, and find_spl_interface_pda_with_index.
| # light-token-client | ||
|
|
||
| Rust client for light-token. Each action is a struct with an | ||
| async `execute` method that builds, signs, and sends the transaction. | ||
|
|
||
| ## Actions | ||
|
|
||
| | Struct | Fields | `execute` signers | | ||
| |--------|--------|-------------------| | ||
| | `CreateMint` | `decimals`, `freeze_authority?`, `token_metadata?`, `seed?` | `payer`, `mint_authority` | | ||
| | `CreateAta` | `mint`, `owner`, `idempotent` | `payer` | | ||
| | `MintTo` | `mint`, `destination`, `amount` | `payer`, `authority` | | ||
| | `Transfer` | `source`, `destination`, `amount` | `payer`, `authority` | | ||
| | `TransferChecked` | `source`, `mint`, `destination`, `amount`, `decimals` | `payer`, `authority` | | ||
| | `TransferInterface` | `source`, `mint`, `destination`, `amount`, `decimals`, `spl_token_program?`, `restricted` | `payer`, `authority` | | ||
| | `Approve` | `token_account`, `delegate`, `amount`, `owner?` | `payer` (or `payer` + `owner`) | | ||
| | `Revoke` | `token_account`, `owner?` | `payer` (or `payer` + `owner`) | | ||
| | `Wrap` | `source_spl_ata`, `destination`, `mint`, `amount`, `decimals` | `payer`, `authority` | | ||
| | `Unwrap` | `source`, `destination_spl_ata`, `mint`, `amount`, `decimals` | `payer`, `authority` | | ||
|
|
||
| `?` = `Option`. All structs derive `Default`, `Clone`, `Debug`. | ||
|
|
||
| `CreateMint::execute` requires `R: Rpc + Indexer` (needs address proof). | ||
| All others require `R: Rpc`. | ||
|
|
||
| `Approve` and `Revoke` also expose `execute_with_owner` for when | ||
| owner differs from payer. | ||
|
|
||
| ## Re-exports | ||
|
|
||
| From `light_token::instruction`: | ||
| - `derive_associated_token_account` | ||
| - `get_associated_token_address` | ||
| - `get_associated_token_address_and_bump` | ||
|
|
||
| ## Supporting types | ||
|
|
||
| ```rust | ||
| pub struct TokenMetadata { | ||
| pub name: String, | ||
| pub symbol: String, | ||
| pub uri: String, | ||
| pub update_authority: Option<Pubkey>, | ||
| pub additional_metadata: Option<Vec<(String, String)>>, | ||
| } | ||
| ``` | ||
|
|
||
| ## Source layout | ||
|
|
||
| ``` | ||
| src/ | ||
| lib.rs -- re-exports actions::* | ||
| actions/ | ||
| mod.rs -- submodule declarations, re-exports | ||
| create_mint.rs -- CreateMint, TokenMetadata | ||
| create_ata.rs -- CreateAta | ||
| mint_to.rs -- MintTo | ||
| transfer.rs -- Transfer | ||
| transfer_checked.rs -- TransferChecked | ||
| transfer_interface.rs -- TransferInterface | ||
| approve.rs -- Approve | ||
| revoke.rs -- Revoke | ||
| wrap.rs -- Wrap | ||
| unwrap.rs -- Unwrap | ||
| ``` No newline at end of file |
There was a problem hiding this comment.
Add the required CLAUDE.md sections (Summary / Used in / Navigation / Source Code Structure).
This file is missing the mandatory structure for crate-level CLAUDE docs, and the Source Code Structure section doesn’t describe SDK layout in the required categories.
📝 Suggested structure update
# light-token-client
+## Summary
+- Action-based async client for Light Token transactions.
+- Wraps instruction building and RPC submission for common token flows.
+- Supports create/mint/transfer/approve/revoke and wrap/unwrap actions.
+
+## Used in
+- Off-chain clients and tests that interact with Light Token over RPC.
+
+## Navigation
+- Start with **Actions** for the public API surface.
+- See **Supporting types** for shared structs.
+- Use **Source Code Structure** for module layout.
+
Rust client for light-token. Each action is a struct with an
async `execute` method that builds, signs, and sends the transaction.
## Actions
@@
-## Source layout
+## Source Code Structure
+
+- Client Functions: `src/actions/*.rs`
+- Instruction Builders: instruction helper re-exports
+- Types: action structs and `TokenMetadata`
src/
lib.rs -- re-exports actions::*
</details>
As per coding guidelines, "Every crate must have a `CLAUDE.md` file at the root containing: Summary (2-5 bullet points), Used in section, Navigation instructions, and High-level sections (Accounts/Instructions/Source Code Structure based on crate type)" and "Source Code Structure section in `CLAUDE.md` should document the `src/` directory organization based on crate type: For SDKs include Client Functions, Instruction Builders, and Types".
<details>
<summary>🧰 Tools</summary>
<details>
<summary>🪛 markdownlint-cli2 (0.20.0)</summary>
9-9: Table column style
Table pipe is missing space to the right for style "compact"
(MD060, table-column-style)
---
9-9: Table column style
Table pipe is missing space to the left for style "compact"
(MD060, table-column-style)
---
9-9: Table column style
Table pipe is missing space to the right for style "compact"
(MD060, table-column-style)
---
9-9: Table column style
Table pipe is missing space to the left for style "compact"
(MD060, table-column-style)
---
9-9: Table column style
Table pipe is missing space to the right for style "compact"
(MD060, table-column-style)
---
9-9: Table column style
Table pipe is missing space to the left for style "compact"
(MD060, table-column-style)
---
50-50: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
---
65-65: Files should end with a single newline character
(MD047, single-trailing-newline)
</details>
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
In @sdk-libs/token-client/CLAUDE.md around lines 1 - 65, The CLAUDE.md is
missing the required crate-level sections; add a Summary (2–5 bullets describing
purpose and key structs like CreateMint, CreateAta, MintTo), a "Used in" list of
consumers, a Navigation guide explaining top-level docs and where to find
actions, and expand "Source Code Structure" to document SDK-specific categories
(Client Functions, Instruction Builders, Types) mapping to src/lib.rs and the
actions/* modules (e.g., CreateMint, TokenMetadata, create_ata.rs, mint_to.rs,
transfer*.rs, approve.rs, revoke.rs, wrap.rs, unwrap.rs); ensure each listed
file/module is described under the appropriate category and include brief notes
on trait bounds (e.g., CreateMint::execute requires R: Rpc + Indexer).
</details>
<!-- fingerprinting:phantom:medusa:eagle -->
<!-- This is an auto-generated comment by CodeRabbit -->
| | Struct | Fields | `execute` signers | | ||
| |--------|--------|-------------------| | ||
| | `CreateMint` | `decimals`, `freeze_authority?`, `token_metadata?`, `seed?` | `payer`, `mint_authority` | |
There was a problem hiding this comment.
Fix markdownlint issues (table spacing, code-fence language, trailing newline).
These are explicitly flagged by static analysis.
✅ Suggested formatting fixes
-|--------|--------|-------------------|
+| -------- | -------- | ------------------- |
@@
-```
+```text
src/
lib.rs -- re-exports actions::*
@@
-```
+```
+Also applies to: 50-65
🧰 Tools
🪛 markdownlint-cli2 (0.20.0)
9-9: Table column style
Table pipe is missing space to the right for style "compact"
(MD060, table-column-style)
9-9: Table column style
Table pipe is missing space to the left for style "compact"
(MD060, table-column-style)
9-9: Table column style
Table pipe is missing space to the right for style "compact"
(MD060, table-column-style)
9-9: Table column style
Table pipe is missing space to the left for style "compact"
(MD060, table-column-style)
9-9: Table column style
Table pipe is missing space to the right for style "compact"
(MD060, table-column-style)
9-9: Table column style
Table pipe is missing space to the left for style "compact"
(MD060, table-column-style)
🤖 Prompt for AI Agents
In `@sdk-libs/token-client/CLAUDE.md` around lines 8 - 10, The Markdown has
linting issues: fix the table spacing around the header and rows for the
`CreateMint` table (ensure pipes and spaces align: "| Struct | Fields |
`execute` signers |" and matching row spacing for "`CreateMint`"), replace the
closing/incorrect code-fence blocks with properly language-tagged fences (use
```text for the example block around src/ tree) and make sure all code fences
are balanced, and add a trailing newline at EOF of CLAUDE.md so the file ends
with a single newline character.
| vec![ExtensionInstructionData::TokenMetadata( | ||
| TokenMetadataInstructionData { | ||
| update_authority: Some( | ||
| metadata | ||
| .update_authority | ||
| .unwrap_or_else(|| mint_authority.pubkey()) | ||
| .to_bytes() | ||
| .into(), | ||
| ), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate the file in question and examine its contents
find . -type f -name "create_mint.rs" -path "*/sdk-libs/*" | head -20Repository: Lightprotocol/light-protocol
Length of output: 174
🏁 Script executed:
# Check the structure of the token-client directory
fd -t f "create_mint.rs" | xargs wc -lRepository: Lightprotocol/light-protocol
Length of output: 602
🏁 Script executed:
# Read the create_mint.rs file around the mentioned lines
cat -n sdk-libs/token-client/src/actions/create_mint.rs | head -170Repository: Lightprotocol/light-protocol
Length of output: 7411
🏁 Script executed:
# Look for TokenMetadata definition
rg "struct TokenMetadata" --type rs -A 15Repository: Lightprotocol/light-protocol
Length of output: 98
🏁 Script executed:
# Check how update_authority is used elsewhere in the codebase
rg "update_authority" --type rs -B 2 -A 2 | head -80Repository: Lightprotocol/light-protocol
Length of output: 98
🏁 Script executed:
# Find TokenMetadataInstructionData definition
rg "TokenMetadataInstructionData" --no-configRepository: Lightprotocol/light-protocol
Length of output: 8478
🏁 Script executed:
# Check the test file for create_mint to understand expected behavior
cat -n sdk-libs/token-client/src/actions/create_mint.rs | tail -50Repository: Lightprotocol/light-protocol
Length of output: 2098
🏁 Script executed:
# Search for how update_authority is handled in related code
rg "update_authority" -B 3 -A 3 | head -120Repository: Lightprotocol/light-protocol
Length of output: 10163
🏁 Script executed:
# Check if addresses indexing is validated elsewhere
rg "addresses\[0\]" -B 2 -A 2Repository: Lightprotocol/light-protocol
Length of output: 13527
🏁 Script executed:
# Check TokenMetadataInstructionData to see if update_authority can be None
fd -t f "token_metadata" | xargs grep -l "TokenMetadataInstructionData" | head -10Repository: Lightprotocol/light-protocol
Length of output: 213
🏁 Script executed:
# Read the TokenMetadataInstructionData struct definition
cat -n program-libs/token-interface/src/instructions/extensions/token_metadata.rs | head -100Repository: Lightprotocol/light-protocol
Length of output: 630
🏁 Script executed:
# Check if update_authority in TokenMetadataInstructionData is Option or required
rg "pub struct TokenMetadataInstructionData" -A 20Repository: Lightprotocol/light-protocol
Length of output: 814
🏁 Script executed:
# Look for tests that demonstrate the expected behavior of update_authority
rg "update_authority.*None" --type rs -B 2 -A 2Repository: Lightprotocol/light-protocol
Length of output: 98
🏁 Script executed:
# Check the compressed token program's token_metadata.rs for any validation
cat -n programs/compressed-token/program/src/extensions/token_metadata.rs | head -80Repository: Lightprotocol/light-protocol
Length of output: 3696
🏁 Script executed:
# Double-check the test to see if there's any test for None update_authority
rg "TokenMetadata.*\{" -A 10 sdk-libs/token-client/src/actions/create_mint.rs | head -30Repository: Lightprotocol/light-protocol
Length of output: 1213
🏁 Script executed:
# Check if there are any examples or documentation about intended use of update_authority
rg "update_authority.*None\|metadata.*update_authority.*None" -B 3 -A 3Repository: Lightprotocol/light-protocol
Length of output: 54
Preserve the Option semantics of update_authority instead of forcing a default.
Lines 124–130 override metadata.update_authority = None to Some(mint_authority), preventing callers from creating immutable metadata. The struct definition and on-chain program design both support None explicitly (see program comment: "We cannot fail on None since if we remove the update authority we allocate None"). Propagate None directly rather than applying a default:
Suggested fix
- update_authority: Some(
- metadata
- .update_authority
- .unwrap_or_else(|| mint_authority.pubkey())
- .to_bytes()
- .into(),
- ),
+ update_authority: metadata
+ .update_authority
+ .map(|authority| authority.to_bytes().into()),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| vec![ExtensionInstructionData::TokenMetadata( | |
| TokenMetadataInstructionData { | |
| update_authority: Some( | |
| metadata | |
| .update_authority | |
| .unwrap_or_else(|| mint_authority.pubkey()) | |
| .to_bytes() | |
| .into(), | |
| ), | |
| vec![ExtensionInstructionData::TokenMetadata( | |
| TokenMetadataInstructionData { | |
| update_authority: metadata | |
| .update_authority | |
| .map(|authority| authority.to_bytes().into()), |
🤖 Prompt for AI Agents
In `@sdk-libs/token-client/src/actions/create_mint.rs` around lines 122 - 130, The
code is forcing a default update authority (mint_authority) instead of
preserving metadata.update_authority = None; change the assignment inside
TokenMetadataInstructionData so it preserves the Option by mapping Some values
to bytes and leaving None as None (e.g., use metadata.update_authority.map(|k|
k.to_bytes().into()) rather than unwrap_or_else(...)); update the field in
create_mint.rs where update_authority is set so
TokenMetadataInstructionData.update_authority reflects the original Option
semantics.
| // Build params | ||
| let params = CreateMintInstructionParams { | ||
| decimals: self.decimals, | ||
| address_merkle_tree_root_index: rpc_result.addresses[0].root_index, | ||
| mint_authority: mint_authority.pubkey(), | ||
| proof: rpc_result.proof.0.ok_or_else(|| { | ||
| RpcError::CustomError("Validity proof is required for create_mint".to_string()) | ||
| })?, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's read the file and see the context
wc -l sdk-libs/token-client/src/actions/create_mint.rsRepository: Lightprotocol/light-protocol
Length of output: 124
🏁 Script executed:
# Read the specific section with more context
sed -n '130,160p' sdk-libs/token-client/src/actions/create_mint.rsRepository: Lightprotocol/light-protocol
Length of output: 1182
🏁 Script executed:
# Let's find the RPC result type definition
rg "rpc_result" sdk-libs/token-client/src/actions/create_mint.rs -B 5 -A 2 | head -50Repository: Lightprotocol/light-protocol
Length of output: 725
🏁 Script executed:
# Let's check if there are type definitions for the RPC response
rg "addresses.*root_index" sdk-libs/token-client/src/ -A 2 -B 2Repository: Lightprotocol/light-protocol
Length of output: 591
🏁 Script executed:
# Find the get_validity_proof method definition
rg "fn get_validity_proof" sdk-libs/ -A 10Repository: Lightprotocol/light-protocol
Length of output: 6908
🏁 Script executed:
# Look for ValidityProofResult or similar type definition
rg "addresses.*Vec|struct.*ValidityProof" sdk-libs/ -A 5Repository: Lightprotocol/light-protocol
Length of output: 48106
🏁 Script executed:
# Check the rpc client module
find sdk-libs -name "*rpc*" -type f | head -20Repository: Lightprotocol/light-protocol
Length of output: 205
🏁 Script executed:
# Let's look at the imports to understand the RPC type
sed -n '1,50p' sdk-libs/token-client/src/actions/create_mint.rsRepository: Lightprotocol/light-protocol
Length of output: 1731
🏁 Script executed:
# Get the full ValidityProofWithContext definition
sed -n '100,130p' sdk-libs/client/src/indexer/types.rsRepository: Lightprotocol/light-protocol
Length of output: 1165
🏁 Script executed:
# Search for other uses of rpc_result.addresses in the token-client
rg "rpc_result\.addresses|\.addresses\[" sdk-libs/token-client/ -B 2 -A 2Repository: Lightprotocol/light-protocol
Length of output: 591
🏁 Script executed:
# Check how other create_* functions handle RPC results
rg "get_validity_proof" sdk-libs/token-client/src/actions/ -A 20 | grep -A 20 "rpc_result"Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Get the full context of the create_mint function around the rpc_result usage
sed -n '100,165p' sdk-libs/token-client/src/actions/create_mint.rsRepository: Lightprotocol/light-protocol
Length of output: 2556
🏁 Script executed:
# Look for ValidityProofWithContext definition
rg "pub struct ValidityProofWithContext" sdk-libs/ -A 5Repository: Lightprotocol/light-protocol
Length of output: 451
🏁 Script executed:
# Search for how addresses is populated in get_validity_proof responses
rg "addresses.*=.*vec|addresses.*Vec::new" sdk-libs/client/src/indexer/ -B 2 -A 2Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Search for AddressProofInputs definition to understand its structure
rg "pub struct AddressProofInputs|pub struct AddressProofInputs" sdk-libs/ -A 3Repository: Lightprotocol/light-protocol
Length of output: 679
🏁 Script executed:
# Look at how get_validity_proof returns addresses - check implementation
rg "ValidityProofWithContext.*addresses" sdk-libs/client/src/indexer/ -B 5 -A 5 | head -50Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check if there's any validation or guarantee about addresses being non-empty
rg "addresses.*is_empty|addresses\.len|require.*addresses" sdk-libs/ -B 2 -A 2 | head -40Repository: Lightprotocol/light-protocol
Length of output: 3575
Guard against empty address list in RPC response.
Line 142 accesses addresses[0] directly. If the RPC returns an empty list, this will panic. Use .get(0).ok_or_else() to safely extract the address root index, consistent with how proof is handled in the next line.
🛡️ Suggested guard
- address_merkle_tree_root_index: rpc_result.addresses[0].root_index,
+ address_merkle_tree_root_index: rpc_result
+ .addresses
+ .get(0)
+ .ok_or_else(|| {
+ RpcError::CustomError(
+ "Validity proof response missing address root index".to_string(),
+ )
+ })?
+ .root_index,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Build params | |
| let params = CreateMintInstructionParams { | |
| decimals: self.decimals, | |
| address_merkle_tree_root_index: rpc_result.addresses[0].root_index, | |
| mint_authority: mint_authority.pubkey(), | |
| proof: rpc_result.proof.0.ok_or_else(|| { | |
| RpcError::CustomError("Validity proof is required for create_mint".to_string()) | |
| })?, | |
| // Build params | |
| let params = CreateMintInstructionParams { | |
| decimals: self.decimals, | |
| address_merkle_tree_root_index: rpc_result | |
| .addresses | |
| .get(0) | |
| .ok_or_else(|| { | |
| RpcError::CustomError( | |
| "Validity proof response missing address root index".to_string(), | |
| ) | |
| })? | |
| .root_index, | |
| mint_authority: mint_authority.pubkey(), | |
| proof: rpc_result.proof.0.ok_or_else(|| { | |
| RpcError::CustomError("Validity proof is required for create_mint".to_string()) | |
| })?, |
🤖 Prompt for AI Agents
In `@sdk-libs/token-client/src/actions/create_mint.rs` around lines 139 - 146, The
code currently accesses rpc_result.addresses[0].root_index which will panic if
the RPC returns an empty addresses list; change this to safely extract the first
address using rpc_result.addresses.get(0).ok_or_else(...) and return a
RpcError::CustomError (e.g., "At least one address is required for create_mint")
if missing, then use that value for address_merkle_tree_root_index when building
CreateMintInstructionParams (keep the existing proof handling as-is).
| pub async fn execute_with_owner<R: Rpc>( | ||
| self, | ||
| rpc: &mut R, | ||
| payer: &Keypair, | ||
| owner: &Keypair, | ||
| ) -> Result<Signature, RpcError> { | ||
| let ix = RevokeInstruction { | ||
| token_account: self.token_account, | ||
| owner: owner.pubkey(), | ||
| } | ||
| .instruction() | ||
| .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?; | ||
|
|
||
| let mut signers: Vec<&Keypair> = vec![payer]; | ||
| if owner.pubkey() != payer.pubkey() { | ||
| signers.push(owner); | ||
| } |
There was a problem hiding this comment.
Validate self.owner in execute_with_owner to avoid silent mismatch.
execute_with_owner ignores self.owner when it’s set, which can mask caller mistakes and lead to confusing failures. Mirror the guard used in Approve.
🛠️ Suggested fix (match Approve behavior)
pub async fn execute_with_owner<R: Rpc>(
self,
rpc: &mut R,
payer: &Keypair,
owner: &Keypair,
) -> Result<Signature, RpcError> {
+ // Guard: if self.owner is set, it must match the provided owner keypair
+ if let Some(expected_owner) = self.owner {
+ if expected_owner != owner.pubkey() {
+ return Err(RpcError::CustomError(format!(
+ "owner mismatch: self.owner ({}) does not match owner.pubkey() ({})",
+ expected_owner,
+ owner.pubkey()
+ )));
+ }
+ }
+
let ix = RevokeInstruction {
token_account: self.token_account,
owner: owner.pubkey(),
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pub async fn execute_with_owner<R: Rpc>( | |
| self, | |
| rpc: &mut R, | |
| payer: &Keypair, | |
| owner: &Keypair, | |
| ) -> Result<Signature, RpcError> { | |
| let ix = RevokeInstruction { | |
| token_account: self.token_account, | |
| owner: owner.pubkey(), | |
| } | |
| .instruction() | |
| .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?; | |
| let mut signers: Vec<&Keypair> = vec![payer]; | |
| if owner.pubkey() != payer.pubkey() { | |
| signers.push(owner); | |
| } | |
| pub async fn execute_with_owner<R: Rpc>( | |
| self, | |
| rpc: &mut R, | |
| payer: &Keypair, | |
| owner: &Keypair, | |
| ) -> Result<Signature, RpcError> { | |
| // Guard: if self.owner is set, it must match the provided owner keypair | |
| if let Some(expected_owner) = self.owner { | |
| if expected_owner != owner.pubkey() { | |
| return Err(RpcError::CustomError(format!( | |
| "owner mismatch: self.owner ({}) does not match owner.pubkey() ({})", | |
| expected_owner, | |
| owner.pubkey() | |
| ))); | |
| } | |
| } | |
| let ix = RevokeInstruction { | |
| token_account: self.token_account, | |
| owner: owner.pubkey(), | |
| } | |
| .instruction() | |
| .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?; | |
| let mut signers: Vec<&Keypair> = vec![payer]; | |
| if owner.pubkey() != payer.pubkey() { | |
| signers.push(owner); | |
| } |
🤖 Prompt for AI Agents
In `@sdk-libs/token-client/src/actions/revoke.rs` around lines 89 - 105, The
execute_with_owner method currently ignores self.owner and accepts any provided
owner Keypair; update execute_with_owner in revoke.rs to validate that if
self.owner is Some(pubkey) it equals the supplied owner.pubkey(), mirroring the
guard used in Approve (return a RpcError/CustomError when mismatched). Locate
the execute_with_owner function and add the same ownership check (compare
self.owner.unwrap() to owner.pubkey()) before building the instruction and
signer list so mismatched callers get a clear error instead of silent behavior.
| let source_owner = source_account.owner; | ||
| let destination_owner = destination_account.owner; | ||
|
|
||
| // Build SplInterface if needed for cross-interface transfers | ||
| let spl_interface = if let Some(spl_program) = self.spl_token_program { | ||
| let (spl_interface_pda, spl_interface_pda_bump) = | ||
| find_spl_interface_pda_with_index(&self.mint, 0, self.restricted); | ||
| Some(SplInterface { | ||
| mint: self.mint, | ||
| spl_token_program: spl_program, | ||
| spl_interface_pda, | ||
| spl_interface_pda_bump, | ||
| }) | ||
| } else { | ||
| None | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's find and read the file in question
fd -e rs transfer_interface | head -20Repository: Lightprotocol/light-protocol
Length of output: 366
🏁 Script executed:
# Get the structure and read the critical sections
head -100 sdk-libs/token-client/src/actions/transfer_interface.rs | tail -50Repository: Lightprotocol/light-protocol
Length of output: 1828
🏁 Script executed:
# Read the wrap.rs reference file
fd -e rs wrap.rs | grep token-clientRepository: Lightprotocol/light-protocol
Length of output: 211
🏁 Script executed:
# Look at the spl_interface module to confirm available exports
fd spl_interface -e rsRepository: Lightprotocol/light-protocol
Length of output: 218
🏁 Script executed:
# Check the light_token module structure
git ls-files | grep light.token | head -20Repository: Lightprotocol/light-protocol
Length of output: 1455
🏁 Script executed:
# Read the full transfer_interface.rs file
cat -n sdk-libs/token-client/src/actions/transfer_interface.rsRepository: Lightprotocol/light-protocol
Length of output: 5137
🏁 Script executed:
# Read wrap.rs to see the reference implementation
cat -n sdk-libs/token-client/src/actions/wrap.rs | head -130Repository: Lightprotocol/light-protocol
Length of output: 4821
🏁 Script executed:
# Check what's exported from spl_interface module
cat -n sdk-libs/token-sdk/src/spl_interface.rs | head -80Repository: Lightprotocol/light-protocol
Length of output: 786
🏁 Script executed:
# Check error handling in the token-client module
grep -r "RpcError" sdk-libs/token-client/src/ -A 2 | head -40Repository: Lightprotocol/light-protocol
Length of output: 2893
🏁 Script executed:
# Check where the SPL token program constants come from
rg "SPL_TOKEN_PROGRAM_ID|SPL_TOKEN_2022_PROGRAM_ID" sdk-libs/token-sdk/src/ -B 2 -A 2Repository: Lightprotocol/light-protocol
Length of output: 488
🏁 Script executed:
# Check what's exported from light_token constants
fd constants.rs | xargs grep -l "SPL_TOKEN" 2>/dev/nullRepository: Lightprotocol/light-protocol
Length of output: 156
🏁 Script executed:
# Check the light_token crate structure
git ls-files | grep -E "light.token|light-token" | grep -E "(src|lib)" | head -20Repository: Lightprotocol/light-protocol
Length of output: 1038
🏁 Script executed:
# Check the actual spl_interface in light_token to see what it exports
git ls-files | grep "light.*token.*spl_interface"Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Look at the compressed-token-sdk spl_interface that's re-exported
fd spl_interface | xargs wc -l | sort -nRepository: Lightprotocol/light-protocol
Length of output: 244
🏁 Script executed:
# Check the light_token crate - is it a program or library?
git ls-files | grep -E "programs/.*light.token|programs/.*light-token" | head -10Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check where wrap.rs gets the constants from
cat -n sdk-libs/token-client/src/actions/wrap.rs | grep -E "^[[:space:]]*(1|7|8|9)" | head -20Repository: Lightprotocol/light-protocol
Length of output: 1039
🏁 Script executed:
# Verify the import path in wrap.rs and see if constants are available
rg "from light_token" sdk-libs/token-client/src/actions/wrap.rsRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check token-types constants
cat -n sdk-libs/token-types/src/constants.rs | head -30Repository: Lightprotocol/light-protocol
Length of output: 1387
🏁 Script executed:
# Check the test file for transfer_interface to understand expected behavior
cat -n sdk-tests/sdk-light-token-test/src/transfer_interface.rs | head -100Repository: Lightprotocol/light-protocol
Length of output: 4370
🏁 Script executed:
# Check what light_token actually exports and where constants come from
fd -e rs lib.rs | xargs grep -l "light_token" | head -5Repository: Lightprotocol/light-protocol
Length of output: 270
🏁 Script executed:
# Check the light_token crate's structure
git ls-files | grep "^programs/.*light" | grep -E "(src|lib)" | head -20Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Find light_token program
find . -name "Cargo.toml" -path "*/programs/*" | xargs grep -l "light.token\|light-token" 2>/dev/null | head -5Repository: Lightprotocol/light-protocol
Length of output: 195
🏁 Script executed:
# Check what transfer_interface instruction expects
rg "TransferInterfaceInstruction\|TransferInterface {" programs/ -A 10 | head -50Repository: Lightprotocol/light-protocol
Length of output: 233
🏁 Script executed:
# Verify by checking how wrap.rs uses the constants and whether transfer_interface follows same pattern
rg "SPL_TOKEN_PROGRAM_ID\|SPL_TOKEN_2022_PROGRAM_ID" sdk-libs/token-client/src/ -B 2 -A 2Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Fix the regex - check for TransferInterface in light_token
rg "TransferInterfaceInstruction|struct TransferInterface" programs/ -A 10 | head -60Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check if there's a light_token program or if it's a library/dependency
find . -name "Cargo.toml" | xargs grep -l "light.token\|light-token" | grep programs | head -5Repository: Lightprotocol/light-protocol
Length of output: 195
🏁 Script executed:
# Check wrap.rs more carefully - where does light_token come from?
grep "use light_token" sdk-libs/token-client/src/actions/wrap.rsRepository: Lightprotocol/light-protocol
Length of output: 90
🏁 Script executed:
# Search for light_token in Cargo files to understand where it comes from
find . -name "Cargo.toml" | xargs grep "light.token\|light-token" | grep -v "\.lock" | head -20Repository: Lightprotocol/light-protocol
Length of output: 1762
🏁 Script executed:
# Check the transfer_interface test for how it handles SPL program validation
cat -n sdk-tests/sdk-light-token-test/src/transfer_interface.rs | head -200Repository: Lightprotocol/light-protocol
Length of output: 5660
🏁 Script executed:
# Check the actual SDK test to see what validation is expected
cat -n sdk-tests/token-client-test/tests/test_transfer_interface.rs 2>/dev/null || echo "File not found, checking other locations"Repository: Lightprotocol/light-protocol
Length of output: 7539
🏁 Script executed:
# Check if the test file exists
fd test_transfer_interface.rs | xargs ls -laRepository: Lightprotocol/light-protocol
Length of output: 296
🏁 Script executed:
# Read the actual token-client test
head -300 sdk-tests/token-client-test/tests/test_transfer_interface.rsRepository: Lightprotocol/light-protocol
Length of output: 6034
🏁 Script executed:
# Look at the light_token constants module to verify what's exported
cat -n program-libs/token-interface/src/constants.rs 2>/dev/null | head -30 || cat -n programs/compressed-token/anchor/src/constants.rs 2>/dev/null | head -30Repository: Lightprotocol/light-protocol
Length of output: 1706
🏁 Script executed:
# Check the light_token crate itself
find . -path "*/light-token*" -name "lib.rs" -o -path "*/light-token*" -name "mod.rs" | head -10Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Verify the exact import path for the SPL constants
rg "SPL_TOKEN_PROGRAM_ID|SPL_TOKEN_2022_PROGRAM_ID" --type rs | grep "^[^:]*:use\|^[^:]*:pub\|^[^:]*:const" | head -20Repository: Lightprotocol/light-protocol
Length of output: 98
🏁 Script executed:
# Check what light_token module exports
find . -path "*/light-token*" -type d | head -10Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Look at the actual dependencies in Cargo.toml for token-client
grep -A 20 "\[dependencies\]" sdk-libs/token-client/Cargo.toml | head -30Repository: Lightprotocol/light-protocol
Length of output: 918
🏁 Script executed:
# Verify the import works in wrap.rs
head -15 sdk-libs/token-client/src/actions/wrap.rsRepository: Lightprotocol/light-protocol
Length of output: 539
🏁 Script executed:
# Check if there are any integration tests for cross-interface transfers
rg "spl_token_program: Some" sdk-tests/ -B 3 -A 3 | head -50Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check what's in light_token::constants
rg "SPL_TOKEN" programs/compressed-token/ -B 1 -A 1 | grep -E "(pub const|SPL_TOKEN)" | head -20Repository: Lightprotocol/light-protocol
Length of output: 2710
🏁 Script executed:
# Check light-token cargo dependencies and where constants come from
grep -r "pub const SPL_TOKEN_PROGRAM_ID\|pub const SPL_TOKEN_2022_PROGRAM_ID" --include="*.rs" | head -10Repository: Lightprotocol/light-protocol
Length of output: 419
🏁 Script executed:
# Verify the actual module path in light_token that wrap.rs uses
rg "mod constants|pub mod constants" programs/Repository: Lightprotocol/light-protocol
Length of output: 293
🏁 Script executed:
# Look at what's in the actual light_token module structure
git ls-files "programs/compressed-token*/*/src/*.rs" | head -10Repository: Lightprotocol/light-protocol
Length of output: 626
🏁 Script executed:
# Check if there are any SPL<->Light transfer tests
rg "spl_token_program.*Some" sdk-tests/ -B 5 -A 5Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Find where light_token is defined
find . -name "light_token" -o -name "light-token" -type d | grep -v node_modules | grep -v targetRepository: Lightprotocol/light-protocol
Length of output: 127
🏁 Script executed:
# Check if light_token is a workspace member
grep -A 30 "\[workspace\]" Cargo.toml | head -50Repository: Lightprotocol/light-protocol
Length of output: 1001
🏁 Script executed:
# Check the light_token dependency more carefully
grep -r "name = \"light-token\"" Cargo.tomlRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Look at the actual imports that work in wrap.rs - what is light_token exporting?
python3 << 'EOF'
import subprocess
# Check all Cargo.toml files for light-token definition
result = subprocess.run(['grep', '-r', 'name = "light-token"', '--include=Cargo.toml'],
capture_output=True, text=True, cwd='.')
print("Light-token definitions:")
print(result.stdout)
# Also check for light_token (underscore version)
result = subprocess.run(['grep', '-r', 'name = "light_token"', '--include=Cargo.toml'],
capture_output=True, text=True, cwd='.')
print("\nLight_token definitions:")
print(result.stdout)
EOFRepository: Lightprotocol/light-protocol
Length of output: 175
🏁 Script executed:
# Look at what wrap.rs imports and verify they're available
rg "use light_token" sdk-libs/token-client/src/actions/wrap.rsRepository: Lightprotocol/light-protocol
Length of output: 90
🏁 Script executed:
# Check the compressed-token-sdk for what it provides
head -50 sdk-libs/compressed-token-sdk/src/lib.rsRepository: Lightprotocol/light-protocol
Length of output: 1383
🏁 Script executed:
# Check what light-token (token-sdk) actually exports
cat -n sdk-libs/token-sdk/src/lib.rsRepository: Lightprotocol/light-protocol
Length of output: 4779
🏁 Script executed:
# Check if constants is part of light-token exports
rg "pub mod|pub use" sdk-libs/token-sdk/src/lib.rsRepository: Lightprotocol/light-protocol
Length of output: 364
🏁 Script executed:
# Now verify wrap.rs compiles and what it's actually importing
head -20 sdk-libs/token-sdk/src/constants.rsRepository: Lightprotocol/light-protocol
Length of output: 762
🏁 Script executed:
# Verify SPL_TOKEN_PROGRAM_ID and SPL_TOKEN_2022_PROGRAM_ID are exported from light_token::constants
rg "pub.*const.*SPL_TOKEN" sdk-libs/compressed-token-sdk/src/constants.rsRepository: Lightprotocol/light-protocol
Length of output: 158
🏁 Script executed:
# Check the exact re-export chain to confirm they're publicly available via light_token
cat -n sdk-libs/token-sdk/src/constants.rsRepository: Lightprotocol/light-protocol
Length of output: 1843
🏁 Script executed:
# Verify the suggested imports would work by checking if they're public
rg "pub use.*SPL_TOKEN\|pub const.*SPL_TOKEN" sdk-libs/ --no-heading | head -10Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Final verification: does wrap.rs actually compile and use these constants successfully?
rg "SPL_TOKEN_PROGRAM_ID\|SPL_TOKEN_2022_PROGRAM_ID" sdk-libs/token-client/src/actions/wrap.rsRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Let me directly check wrap.rs line 7 which we already saw
sed -n '1,15p' sdk-libs/token-client/src/actions/wrap.rsRepository: Lightprotocol/light-protocol
Length of output: 539
🏁 Script executed:
# Verify the exact path used in wrap.rs
rg "use light_token::" sdk-libs/token-client/src/actions/wrap.rs -A 3Repository: Lightprotocol/light-protocol
Length of output: 262
🏁 Script executed:
# Now let's check if transfer_interface.rs needs similar validation by looking at the whole spl_interface module
cat -n sdk-libs/token-sdk/src/spl_interface.rsRepository: Lightprotocol/light-protocol
Length of output: 786
Add SPL token program validation to guard against mismatched or unsupported programs.
Lines 86-97 build SplInterface whenever spl_token_program is provided, but the code never validates that the program matches the account owners or is a supported SPL implementation. This defeats the "auto-route" promise and can silently fail or route to the wrong program. Follow the pattern in wrap.rs (lines 69-80): require spl_token_program when either owner is SPL Token or Token-2022, validate it's a supported program, and verify it matches the owner.
The required imports are available via light_token::constants::{SPL_TOKEN_PROGRAM_ID, SPL_TOKEN_2022_PROGRAM_ID}, just as wrap.rs uses them.
🤖 Prompt for AI Agents
In `@sdk-libs/token-client/src/actions/transfer_interface.rs` around lines 82 -
97, Add validation before constructing SplInterface: check source_account.owner
and destination_account.owner for SPL_TOKEN_PROGRAM_ID or
SPL_TOKEN_2022_PROGRAM_ID and require self.spl_token_program be Some when either
owner is an SPL owner; verify self.spl_token_program equals the matching owner
program and that it is one of the supported constants (SPL_TOKEN_PROGRAM_ID or
SPL_TOKEN_2022_PROGRAM_ID) before calling find_spl_interface_pda_with_index and
creating SplInterface; if validation fails return an appropriate error. Use the
same guard pattern as wrap.rs (lines referenced) and the light_token::constants
to compare program IDs, referencing spl_token_program, source_account.owner,
destination_account.owner, SplInterface, and find_spl_interface_pda_with_index.
Summary
light-token-clientinto clean action structs (CreateMint, CreateAta, MintTo, Transfer, TransferChecked, TransferInterface, Approve, Revoke, Wrap, Unwrap)program-tests/utils/src/actions/legacy/CLAUDE.mdfortoken-clientcrateapprove_checkedinstruction from token-sdkTest plan
token-client-testtests pass (approve/revoke, create_mint, transfer, transfer_checked, transfer_interface, wrap/unwrap)Summary by CodeRabbit
New Features
Refactoring
✏️ Tip: You can customize this high-level summary in your review settings.