Skip to content

Working with SPL Tokens

Tokens on the SVM are managed by on-chain programs — not by the runtime itself. The SPL Token Program is the standard implementation for fungible and non-fungible tokens. Its successor, Token-2022 (Token Extensions), adds advanced features while maintaining the same core account model. Both are available on Zink.

Upstream-compatible

The Token Program, Token-2022, and Associated Token Account program on Zink are identical to Solana mainnet. Existing token operations, client code, and CPIs work without modification.

Core concepts

The token model revolves around three account types:

Account typeProgramPurpose
Mint accountToken Program or Token-2022Defines a token: supply, decimals, authorities. One per token.
Token accountToken Program or Token-2022Holds a balance of a specific mint for a specific owner. Many per token.
Associated Token Account (ATA)ATA ProgramA deterministically-derived token account for a given wallet + mint pair.
┌──────────────┐       ┌─────────────────────┐       ┌──────────────────────┐
│ Mint Account │       │  Token Account       │       │  Token Account       │
│              │       │                      │       │                      │
│ supply: 1M   │◄──────│ mint: <this mint>    │       │ mint: <this mint>    │
│ decimals: 6  │       │ owner: Alice         │       │ owner: Bob           │
│ mint_auth: X │       │ amount: 400,000      │       │ amount: 600,000      │
│ freeze_auth: Y│      │                      │       │                      │
└──────────────┘       └─────────────────────┘       └──────────────────────┘

Mint accounts

A mint account defines a token type. Key fields:

FieldDescription
supplyTotal number of tokens currently in circulation
decimalsNumber of decimal places (e.g., 6 means 1 token = 1,000,000 base units)
mint_authorityThe public key (or PDA) authorized to mint new tokens. Can be set to None to cap supply permanently.
freeze_authorityThe public key authorized to freeze/thaw token accounts. Can be None.
is_initializedWhether the mint has been initialized

Creating a mint

bash
# CLI
spl-token create-token --decimals 6
rust
// On-chain via CPI
invoke(
    &spl_token::instruction::initialize_mint(
        token_program.key,
        mint.key,
        mint_authority.key,
        Some(freeze_authority.key),
        decimals,
    )?,
    &[mint.clone(), rent_sysvar.clone()],
)?;

Token accounts

A token account holds a balance of a specific token (mint) for a specific owner. Key fields:

FieldDescription
mintThe mint this token account is associated with
ownerThe wallet that controls this token account (can transfer, burn, close)
amountCurrent token balance in base units
delegateOptional delegated authority for a one-time transfer/burn up to delegated_amount
stateInitialized, Frozen, or Uninitialized
close_authorityOptional authority that can close this account (defaults to owner)

The owner of a token account (the wallet) is distinct from the account's on-chain owner field (which is the Token Program). The Token Program uses the owner field in the token account data to authorize operations.

Associated Token Accounts (ATAs)

Manually creating token accounts is cumbersome — you need to generate a keypair, allocate space, and initialize. The Associated Token Account (ATA) program solves this by deterministically deriving a token account address for any wallet + mint pair:

ATA address = PDA(wallet_pubkey, token_program_id, mint_pubkey)

This means every wallet has exactly one canonical ATA per token. If it doesn't exist yet, anyone can create it (the ATA program's create instruction is permissionless — the payer funds the rent-exempt minimum).

typescript
// TypeScript
import { getAssociatedTokenAddress, createAssociatedTokenAccountInstruction } from "@solana/spl-token";

const ata = await getAssociatedTokenAddress(mint, wallet);
// If ata doesn't exist yet:
transaction.add(createAssociatedTokenAccountInstruction(payer, ata, wallet, mint));

Zink recommendation

Always use ATAs when sending tokens to user wallets. This ensures consistent token account addresses across ecosystem programs and frontends.

Token operations

Transfer

rust
// CPI from your program
invoke(
    &spl_token::instruction::transfer(
        token_program.key,
        source.key,
        destination.key,
        authority.key,
        &[],
        amount,
    )?,
    &[source.clone(), destination.clone(), authority.clone()],
)?;

Mint

Requires the mint_authority to sign:

rust
invoke_signed(
    &spl_token::instruction::mint_to(
        token_program.key,
        mint.key,
        destination.key,
        mint_authority_pda.key,
        &[],
        amount,
    )?,
    &[mint.clone(), destination.clone(), mint_authority_pda.clone()],
    &[&[b"mint_authority", &[bump]]],
)?;

Burn

Destroys tokens, reducing the mint's supply:

rust
invoke(
    &spl_token::instruction::burn(
        token_program.key,
        token_account.key,
        mint.key,
        authority.key,
        &[],
        amount,
    )?,
    &[token_account.clone(), mint.clone(), authority.clone()],
)?;

Close account

Closes a token account and reclaims the rent-exempt lamports. The token account must have a zero balance:

rust
invoke(
    &spl_token::instruction::close_account(
        token_program.key,
        token_account.key,
        destination.key,  // receives the lamports
        authority.key,
        &[],
    )?,
    &[token_account.clone(), destination.clone(), authority.clone()],
)?;

Authorities

Tokens have two key authorities:

AuthorityControlsSet on
Mint authorityCan mint new tokens. Set to None to make supply fixed.Mint account
Freeze authorityCan freeze/thaw individual token accounts, preventing transfers.Mint account

Both can be transferred to a new key or revoked (set_authority instruction). For program-controlled tokens, these authorities are typically PDAs so the program can sign mint/freeze operations via CPI.

bash
# Transfer mint authority to a PDA or multisig
spl-token authorize <MINT> mint <NEW_AUTHORITY>

# Revoke mint authority permanently
spl-token authorize <MINT> mint --disable

Token-2022 (Token Extensions)

Token-2022 is the next-generation token program. It is a separate program (TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb) that supports all original Token Program functionality plus extensions — optional features that are enabled per-mint or per-token-account at creation time:

ExtensionDescription
Transfer feesAutomatic fee on every transfer, collected in a withheld balance
Interest-bearingUI-layer interest rate for display purposes (does not actually accrue tokens)
Non-transferableSoulbound tokens that cannot be transferred after minting
Permanent delegateA delegate that can never be revoked — can always transfer or burn
Transfer hookCalls a custom program on every transfer for validation or side effects
MetadataOn-chain token metadata stored directly in the mint account
Confidential transfersEncrypted balances and transfer amounts using zero-knowledge proofs
Default account stateNew token accounts start as frozen (require explicit thaw)
Group / memberToken grouping for collections

When to use Token-2022

  • Use the original Token Program for maximum compatibility — it is supported by all wallets, DEXes, and tools.
  • Use Token-2022 when you need one or more of its extensions. Note that not all ecosystem tools support Token-2022 tokens yet, so verify compatibility with wallets and frontends in advance.

Interacting with Token-2022 via CPI

The instruction interface is the same as the original Token Program — you just target a different program ID:

rust
// Use the Token-2022 program ID instead
let token_program_id = spl_token_2022::ID;

invoke(
    &spl_token_2022::instruction::transfer_checked(
        &token_program_id,
        source.key,
        mint.key,
        destination.key,
        authority.key,
        &[],
        amount,
        decimals,
    )?,
    &[source.clone(), mint.clone(), destination.clone(), authority.clone()],
)?;

Note: Token-2022 prefers transfer_checked (which validates decimals) over plain transfer.

Token account sizes

Account typeSize (bytes)Notes
Mint (Token Program)82Fixed
Token account (Token Program)165Fixed
Mint (Token-2022)82 + extensionsVariable depending on enabled extensions
Token account (Token-2022)165 + extensionsVariable depending on enabled extensions

Query the exact size and rent-exempt minimum before creating accounts. See Accounts — Rent and Fees.

Zink is a general-purpose SVM network for programs, operators, and bridge integrations.