# SPL Token Program - Validation Patterns Validation patterns for SPL Token accounts including ownership verification, mint validation, ATA address derivation checks, and balance verification. Covers both Anchor constraint-based and Native Rust manual validation approaches. **For related topics, see:** - **[tokens-overview.md](tokens-overview.md)** - Token fundamentals and account structures - **[tokens-operations.md](tokens-operations.md)** - Create, mint, transfer, burn, close operations - **[tokens-2022.md](tokens-2022.md)** - Token Extensions Program features - **[tokens-patterns.md](tokens-patterns.md)** - Common patterns and security ## Table of Contents 1. [Validate Token Account Ownership and Mint](#validate-token-account-ownership-and-mint) 2. [Validate ATA Address](#validate-ata-address) 3. [Check Token Balance](#check-token-balance) --- ## Validate Token Account Ownership and Mint ### Using Anchor ```rust use anchor_spl::token_interface::{TokenAccount, Mint}; #[derive(Accounts)] pub struct ValidateTokenAccount<'info> { #[account( constraint = token_account.owner == owner.key() @ ErrorCode::InvalidOwner, constraint = token_account.mint == mint.key() @ ErrorCode::InvalidMint, )] pub token_account: InterfaceAccount<'info, TokenAccount>, pub mint: InterfaceAccount<'info, Mint>, /// CHECK: Any account pub owner: UncheckedAccount<'info>, } pub fn validate_token_account(ctx: Context) -> Result<()> { // Validation is automatic via constraints // Additional checks if needed require!( ctx.accounts.token_account.amount >= 100, ErrorCode::InsufficientBalance ); Ok(()) } ``` ### Using Native Rust ```rust use spl_token::state::Account as TokenAccount; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, }; pub fn validate_token_account( token_account_info: &AccountInfo, expected_owner: &Pubkey, expected_mint: &Pubkey, ) -> ProgramResult { // 1. Verify owned by Token Program if token_account_info.owner != &spl_token::ID { msg!("Account not owned by Token Program"); return Err(ProgramError::IllegalOwner); } // 2. Deserialize token account let token_account = TokenAccount::unpack(&token_account_info.data.borrow())?; // 3. Verify owner if token_account.owner != *expected_owner { msg!("Token account owner mismatch"); return Err(ProgramError::IllegalOwner); } // 4. Verify mint if token_account.mint != *expected_mint { msg!("Token account mint mismatch"); return Err(ProgramError::InvalidAccountData); } // 5. Verify not frozen if token_account.state != spl_token::state::AccountState::Initialized { msg!("Token account is frozen or uninitialized"); return Err(ProgramError::InvalidAccountData); } Ok(()) } ``` --- ## Validate ATA Address ### Using Anchor ```rust use anchor_spl::associated_token::AssociatedToken; use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; #[derive(Accounts)] pub struct ValidateATA<'info> { #[account( associated_token::mint = mint, associated_token::authority = owner, associated_token::token_program = token_program, )] pub ata: InterfaceAccount<'info, TokenAccount>, pub mint: InterfaceAccount<'info, Mint>, /// CHECK: Any account pub owner: UncheckedAccount<'info>, pub token_program: Interface<'info, TokenInterface>, } pub fn validate_ata(ctx: Context) -> Result<()> { // ATA address is automatically validated by Anchor constraints Ok(()) } ``` ### Using Native Rust ```rust use spl_associated_token_account::get_associated_token_address; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, pubkey::Pubkey, }; pub fn validate_ata( ata_info: &AccountInfo, wallet: &Pubkey, mint: &Pubkey, ) -> ProgramResult { // Derive expected ATA address let expected_ata = get_associated_token_address(wallet, mint); // Validate match if expected_ata != *ata_info.key { msg!("Invalid ATA address"); return Err(ProgramError::InvalidAccountData); } Ok(()) } ``` --- ## Check Token Balance ### Using Anchor ```rust use anchor_spl::token_interface::TokenAccount; pub fn check_balance( ctx: Context, minimum_amount: u64 ) -> Result<()> { let token_account = &ctx.accounts.token_account; require!( token_account.amount >= minimum_amount, ErrorCode::InsufficientBalance ); Ok(()) } ``` ### Using Native Rust ```rust use spl_token::state::Account as TokenAccount; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, program_pack::Pack, }; pub fn check_token_balance( token_account_info: &AccountInfo, minimum_amount: u64, ) -> ProgramResult { let token_account = TokenAccount::unpack(&token_account_info.data.borrow())?; if token_account.amount < minimum_amount { msg!("Insufficient token balance: {} < {}", token_account.amount, minimum_amount); return Err(ProgramError::InsufficientFunds); } Ok(()) } ``` --- ## Next Steps - **Token-2022**: See [tokens-2022.md](tokens-2022.md) for Token Extensions Program features - **Patterns & Security**: See [tokens-patterns.md](tokens-patterns.md) for common patterns and comprehensive security best practices