The SPL Token Program
On Solana, fungible (and many NFT-style) assets are not balances stored inside user wallets the way a bank ledger stores a single row per customer.
Search across all documentation pages
On Solana, fungible (and many NFT-style) assets are not balances stored inside user wallets the way a bank ledger stores a single row per customer.
They are accounts owned by the SPL Token program, which enforces mint supply, who can move funds, and how amounts are stored.
SPL Token Basics shows create-mint and ATA recipes; Associated Token Accounts (ATAs), Authorities, Decimals & Amounts, Minting & Burning, and SPL Token in Programs each zoom in on one layer.
This page is the layer underneath: the single model that makes those pages feel like one system rather than a pile of CLI flags.
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA) owns mint accounts (token definition and supply) and token accounts (balances for a mint under an authority), and only mutates them through its instructions.transfer_checked, PDA mint/vault authorities, Token-2022 program id.SPL Token separates what a token is from who holds how much.
A mint account is the type definition for one token: decimals, total supply, mint authority (who may increase supply), and freeze authority (who may freeze token accounts for that mint).
The mint does not store per-wallet balances. Creating a mint does not give anyone tokens until a mint instruction credits a token account.
A token account holds a balance for exactly one mint under one authority (usually a wallet or a PDA).
Its state includes the mint pubkey, the owner/authority, the raw amount, optional delegate and delegated amount, and freeze state.
Wallets "have USDC" only in the sense that they control a token account whose mint is the USDC mint and whose amount is non-zero.
That split is why you never send SPL tokens to "the mint address" as if it were a bank account, and why listing only a wallet pubkey is incomplete without mint context.
Associated Token Accounts (ATAs) are the social and tooling convention that removes address chaos.
For a given (wallet, mint, token program) triple, the Associated Token program derives a deterministic PDA as the preferred token account.
Wallets, explorers, and indexers assume that address when a user says "send me this token."
You can still create non-ATA token accounts; production UX almost always creates or uses the ATA.
Authorities are the trust surface of a mint and of each token account.
None.None.Revoking mint and freeze authorities is a common launch signal: fixed or community-controlled supply and no freeze power.
Decimals and amounts close the foundation.
On-chain storage is always a raw integer (u64). Decimals on the mint only define the human scale: with 6 decimals, UI 1.5 is raw 1_500_000.
Clients, CLIs, and programs that mix UI floats with raw fields without converting are a top source of 1000x and 1e6x bugs.
Classic SPL Token program id:
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Token accounts for classic SPL Token are 165 bytes and must remain rent-exempt; ATAs are still just token accounts at a derived address.
The productive token lifecycle is ordered: define the mint, create a holder account, change supply or move balances, then (optionally) lock authorities.
mint account token account / ATA ops
------------ ------------------- ---
create mint ──► set decimals + ──► create ATA (or ──► mint_to
(Token prog) authorities token account) transfer
│ │ │ burn
│ │ │ approve
▼ ▼ ▼ freeze
supply on mint authority keys balance, delegate set_authority
(raw total) mint / freeze freeze state close accountA mint is created as an account owned by the Token program, initialized with decimals and authorities (and usually funded rent-exempt by a fee payer).
spl-token create-token --decimals 6 (Solana CLI 3.0.10 stack) is the operator path; @solana/kit 7.0.0 and @solana-program/token build the same initialize-mint instructions for apps.
Until you mint, supply is zero; decimals never change after initialization on classic SPL Token.
Balances need a destination account for that mint.
ATA creation is typically one instruction (or an idempotent create) that derives the address, creates the account if missing, and initializes it for the mint and owner.
Clients that transfer without ensuring the recipient ATA exists fail with missing-account errors; production flows use create-idempotent or check-then-create.
Prefer transfer_checked / mint_to_checked style instructions when available so the mint and decimals are explicit in the instruction, catching mint-mismatch mistakes early.
Raw amounts only in instruction data: if the mint has 9 decimals and you want 5 whole tokens, the instruction amount is 5_000_000_000, not 5.
The spl-token CLI is the exception, and the direction catches people out: its amount arguments are UI amounts that it scales by the mint's decimals for you. spl-token mint <MINT> 5 mints five tokens; spl-token mint <MINT> 5000000000 mints five billion.
Early: mint authority is a deployer key or multisig; freeze authority is optional.
After distribution: many projects disable mint authority (no more inflation) and freeze authority (no freeze risk), or move them to a DAO/PDA with documented policy.
Token account ownership can be a user wallet or a PDA so a program can sign transfers and burns with seeds (vaults, escrows, staking).
Off-chain: CLI, kit clients, and wallets build Token program instructions into transactions (fee payer, recent blockhash, signatures) like any other Solana work.
On-chain: your program never "writes token balances" in its own account layout for SPL assets; it CPIs into the Token program with the correct accounts, amounts, and signer privileges.
Anchor 0.32.1 anchor-spl types (Mint, TokenAccount, Token, TransferChecked, MintTo, Burn) validate ownership and fields, then issue those CPIs. Use TransferChecked, not the deprecated Transfer. Note that anchor_spl::token::{Mint, TokenAccount} are classic-Token-only and their owner check rejects Token-2022 accounts; a program that must accept either uses anchor_spl::token_interface instead.
A minimal operator smoke path (not a full product recipe):
spl-token create-token --decimals 6
spl-token create-account <MINT>
spl-token mint <MINT> 1 # UI amount: 1 token = raw 1_000_000 at 6 decimals
spl-token display <MINT>Mechanics to keep: mint defines type and supply; ATAs hold balances; authorities gate mutations; amounts are raw.
| Concern | Classic SPL Token approach | Why it matters | Deeper page |
|---|---|---|---|
| Recipient address UX | Prefer ATAs per wallet+mint | Wallets and indexers agree on one address | ATAs |
| Supply policy | Mint authority then revoke or DAO | Trust and tokenomics | Authorities, Minting & Burning |
| UI vs chain math | Always convert with decimals | Prevents catastrophic unit bugs | Decimals & Amounts |
| Program vaults | PDA authority on vault ATA; CPI transfer | Escrow without custodial hot wallets | SPL Token in Programs |
| Checked instructions | Pass mint + decimals on transfer/mint | Rejects wrong mint accounts | In-programs and client builders |
| Extensions (fees, hooks) | Use Token-2022, not classic Token | Different program id and account layouts | Token-2022 section (sibling) |
PDA authorities are how DeFi programs custody assets safely: the vault ATA's authority is a PDA your program can sign for with invoke_signed, not a human key on a server.
Validate mint, ATA derivation, and amount in the same instruction that moves funds.
create_idempotent ATA paths reduce race failures when two txs try to create the same ATA.
Delegates enable limited spend allowance (approvals) without transferring ownership; clear or overwrite carefully so leftover allowances do not surprise users.
Wrapped SOL is classic SPL Token used as a bridge: native SOL is wrapped into a token account so programs can treat SOL like any other mint in CPI paths. The mint and wrap/unwrap flows are special-cased tooling on top of the same token account model.
Token-2022 is a separate program with mint/token account extensions. Do not assume Tokenkeg accounts and Token-2022 accounts are interchangeable; program id, ATAs (token program in seeds), and instruction sets must match.
Clients on @solana/kit 7.0.0 should use program-specific instruction builders and PDA helpers rather than hand-rolled byte layouts.
Programs on Anchor 0.32.1 / Rust 1.91.1 should pin anchor-spl to the same release line and prefer constraint macros (token::mint, associated_token::...) so invalid accounts fail before CPI.
u64 raw units; decimals only scale display and conversion.transfer is deprecated, and Token-2022 rejects it outright on mints with a transfer-fee extension.spl-token amounts are raw units like instruction data." They are UI amounts. The CLI multiplies by 10^decimals for you, so a "raw" value overspends by that factor.The on-chain program that owns mint and token accounts and enforces minting, burning, transfers, freezes, and authorities for classic Solana tokens.
A mint defines the token type, decimals, supply, and mint/freeze authorities; a token account holds a raw balance for one mint under one owner/authority.
An Associated Token Account is the canonical PDA token account for a wallet and mint (and token program), derived so everyone agrees where that user's balance lives.
Only the current mint authority (a keypair, multisig, or PDA that signs the mint instruction), and only while that authority is still set on the mint.
The mint's freeze authority, if one is set; if freeze authority is disabled, accounts for that mint cannot be frozen through the Token program.
They often show raw units. Divide by 10^decimals for UI amounts, or use tooling that formats with the mint's decimals.
No. Transfer moves raw amount between token accounts of the same mint. Mint increases supply; burn decreases it.
No. Each token account is initialized for exactly one mint. A wallet uses a separate token account (usually a separate ATA) per mint.
By CPI into the Token program with the right accounts and authority (user signature or PDA invoke_signed), typically via anchor-spl helpers on Anchor 0.32.1.
When you want a fixed maximum supply and no further inflation under that authority - a common post-distribution or launch-hardening step. See Authorities.
165 bytes of data, allocated rent-exempt so the account is not garbage-collected; ATAs use that same token account layout at a derived address.
It builds and sends the same Token and Associated Token instructions against RPC; the account model does not change, only the client ergonomics.
Wrapped SOL uses an SPL mint and token accounts so SOL can participate in token CPIs; wrap and unwrap bridge native lamports and token balances.
Use classic SPL Token when you need maximum compatibility and no extensions; choose Token-2022 when you need extension features and can require its program id and ATA derivation everywhere.
Work through SPL Token Basics, then ATAs, mint/burn, authorities, decimals, and on-chain CPI patterns in the Related list below.
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1, and @solana/kit 7.0.0.
Reviewed by Chris St. John·Last updated Jul 19, 2026