SPL Tokens: mints, accounts and transfers
Fungible tokens on Solana are accounts owned by the Token Program. A mint account defines supply and decimals, and a token account holds a balance of one mint for one owner. The Associated Token Account (ATA) is the canonical token account address for a wallet and mint. Transfers are instructions to the Token Program. The checked variants also pass the mint and decimals so mistakes fail loudly.
Reference: Solana docs: Tokens on Solana · Anchor docs: SPL Token basics
Real code, line by line
6 excerpts from different projects. Each caption links the exact lines at the fork's pinned commit. The notes below each excerpt explain the lines that matter.
anchor/programs/valhalla/src/instructions/disburse.rs L1–26 @ 7aa9ddf · MIT · originaluse anchor_lang::prelude::*;use anchor_spl::{ associated_token::AssociatedToken, token_interface::{ mint_to, transfer_checked, Mint, MintTo, TokenAccount, TokenInterface, TransferChecked, },}; use crate::{constants, errors::ValhallaError, state::Vault, Config}; #[derive(Accounts)]pub struct DisburseVault<'info> { #[account(mut)] pub signer: Signer<'info>, pub creator: SystemAccount<'info>, pub recipient: SystemAccount<'info>, #[account(mut)] pub dev_treasury: SystemAccount<'info>, #[account(seeds = [constants::CONFIG_SEED], bump, has_one = dev_treasury)] pub config: Box<Account<'info, Config>>, #[account(- Line 5
mint_to, transfer_checked, Mint, MintTo, TokenAccount, TokenInterface, TransferChecked,: A checked token transfer, which also verifies the mint and decimals. - Line 5
mint_to, transfer_checked, Mint, MintTo, TokenAccount, TokenInterface, TransferChecked,: Mints new tokens. The mint authority must sign. - Line 14
pub signer: Signer<'info>,: Requires this account to have signed the transaction. - Line 23
#[account(seeds = [constants::CONFIG_SEED], bump, has_one = dev_treasury)]: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 23
#[account(seeds = [constants::CONFIG_SEED], bump, has_one = dev_treasury)]: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 23
#[account(seeds = [constants::CONFIG_SEED], bump, has_one = dev_treasury)]: Checks that a stored pubkey field matches the passed account.
programs/kryptpay/src/instructions/swap_exact_tokens_for_tokens.rs L72–97 @ a528c51 · MIT · original &[authority_bump], ]; let signer_seeds = &[&authority_seeds[..]]; if swap_a { token::transfer( CpiContext::new( ctx.accounts.token_program.to_account_info(), Transfer { from: ctx.accounts.trader_account_a.to_account_info(), to: ctx.accounts.pool_account_a.to_account_info(), authority: ctx.accounts.trader.to_account_info(), }, ), input, )?; token::transfer( CpiContext::new_with_signer( ctx.accounts.token_program.to_account_info(), Transfer { from: ctx.accounts.pool_account_b.to_account_info(), to: ctx.accounts.trader_account_b.to_account_info(), authority: ctx.accounts.pool_authority.to_account_info(), }, signer_seeds, ), output,- Line 74
let signer_seeds = &[&authority_seeds[..]];: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 76
token::transfer(: An SPL token transfer through the Token Program. - Line 77
CpiContext::new(: Makes a cross-program invocation into another program. - Line 88
CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
programs/poe/src/contexts/register_user.rs L43–64 @ 29a7c77 · MIT · original Ok(()) } pub fn mint_tokens(&mut self, bumps: &RegisterUserBumps) -> Result<()> { mint_to( CpiContext::new_with_signer( self.token_program.to_account_info(), MintTo { authority: self.mint.to_account_info(), to: self.token_account.to_account_info(), mint: self.mint.to_account_info(), }, &[&["poeken_mint".as_bytes(), &[bumps.mint]]], ), 1000 * 1000000000, )?; msg!("Minted tokens"); Ok(()) }}- Line 46
pub fn mint_tokens(&mut self, bumps: &RegisterUserBumps) -> Result<()> {: Mints new tokens. The mint authority must sign. - Line 48
CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address. - Line 60
msg!("Minted tokens");: Logs to the program log, which is visible in explorers and the test output.
frontend/src/hooks/useSolana.ts L53–78 @ ca496be · Apache-2.0 · original ); const { mintAddress = "" } = (mintedProducts as IMints)[type!!] ?? {}; const buyers_token_init = await getOrCreateAssociatedTokenAccount( connection, sellerAccept, new PublicKey(mintAddress), buyerWalletPK ); const buyers_usdc_token = await getOrCreateAssociatedTokenAccount( connection, sellerAccept, USDC_MINT, buyerWalletPK ); const sellers_usdc_token = await getOrCreateAssociatedTokenAccount( connection, sellerAccept, USDC_MINT, sellerAccept.publicKey ); const xRequested = +quantity * 1000000; const yAmount = +xRequested * YPRICE;anchor/programs/gree/src/lib.rs L29–54 @ 7ef8c89 · MIT · original to: ctx.accounts.associated_token_account.to_account_info(), authority: ctx.accounts.signer.to_account_info(), }; let cpi_context = CpiContext::new(ctx.accounts.token_program.to_account_info(), cpi_accounts); mint_to(cpi_context, 1)?; } // Create metadata with minimized data structure scope let data_v2 = DataV2 { name, symbol, uri, seller_fee_basis_points: 0, creators: None, collection: None, uses: None, }; { let cpi_accounts = CreateMetadataAccountsV3 { metadata: ctx.accounts.metadata_account.to_account_info(), mint: ctx.accounts.mint.to_account_info(), mint_authority: ctx.accounts.signer.to_account_info(), update_authority: ctx.accounts.signer.to_account_info(), payer: ctx.accounts.signer.to_account_info(), system_program: ctx.accounts.system_program.to_account_info(), rent: ctx.accounts.rent.to_account_info(),- Line 32
let cpi_context = CpiContext::new(ctx.accounts.token_program.to_account_info(), cpi_accoun: Makes a cross-program invocation into another program. - Line 33
mint_to(cpi_context, 1)?;: Mints new tokens. The mint authority must sign.
app_page/token_faucet/faucet.js L62–87 @ 77b39eb · MIT · original } let recipientTokenAccount; try { recipientTokenAccount = await getOrCreateAssociatedTokenAccount( connection, sender, mintAddress, recipientPublicKey, ); // console.log("recipient Token Account:", recipientTokenAccount.address.toString()); } catch (error) { console.error("Не удалось найти или создать токеновый аккаунт получателя:", error); return res.status(500).send("Не удалось найти или создать токеновый аккаунт получателя"); } let amount = 50000000000; const transferInstruction = createTransferInstruction( senderTokenAccount.address, recipientTokenAccount.address, sender.publicKey, amount, [], TOKEN_PROGRAM_ID );- Line 80
const transferInstruction = createTransferInstruction(: An SPL token transfer through the Token Program.
Go further
- valhalla: explore the full project
- kryptpay-solana-contract: explore the full project
- poe-solana: explore the full project
- peer-pay: explore the full project
- gree: explore the full project
- elyse: explore the full project
- Next lesson: Token-2022 (Token Extensions)