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.
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.
Summary
- The classic SPL Token program (
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. - Insight: Wrong authority, wrong raw amount, missing ATA, or CPI without the Token program id are the usual "tokens disappeared / supply wrong / transfer failed" bugs - not Solana consensus itself.
- Key Concepts: mint, token account, ATA, mint authority, freeze authority, decimals, raw amount, mint / burn / transfer, delegate, Token program CPI, Token-2022 as a sibling standard.
- When to Use: Launching or integrating fungible tokens, building wallets and dapps that hold balances, writing Anchor vaults/escrows, or teaching how Solana represents assets before Token-2022 extensions.
- Limitations/Trade-offs: Classic SPL Token has a fixed account layout and no extensions (transfer fees, interest, confidential transfers live on Token-2022). ATAs are the UX standard, but any valid token account can still hold balances if you allow it.
- Related Topics: Associated Token program, rent-exempt account size,
transfer_checked, PDA mint/vault authorities, Token-2022 program id.
Foundations
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.
- Mint authority (on the mint): may mint more supply into token accounts until set to
None. - Freeze authority (on the mint): may freeze or thaw token accounts for that mint until set to
None. - Token account authority (owner): may transfer, burn (from that account), approve a delegate, or close the account when empty (subject to freeze state and program rules).
- Delegate (optional on a token account): may transfer or burn up to an approved raw amount without being the owner.
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.
Mechanics & Interactions
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 account1. Create and configure the mint
A 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.
2. Create a token account or ATA for each holder
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.
3. Mint, transfer, burn
- Mint (mint_to): requires mint authority signature (or PDA sign via CPI). Increases mint supply and credits a token account for that mint.
- Transfer: requires the token account authority (or a delegate within allowance). Moves raw amount between two token accounts of the same mint. Does not change total supply.
- Burn: requires the token account authority (or delegate). Decreases the account balance and mint supply.
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: if the mint has 9 decimals and you want 5 whole tokens, the instruction amount is 5_000_000_000, not 5.
4. Authorities over the lifecycle
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).
5. Clients and programs share one instruction surface
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, Transfer / TransferChecked, MintTo, Burn) validate ownership and fields, then issue those CPIs.
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> 1000000
spl-token display <MINT>Mechanics to keep: mint defines type and supply; ATAs hold balances; authorities gate mutations; amounts are raw.
Advanced Considerations & Applications
| 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.
Common Misconceptions
- "My wallet balance is stored on my system account." SOL lamports live on the system account; SPL balances live on Token-owned token accounts keyed by mint.
- "Send tokens to the mint address." The mint is the type and supply ledger, not a user balance inbox. Send to the recipient's token account (usually their ATA).
- "Decimals change how many bytes the amount uses." Amount is always
u64raw units; decimals only scale display and conversion. - "Creating a mint mints the supply." Initialization sets metadata; supply increases only through mint instructions (or stays zero).
- "Any token account for that wallet is fine for UX." Non-ATA accounts work on-chain but break wallet and explorer assumptions; prefer ATAs.
- "My program can assign token balances by writing account data." Only the Token program may mutate token account data; you CPI with proper authority.
- "transfer and transfer_checked are the same risk profile." Checked variants require the mint and decimals, reducing mint-confusion bugs.
- "Classic SPL Token and Token-2022 are drop-in replacements." Different program ids, ATAs, and capabilities; integrate deliberately.
FAQs
What is the SPL Token program in one sentence?
The on-chain program that owns mint and token accounts and enforces minting, burning, transfers, freezes, and authorities for classic Solana tokens.
What is the difference between a mint and a token account?
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.
What is an ATA?
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.
Who can mint more tokens?
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.
Who can freeze a token account?
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.
Why do CLIs and explorers show huge integer amounts?
They often show raw units. Divide by 10^decimals for UI amounts, or use tooling that formats with the mint's decimals.
Does a transfer change total supply?
No. Transfer moves raw amount between token accounts of the same mint. Mint increases supply; burn decreases it.
Can two different mints share one token account?
No. Each token account is initialized for exactly one mint. A wallet uses a separate token account (usually a separate ATA) per mint.
How do on-chain programs move SPL tokens?
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 should I revoke mint authority?
When you want a fixed maximum supply and no further inflation under that authority - a common post-distribution or launch-hardening step. See Authorities.
What size is a classic SPL token account?
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.
How does @solana/kit 7.0.0 fit this model?
It builds and sends the same Token and Associated Token instructions against RPC; the account model does not change, only the client ergonomics.
Where does wrapped SOL fit?
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.
Should new assets use classic SPL Token or Token-2022?
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.
What should I learn next after this page?
Work through SPL Token Basics, then ATAs, mint/burn, authorities, decimals, and on-chain CPI patterns in the Related list below.
Related
- SPL Token Basics - create mints, ATAs, and first balances
- Associated Token Accounts (ATAs) - derivation, create, and idempotent UX
- Authorities - mint authority, freeze authority, revoke patterns
- Decimals & Amounts - raw vs UI math without off-by-decimals bugs
- Minting & Burning - supply increases and burns
- SPL Token in Programs - Anchor CPI, vaults, and constraints
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.