Skip to content

Revamp svm::send_token#417

Open
aspnxdd wants to merge 8 commits into
solana-foundation:mainfrom
aspnxdd:feat/revamp-svm-send-token
Open

Revamp svm::send_token#417
aspnxdd wants to merge 8 commits into
solana-foundation:mainfrom
aspnxdd:feat/revamp-svm-send-token

Conversation

@aspnxdd

@aspnxdd aspnxdd commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Closes #324

Besides what was requested in issue 324, few notes:

  • Add support for token2022 (sending, deriving ATAs). We can use both for spl and t22: spl_token_2022_interface::instruction::transfer_checked (requires decimal arg, which is obtained decoding the mint account. Base layout is shared between both programs)

  • Added tests for some functions and helpers.

  • For the funding ata recipient case: we could use the idempotent ix to create the ATA to save 1 RPC call to check if the ATA exists, but then in the output we can't know for sure if the ATA was actually created or not.

.tx file I used for e2e testing:

addon "svm" {
  network_id  = "localnet"
  rpc_api_url = "http://localhost:8899"
}

signer "authority" "svm::secret_key" {
  keypair_json = "~/.config/solana/id.json"
}

variable "paypal_usd" {
  value = "2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo"
}

variable "wallet_address" {
  value = "<wallet_pubkey>"
}

variable "paypal_usd_ata" {
    value = svm::get_associated_token_account(variable.wallet_address, variable.paypal_usd, "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")
}

action "set_my_paypal_usd_amount" "svm::setup_surfnet" {
  set_token_account {
    public_key = variable.wallet_address
    token = variable.paypal_usd
    amount = 9999999
    token_program = "token2022"
  }
}

action "send_paypal_usd" "svm::send_token" {
  description = "Send 5 paypal_usd"
  amount = 5000000
  signers = [signer.authority]
  recipient = "zbBjhHwuqyKMmz8ber5oUtJJ3ZV4B6ePmANfGyKzVGV"
  mint = variable.paypal_usd
  fund_recipient = true
}

output "mint_address" {
  value = action.send_paypal_usd.mint_address
}
output "token_deployment_signature" {
  value = action.send_paypal_usd.signature
}
output "recipient_associated_token_address" {
  value = action.send_paypal_usd.recipient_associated_token_address
}
output "sender_associated_token_address" {
  value = action.send_paypal_usd.sender_associated_token_address
}
output "authority_address" {
  value = action.send_paypal_usd.authority_address
}
output "recipient_address" {
  value = action.send_paypal_usd.recipient_address
}
output "is_funding_recipient" {
  value = action.send_paypal_usd.is_funding_recipient
}
output "sender_address" {
  value = action.send_paypal_usd.sender_address
}

@aspnxdd aspnxdd marked this pull request as ready for review June 20, 2026 12:51
@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown

Greptile Summary

This PR revamps svm::send_token to support Token-2022 mints alongside standard SPL tokens, replace the deprecated transfer instruction with transfer_checked (which requires and validates decimal precision), and rename all outputs to more descriptive names. It also extracts several pure helper functions (derive_send_token_associated_accounts, unpack_mint_decimals, build_send_token_instructions, insert_send_token_outputs) which are each covered by unit tests.

  • Token-2022 support: the token program (spl_token_interface vs spl_token_2022_interface) is now detected at runtime by fetching the mint account and reading its owner field, so ATAs and instructions use the correct program automatically.
  • Breaking output renames: recipient_token_addressrecipient_associated_token_address, source_token_addresssender_associated_token_address, token_mint_addressmint_address; plus three new outputs (authority_address, sender_address, recipient_address, is_funding_recipient).
  • New mint input alias: the existing token input becomes a deprecated alias for the new mint input; both are optional: true in the spec with runtime validation enforcing that at least one is present.

Confidence Score: 5/5

The change is safe to merge — the logic is correct, the token-2022 upgrade path is handled cleanly, and the helper functions are well-tested.

The revamp is straightforward: the program-id is derived from the on-chain mint account so there is no risk of sending a token-2022 mint through the wrong program, transfer_checked adds a decimal guard against mismatched precision, and the new helper functions are each exercised by dedicated unit tests. The only rough edges are a retained substring-match for AccountNotFound (pre-existing pattern) and documentation-only clarifications for the optional mint/token inputs — neither affects correctness at runtime.

No files require special attention; send_token.rs has the most surface area but is well-covered by the new tests.

Important Files Changed

Filename Overview
addons/svm/core/src/commands/send_token.rs Core revamp: switches to transfer_checked with token-2022 support, derives token_program_id from on-chain mint account, renames all outputs (breaking), extracts helper functions, and adds comprehensive unit tests.
addons/svm/core/src/commands/setup_surfnet/tokens.rs Adds WSOL to the mainnet token registry and exposes get_mainnet_token_by_name as a convenience wrapper; module visibility promoted to pub.
addons/svm/core/src/commands/setup_surfnet/mod.rs Single-line visibility change: mod tokenspub mod tokens to allow the new import from send_token.rs.
addons/svm/core/src/constants.rs Adds MINT, SENDER_ADDRESS, MINT_ADDRESS, RECIPIENT_ATA, and SENDER_ATA constants; retains the now-unused TOKEN_MINT_ADDRESS. RECIPIENT_TOKEN_ADDRESS and SOURCE_TOKEN_ADDRESS are removed.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[check_signed_executability] --> B{Resolve mint address}
    B -->|symbol e.g. 'usdc'| C[get_mainnet_token_by_name]
    B -->|raw pubkey string| D[Pubkey::from_str]
    C --> E[Fetch mint account via RPC]
    D --> E
    E --> F[Extract token_program_id + mint_data]
    F --> G[derive_send_token_associated_accounts\nsender_ata + recipient_ata]
    G --> H{Check recipient_ata on-chain}
    H -->|AccountNotFound or lamports==0| I[recipient_needs_funding = true]
    H -->|Account exists| J[recipient_needs_funding = false]
    I --> K[unpack_mint_decimals from mint_data]
    J --> K
    K --> L[build_send_token_instructions]
    L --> M{recipient_needs_funding?}
    M -->|No| N[VecDeque: transfer_checked only]
    M -->|Yes, fund_recipient=true| O[VecDeque: create_ata + transfer_checked]
    M -->|Yes, fund_recipient=false| P[Error: recipient unfunded]
    N --> Q[Build + sign Transaction]
    O --> Q
    Q --> R[insert_send_token_outputs]
    R --> S[run_signed_execution]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[check_signed_executability] --> B{Resolve mint address}
    B -->|symbol e.g. 'usdc'| C[get_mainnet_token_by_name]
    B -->|raw pubkey string| D[Pubkey::from_str]
    C --> E[Fetch mint account via RPC]
    D --> E
    E --> F[Extract token_program_id + mint_data]
    F --> G[derive_send_token_associated_accounts\nsender_ata + recipient_ata]
    G --> H{Check recipient_ata on-chain}
    H -->|AccountNotFound or lamports==0| I[recipient_needs_funding = true]
    H -->|Account exists| J[recipient_needs_funding = false]
    I --> K[unpack_mint_decimals from mint_data]
    J --> K
    K --> L[build_send_token_instructions]
    L --> M{recipient_needs_funding?}
    M -->|No| N[VecDeque: transfer_checked only]
    M -->|Yes, fund_recipient=true| O[VecDeque: create_ata + transfer_checked]
    M -->|Yes, fund_recipient=false| P[Error: recipient unfunded]
    N --> Q[Build + sign Transaction]
    O --> Q
    Q --> R[insert_send_token_outputs]
    R --> S[run_signed_execution]
Loading

Reviews (3): Last reviewed commit: "feat: add get_mainnet_token_by_name" | Re-trigger Greptile

Comment thread addons/svm/core/src/commands/send_token.rs Outdated
Comment thread addons/svm/core/src/commands/send_token.rs
@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown

Want your agent to iterate on Greptile's feedback? Try greploops.

@aspnxdd

aspnxdd commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

@MicaiahReid just tagging you for visibility 👀

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.

SVM: revamp send_token action

1 participant