LogoAnchor Docs

Create a Token Account

Learn how to create and initialize token accounts in Solana programs using Anchor. Covers creating Associated Token Accounts (ATAs) and Program Derived Address (PDA) token accounts with code examples.

What is a Token Account?

A token account is an account type in Solana's Token Programs that stores information about an individual's ownership of a specific token (mint). Each token account is associated with a single mint and tracks details like the token balance and owner.

/// Account data.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Account {
    /// The mint associated with this account
    pub mint: Pubkey,
    /// The owner of this account.
    pub owner: Pubkey,
    /// The amount of tokens this account holds.
    pub amount: u64,
    /// If `delegate` is `Some` then `delegated_amount` represents
    /// the amount authorized by the delegate
    pub delegate: COption<Pubkey>,
    /// The account's state
    pub state: AccountState,
    /// If `is_native.is_some`, this is a native token, and the value logs the
    /// rent-exempt reserve. An Account is required to be rent-exempt, so
    /// the value is used by the Processor to ensure that wrapped SOL
    /// accounts do not drop below this threshold.
    pub is_native: COption<u64>,
    /// The amount delegated
    pub delegated_amount: u64,
    /// Optional authority to close the account.
    pub close_authority: COption<Pubkey>,
}

Note that in the source code, a Token account is referred to as an Account type. Both the Token Program and Token Extension Program have the same base implementation for the Token account.

To hold tokens for a specific mint, users must first create a token account. Each token account is associated with:

  1. A specific mint (the token type the token account holds units of)
  2. An owner (the authority who can transfer tokens from the account)

Let's look at an example using USDC on Solana:

  • The USDC mint address is EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
  • Circle (the USDC issuer) has a token account at 3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa
  • This token account can only hold units of the USDC token (mint)
  • Circle is set as the owner at 7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE and can transfer these tokens

You can view the details of this token account on Solana Explorer.

The term "owner" is used in two different contexts:

  1. The token account "owner" - This is an address stored in the token account's as the "owner" field of the Account type defined by the Token Program. The owner can transfer, burn, or delegate tokens from the account. This address is sometimes referred to as the "authority" of the token account to distinguish it from the program owner.

  2. The program "owner" - This refers to the program that owns the account data on Solana. For token accounts, this is always either the Token Program or Token Extension Program, as specified in the "owner" field of the base Solana Account type.

When working with token accounts, "owner" typically refers to the authority that can spend the tokens, not the program that owns the account.

What is an Associated Token Account?

An associated token account (ATA) is simply a token account with an address that is a PDA derived from and created by the Associated Token Program. You can think of an ATA as the default token account for a user to hold units of a specific token (mint).

Only token accounts created through the Associated Token Program are referred to as associated token accounts.

ATAs provide a deterministic way to find a user's token account for any given mint. You can inspect the implementation of the derivation here.

Associated Token Account Address Derivation
pub fn get_associated_token_address_and_bump_seed_internal(
    wallet_address: &Pubkey,
    token_mint_address: &Pubkey,
    program_id: &Pubkey,
    token_program_id: &Pubkey,
) -> (Pubkey, u8) {
    Pubkey::find_program_address(
        &[
            &wallet_address.to_bytes(), // Owner's public key
            &token_program_id.to_bytes(), // Token Program or Token Extension Program
            &token_mint_address.to_bytes(), // Token mint address
        ],
        program_id, // Associated Token Program ID
    )
}

This deterministic derivation ensures that for any combination of wallet address and token mint, there exists exactly one associated token account address. This approach makes it simple to find a user's token account for any given token mint, eliminating the need to track token account addresses separately.

The Associated Token Program acts as a helper program that creates token accounts with deterministic addresses (PDAs). When creating an associated token account, the Associated Token Program makes a CPI (Cross-Program Invocation) to either the Token Program or Token Extension Program. The created account is owned by the token program and has the same Account type structure as defined in the token program. The Associated Token Program itself maintains no state - it simply provides a standardized way to create token accounts at a deterministic address.

Usage

Use the token_interface and associated_token modules from the anchor-spl crate to work with ATAs compatible with either the Token Program and Token Extension Program.

snippet

use anchor_spl::associated_token::AssociatedToken;
use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};
 
// --snip--
 
#[derive(Accounts)]
pub struct CreateTokenAccount<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        init_if_needed,
        payer = signer,
        associated_token::mint = mint,
        associated_token::authority = signer,
        associated_token::token_program = token_program,
    )]

    pub token_account: InterfaceAccount<'info, TokenAccount>,
    pub mint: InterfaceAccount<'info, Mint>,
    pub token_program: Interface<'info, TokenInterface>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub system_program: Program<'info, System>,
}

To create token accounts with PDAs derived from your program, you can use the token::mint, token::authority, and token::token_program constraints along with the seeds and bump constraints.

snippet
use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};
 
// --snip--
 
#[derive(Accounts)]
pub struct CreateTokenAccount<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(



        init_if_needed,
        payer = signer,
        token::mint = mint,
        token::authority = token_account,
        token::token_program = token_program,
        seeds = [b"token"],
        bump
    )]
    pub token_account: InterfaceAccount<'info, TokenAccount>,
    pub mint: InterfaceAccount<'info, Mint>,
    pub token_program: Interface<'info, TokenInterface>,
    pub system_program: Program<'info, System>,
}

Account Types

The InterfaceAccount type is a wrapper that allows the account to work with both the Token Program and Token Extension Program.

The TokenAccount type represents the base Account data structure shared by both token programs. When an account of this type is passed in, Anchor will automatically deserialize the account data into the Account struct, regardless of which token program created it.

Account Type
pub token_account: InterfaceAccount<'info, TokenAccount>,

Account Constraints

Anchor provides two sets of constraints for working with token accounts:

  • Use associated_token constraints when working with Associated Token Accounts (ATAs)
  • Use token constraints when working with token accounts that are not specifically ATAs, such as custom PDAs or token accounts with addresses that are public keys from a keypair

The appropriate constraint to use depends on your specific use case. ATAs are recommended for user wallets, while custom token accounts are useful for program controlled accounts.

associated_token constraints

The following account constraints are used in combination to create and initialize a new associated token account:

ConstraintDescription
initCreates a new account by making a cross program invocation (CPI) to the System Program. This allocates the required space for the token account and transfers ownership to the appropriate token program.
init_if_neededSimilar to init, but only creates the account if it doesn't already exist. Requires enabling the init-if-needed feature.
payerSpecifies which account will pay the rent (SOL deposit) required to create the new account.
associated_token::mintSpecifies the mint account that this token account will be associated with.
associated_token::authoritySets the authority (owner) of the token account who has permission to transfer or burn tokens.
associated_token::token_programSpecifies which token program (Token Program or Token Extension Program) to use when creating the token account.
Create Associated Token Account
#[account(
    init,
    payer = <payer>,
    associated_token::mint = <mint>,
    associated_token::authority = <authority>,
    associated_token::token_program = <token_program>
)]
pub token_account: InterfaceAccount<'info, TokenAccount>,

token constraints

The following account constraints are used in combination to create and initialize a new token account:

ConstraintDescription
initCreates a new account by making a cross program invocation (CPI) to the System Program. This allocates the required space for the token account and transfers ownership to the appropriate token program.
init_if_neededSimilar to init, but only creates the account if it doesn't already exist. Requires enabling the init-if-needed feature.
payerSpecifies which account will pay the rent (SOL deposit) required to create the new account.
token::mintSpecifies the mint account that this token account will be associated with.
token::authoritySets the authority (owner) of the token account who has permission to transfer or burn tokens.
token::token_programSpecifies which token program (Token Program or Token Extension Program) to use when creating the token account.
Create Token Account with Keypair Public Key as Address
#[account(
    init,
    payer = <payer>,
    token::mint = <mint>,
    token::authority = <authority>,
    token::token_program = <token_program>
)]
pub token_account: InterfaceAccount<'info, TokenAccount>,
Create Token Account with PDA as Address
#[account(
    init,
    payer = <payer>,
    token::mint = <mint>,
    token::authority = <authority>,
    token::token_program = <token_program>,
    seeds = [<seeds>],
    bump
)]
pub token_account: InterfaceAccount<'info, TokenAccount>,

Note that you can use the same PDA as both the token::authority and the token account address. Using a PDA as the token::authority enables your program to "sign" CPI instructions to transfer tokens from the token account. This pattern allows for a single deterministic address for both purposes.

To use the init_if_needed constraint, enable the init-if-needed feature in Cargo.toml and replace the init constraint with init_if_needed.

Cargo.toml
[dependencies]
anchor-lang = { version = "0.30.1", features = ["init-if-needed"] }

Examples

The following examples demonstrate how to create a token account in an Anchor program using two different approaches:

  1. Using an Associated Token Account (ATA) - This is the standard approach to create a token account for a specific user to hold units of a specific token (mint).

  2. Using a Program Derived Address (PDA) - This approach creates a token account where the address is a custom PDA. This allows for deterministic token account addresses specific to your program. You can also set the authority (owner) as a PDA to enable your program to transfer tokens from the token account.

Both approaches are can be done entirely using account constraints.

Create Associated Token Account

Create an associated token account for a user.

lib.rs
use anchor_lang::prelude::*;
use anchor_spl::associated_token::AssociatedToken;
use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};
 
declare_id!("3pX5NKLru1UBDVckynWQxsgnJeUN3N1viy36Gk9TSn8d");
 
#[program]
pub mod token_example {
    use super::*;
 
    pub fn create_mint(ctx: Context<CreateMint>) -> Result<()> {
        msg!("Created Mint Account: {:?}", ctx.accounts.mint.key());
        Ok(())
    }
 
    pub fn create_token_account(ctx: Context<CreateTokenAccount>) -> Result<()> {
        msg!(
            "Created Token Account: {:?}",
            ctx.accounts.token_account.key()
        );
        Ok(())
    }
}
 
#[derive(Accounts)]
pub struct CreateMint<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        init,
        payer = signer,
        mint::decimals = 6,
        mint::authority = mint.key(),
        mint::freeze_authority = mint.key(),
        seeds = [b"mint"],
        bump
    )]
    pub mint: InterfaceAccount<'info, Mint>,
    pub token_program: Interface<'info, TokenInterface>,
    pub system_program: Program<'info, System>,
}
 
#[derive(Accounts)]
pub struct CreateTokenAccount<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(

        init_if_needed,
        payer = signer,
        associated_token::mint = mint,
        associated_token::authority = signer,
        associated_token::token_program = token_program,
    )]
    pub token_account: InterfaceAccount<'info, TokenAccount>,
    pub mint: InterfaceAccount<'info, Mint>,
    pub token_program: Interface<'info, TokenInterface>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub system_program: Program<'info, System>,
}

Create Token Account using PDA

Create a token account using a Program Derived Address (PDA) as the address of the token account.

lib.rs
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};
 
declare_id!("3pX5NKLru1UBDVckynWQxsgnJeUN3N1viy36Gk9TSn8d");
 
#[program]
pub mod token_example {
    use super::*;
 
    pub fn create_mint(ctx: Context<CreateMint>) -> Result<()> {
        msg!("Created Mint Account: {:?}", ctx.accounts.mint.key());
        Ok(())
    }
 
    pub fn create_token_account(ctx: Context<CreateTokenAccount>) -> Result<()> {
        msg!(
            "Created Token Account: {:?}",
            ctx.accounts.token_account.key()
        );
        Ok(())
    }
}
 
#[derive(Accounts)]
pub struct CreateMint<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        init,
        payer = signer,
        mint::decimals = 6,
        mint::authority = mint.key(),
        mint::freeze_authority = mint.key(),
        seeds = [b"mint"],
        bump
    )]
    pub mint: InterfaceAccount<'info, Mint>,
    pub token_program: Interface<'info, TokenInterface>,
    pub system_program: Program<'info, System>,
}
 
#[derive(Accounts)]
pub struct CreateTokenAccount<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(

        init_if_needed,
        payer = signer,
        token::mint = mint,
        token::authority = token_account,
        token::token_program = token_program,
        seeds = [b"token"],
        bump
    )]
    pub token_account: InterfaceAccount<'info, TokenAccount>,
    pub mint: InterfaceAccount<'info, Mint>,
    pub token_program: Interface<'info, TokenInterface>,
    pub system_program: Program<'info, System>,
}

On this page

Edit on GitHub