spl-token & spl-associated-token-account
The SPL Token program defines mints and token accounts; the Associated Token Account (ATA) program derives canonical wallet-per-mint addresses. Rust crates spl-token and spl-associated-token-account (plus anchor-spl wrappers) encode layouts and CPI helpers for Agave 4.1.1 programs and clients.
Recipe
Quick-reference recipe card - copy-paste ready.
[dependencies]
spl-token = "7.0"
spl-associated-token-account = "6.0"use spl_associated_token_account::get_associated_token_address;
use spl_token::state::Mint;
let ata = get_associated_token_address(&owner, &mint);When to reach for this:
- Programs that transfer, mint, or burn SPL tokens via CPI.
- Clients resolving ATA addresses before building instructions.
- Parsing mint decimals and token account balances off-chain.
- Integrating with Token-2022 extensions via sibling crates.
Working Example
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token::{self, Mint, Token, TokenAccount, Transfer},
};
#[derive(Accounts)]
pub struct Pay<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(mut)]
pub payer_ata: Account<'info, TokenAccount>,
#[account(mut)]
pub merchant_ata: Account<'info, TokenAccount>,
pub mint: Account<'info, Mint>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
}
pub fn pay(ctx: Context<Pay>, amount: u64) -> Result<()> {
require!(amount > 0, ErrorCode::InvalidAmount);
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.payer_ata.to_account_info(),
to: ctx.accounts.merchant_ata.to_account_info(),
authority: ctx.accounts.payer.to_account_info(),
},
),
amount,
)?;
Ok(())
}
#[error_code]
pub enum ErrorCode {
#[msg("amount must be positive")]
InvalidAmount,
}What this demonstrates:
TokenAccountandMinttypes validate owners and layouts.- Transfers CPI into SPL Token with correct authority signer.
- ATA program is available when instructions create ATAs.
- Checked
amountbefore token movement.
Deep Dive
How It Works
- Mint accounts store supply, decimals, authorities.
- Token accounts hold balances per mint for a single owner.
- ATA program derives PDAs deterministically from wallet + mint + token program id.
- Token program ID
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DAon classic SPL.
Rust Crate Table
| Crate | Role | Notes |
|---|---|---|
spl-token | Token instruction builders, state layouts | On-chain + off-chain |
spl-associated-token-account | ATA derivation and create instructions | Pair with token program id |
anchor-spl | Typed wrappers and CPI helpers | Anchor 0.32.1 projects |
spl-token-2022 | Token extensions program | Separate program id |
TypeScript / NPM Table
| Package | Role | Pin |
|---|---|---|
@solana/spl-token | ATA + token ix helpers | Match token program version |
@solana/kit | Lower-level tx with imported ix | 7.0.0 |
| Codama-generated clients | Program-specific token flows | From your IDL |
Rust Notes
// Always use checked math for human amounts
let raw = amount
.checked_mul(10u64.pow(mint.decimals as u32))
.ok_or(ProgramError::InvalidArgument)?;Gotchas
- Wrong token program id for ATA derivation - Token-2022 mints need Token-2022 program id. Fix: pass correct program id to ATA helpers.
- Transfer without delegate when using delegated authority - fails with owner mismatch. Fix: include delegate signer or use owner.
- Decimals assumed as 9 - USDC uses 6. Fix: read
mint.decimalsat runtime. - Closing ATA with dust - reopen costs rent. Fix: sweep to zero before close or keep ATA.
- Mint authority left on mainnet mints - unlimited inflation risk. Fix: revoke mint authority after launch.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
token-2022 extensions | Transfer hooks, fees | Simple fungible only |
| Wrapped SOL via native mint | SOL in SPL form | Pure lamport transfers suffice |
mpl payment flows | NFT checkout | Fungible-only payments |
| System program lamports | SOL-only tips | Need SPL composability |
FAQs
ATA always exists?
No - create with associated_token::create before first transfer to new wallet/mint pair.
Who pays ATA rent?
Payer on create instruction - often the protocol or user onboarding flow.
Multisig token authority?
Supported via SPL multisig layouts - include all required signers.
Offline mint info?
Fetch mint account once and cache decimals in clients.
Token program CPI from native?
Use spl_token::instruction::transfer with invoke.
CLI parity?
spl-token CLI from Solana 3.0.10 bundle mirrors many operations.
Wrapped SOL mint?
So11111111111111111111111111111111111111112 - treat decimals as 9.
Permit delegate?
Approve instruction sets delegate allowance - watch security on mainnet.
Immutable owner extension?
Token-2022 feature - plan ATA creation before enabling.
Indexers?
Subscribe to token program for balance dashboards at scale.
Related
- anchor-lang & anchor-spl - Anchor SPL helpers
- SPL Token Basics - concepts
- Associated Token Accounts - ATA guide
- Token-2022 Basics - extensions
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.