Skip to content

Refactor light-token-client, add CLAUDE.md#2220

Closed
tilo-14 wants to merge 7 commits intomainfrom
jorrit/refactor-light-token-client
Closed

Refactor light-token-client, add CLAUDE.md#2220
tilo-14 wants to merge 7 commits intomainfrom
jorrit/refactor-light-token-client

Conversation

@tilo-14
Copy link
Member

@tilo-14 tilo-14 commented Jan 28, 2026

Summary

  • Refactored light-token-client into clean action structs (CreateMint, CreateAta, MintTo, Transfer, TransferChecked, TransferInterface, Approve, Revoke, Wrap, Unwrap)
  • Moved legacy test actions to program-tests/utils/src/actions/legacy/
  • Added CLAUDE.md for token-client crate
  • Added token-client integration tests
  • Removed unused approve_checked instruction from token-sdk

Test plan

  • token-client-test tests pass (approve/revoke, create_mint, transfer, transfer_checked, transfer_interface, wrap/unwrap)
  • Existing sdk-tests still pass
  • CI green

Summary by CodeRabbit

  • New Features

    • Added comprehensive action-based token operations API including mint creation, account management, minting, transfers, approvals, revocation, and wrapping/unwrapping.
    • Introduced token metadata support for enhanced mint creation with name, symbol, and URI fields.
    • Added checked transfer operations with decimal validation.
  • Refactoring

    • Reorganized token client module structure for improved clarity and usability.
    • Streamlined internal compression instruction handling.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 28, 2026

📝 Walkthrough

Walkthrough

This 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 execute methods. The token-sdk's ApproveChecked instruction is removed, consolidated into the client-level Approve action. Mint compressor logic is updated to use the new instruction builder, and test imports are relocated to the new test utilities module.

Changes

Cohort / File(s) Summary
Mint compression refactor
forester/src/compressible/mint/compressor.rs
Replaces MintActionParams-based instruction construction with direct calls to create_compress_and_close_mint_instruction across all compress methods; updates imports to remove MintAction types.
Test import relocation
forester/tests/e2e_test.rs, forester/tests/test_compressible_ctoken.rs
Moves imports for create_compressible_token_account and CreateCompressibleTokenAccountInputs from light_token_client::actions to light_test_utils::actions (and light_test_utils::actions::legacy for the ctoken test).
Token-client action API
sdk-libs/token-client/src/actions/{create_mint,transfer,approve,revoke,create_ata,mint_to,transfer_checked,transfer_interface,wrap,unwrap}.rs
Ten new action modules implementing the struct-with-execute-method pattern. Each action encapsulates instruction building and RPC transaction submission with appropriate error handling and signer management.
Token-client module structure
sdk-libs/token-client/src/actions/mod.rs, sdk-libs/token-client/src/lib.rs
Restructures public API: mod.rs declares and re-exports all action types and helpers from light_token::instruction; lib.rs exports actions::* instead of the older instructions module, making action types available at crate root.
Token-client documentation
sdk-libs/token-client/CLAUDE.md
New reference document describing the action-based API surface: lists ten action structs with fields, execution requirements (Rpc/Indexer), and signer specifications. Documents the TokenMetadata helper struct and re-exports from light_token::instruction.
Token-SDK instruction removal
sdk-libs/token-sdk/src/instruction/{approve_checked.rs,mod.rs}
Removes the entire ApproveChecked instruction struct and CPI helper (144 lines), including its instruction builder, invoke methods, and associated conversions; removes module declaration and re-export from mod.rs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Possibly related PRs

  • Lightprotocol/light-protocol#2197 — Updates forester compressor to use create_compress_and_close_mint_instruction directly, matching the main refactor of mint instruction construction.
  • Lightprotocol/light-protocol#2020 — Refactors compressible instruction builders and adds compressible-client APIs, directly related to the new instruction builder surface now consumed by the token-client actions.
  • Lightprotocol/light-protocol#2064 — Applies the same struct-based builder pattern refactor to compressed-token transfer flows, paralleling the action-struct consolidation across the light-token-client.

Suggested labels

ai-review

Suggested reviewers

  • sergeytimoshin
  • ananas-block

Poem

🎯 From scattered functions, order blooms so bright,
Action structs now guide each transaction's flight,
Execute, await, and watch the magic flow—
Where once was chaos, clarity takes the show! ✨

🚥 Pre-merge checks | ✅ 3
✅ 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 summarizes the main changes: refactoring light-token-client into action structs and adding CLAUDE.md documentation.
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
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jorrit/refactor-light-token-client

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 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.

Comment on lines +1 to +65
# 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
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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 -->

Comment on lines +8 to +10
| Struct | Fields | `execute` signers |
|--------|--------|-------------------|
| `CreateMint` | `decimals`, `freeze_authority?`, `token_metadata?`, `seed?` | `payer`, `mint_authority` |
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +122 to +130
vec![ExtensionInstructionData::TokenMetadata(
TokenMetadataInstructionData {
update_authority: Some(
metadata
.update_authority
.unwrap_or_else(|| mint_authority.pubkey())
.to_bytes()
.into(),
),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 -20

Repository: 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 -l

Repository: 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 -170

Repository: Lightprotocol/light-protocol

Length of output: 7411


🏁 Script executed:

# Look for TokenMetadata definition
rg "struct TokenMetadata" --type rs -A 15

Repository: 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 -80

Repository: Lightprotocol/light-protocol

Length of output: 98


🏁 Script executed:

# Find TokenMetadataInstructionData definition
rg "TokenMetadataInstructionData" --no-config

Repository: 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 -50

Repository: 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 -120

Repository: Lightprotocol/light-protocol

Length of output: 10163


🏁 Script executed:

# Check if addresses indexing is validated elsewhere
rg "addresses\[0\]" -B 2 -A 2

Repository: 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 -10

Repository: 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 -100

Repository: Lightprotocol/light-protocol

Length of output: 630


🏁 Script executed:

# Check if update_authority in TokenMetadataInstructionData is Option or required
rg "pub struct TokenMetadataInstructionData" -A 20

Repository: 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 2

Repository: 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 -80

Repository: 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 -30

Repository: 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 3

Repository: 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.

Suggested change
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.

Comment on lines +139 to +146
// 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())
})?,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's read the file and see the context
wc -l sdk-libs/token-client/src/actions/create_mint.rs

Repository: 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.rs

Repository: 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 -50

Repository: 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 2

Repository: Lightprotocol/light-protocol

Length of output: 591


🏁 Script executed:

# Find the get_validity_proof method definition
rg "fn get_validity_proof" sdk-libs/ -A 10

Repository: Lightprotocol/light-protocol

Length of output: 6908


🏁 Script executed:

# Look for ValidityProofResult or similar type definition
rg "addresses.*Vec|struct.*ValidityProof" sdk-libs/ -A 5

Repository: Lightprotocol/light-protocol

Length of output: 48106


🏁 Script executed:

# Check the rpc client module
find sdk-libs -name "*rpc*" -type f | head -20

Repository: 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.rs

Repository: Lightprotocol/light-protocol

Length of output: 1731


🏁 Script executed:

# Get the full ValidityProofWithContext definition
sed -n '100,130p' sdk-libs/client/src/indexer/types.rs

Repository: 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 2

Repository: 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.rs

Repository: Lightprotocol/light-protocol

Length of output: 2556


🏁 Script executed:

# Look for ValidityProofWithContext definition
rg "pub struct ValidityProofWithContext" sdk-libs/ -A 5

Repository: 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 2

Repository: 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 3

Repository: 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 -50

Repository: 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 -40

Repository: 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.

Suggested change
// 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).

Comment on lines +89 to +105
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

Comment on lines +82 to +97
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
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and read the file in question
fd -e rs transfer_interface | head -20

Repository: 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 -50

Repository: Lightprotocol/light-protocol

Length of output: 1828


🏁 Script executed:

# Read the wrap.rs reference file
fd -e rs wrap.rs | grep token-client

Repository: Lightprotocol/light-protocol

Length of output: 211


🏁 Script executed:

# Look at the spl_interface module to confirm available exports
fd spl_interface -e rs

Repository: Lightprotocol/light-protocol

Length of output: 218


🏁 Script executed:

# Check the light_token module structure
git ls-files | grep light.token | head -20

Repository: 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.rs

Repository: 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 -130

Repository: 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 -80

Repository: 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 -40

Repository: 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 2

Repository: 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/null

Repository: 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 -20

Repository: 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 -n

Repository: 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 -10

Repository: 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 -20

Repository: 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.rs

Repository: Lightprotocol/light-protocol

Length of output: 54


🏁 Script executed:

# Check token-types constants
cat -n sdk-libs/token-types/src/constants.rs | head -30

Repository: 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 -100

Repository: 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 -5

Repository: 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 -20

Repository: 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 -5

Repository: Lightprotocol/light-protocol

Length of output: 195


🏁 Script executed:

# Check what transfer_interface instruction expects
rg "TransferInterfaceInstruction\|TransferInterface {" programs/ -A 10 | head -50

Repository: 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 2

Repository: 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 -60

Repository: 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 -5

Repository: 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.rs

Repository: 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 -20

Repository: 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 -200

Repository: 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 -la

Repository: 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.rs

Repository: 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 -30

Repository: 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 -10

Repository: 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 -20

Repository: Lightprotocol/light-protocol

Length of output: 98


🏁 Script executed:

# Check what light_token module exports
find . -path "*/light-token*" -type d | head -10

Repository: 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 -30

Repository: 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.rs

Repository: 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 -50

Repository: 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 -20

Repository: 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 -10

Repository: 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 -10

Repository: 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 5

Repository: 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 target

Repository: Lightprotocol/light-protocol

Length of output: 127


🏁 Script executed:

# Check if light_token is a workspace member
grep -A 30 "\[workspace\]" Cargo.toml | head -50

Repository: Lightprotocol/light-protocol

Length of output: 1001


🏁 Script executed:

# Check the light_token dependency more carefully
grep -r "name = \"light-token\"" Cargo.toml

Repository: 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)
EOF

Repository: 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.rs

Repository: 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.rs

Repository: 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.rs

Repository: 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.rs

Repository: 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.rs

Repository: 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.rs

Repository: 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.rs

Repository: 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 -10

Repository: 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.rs

Repository: 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.rs

Repository: 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 3

Repository: 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.rs

Repository: 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.

@tilo-14 tilo-14 closed this Jan 29, 2026
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