Anchor v2 alpha is here! Up to 95% smaller binaries, 3.0 to 50.4× fewer CU
Anchor Docs
A lone boy hauls an anchor on the shore, evoking the weight of maritime destiny and Homer's mastery of atmosphere and technique.
Boy with Anchor, Winslow Homer, 1873
Overview

Tokens and CPI

Learn how to interact with Solana's Token Programs from an Anchor program.

What are token programs?#

Two token programs developed by Anza (previously Solana Labs) issue and manage fungible and non-fungible tokens on Solana. The original Token Program ships immutable and is the most widely used; it covers the basics like minting and transferring. The newer Token Extension Program (Token 2022) is a superset that adds optional behaviors through “extensions” and is the recommended choice for new tokens.

Invoking token programs in an Anchor program#

The anchor-spl crate simplifies interacting with Solana’s Token Programs from an Anchor program. It ships instructions and account types for both the original Token Program and the newer Token Extension Program (Token 2022).

Add the anchor-spl crate as a dependency and add "anchor-spl/idl-build" to the idl-build feature list in Cargo.toml. See the quickstart page for a walkthrough of creating an Anchor program locally:

Terminal window
cargo add anchor-spl
Cargo.toml
[features]
idl-build = [
"anchor-lang/idl-build",
"anchor-spl/idl-build",
]
[dependencies]
anchor-lang = "1.0.1"
anchor-spl = "1.0.1"

Core modules#

The most commonly used modules in anchor-spl:

ModuleDescription
tokenToken Program (legacy) instructions and account types
token_2022Token 2022 base instructions (matching the Token Program functionality)
token_2022_extensionsToken 2022 extensions instructions
token_interfaceAccount types that work with both the Token Program and Token 2022 Program
associated_tokenAssociated token account instruction

The pages under this section walk through the most common token operations in Anchor programs.

SPL token basics

Learn how to integrate SPL tokens into your Solana programs using the Anchor framework.

This section covers the basics of interacting with SPL tokens in Anchor programs, focusing on the most commonly used instructions. All examples work identically with both the original Token Program and the Token Extension Program (Token 2022), which share the same base implementation.

Create a token mint

Create and initialize token mint accounts in Anchor programs using account constraints, with examples for both generated keypairs and program derived addresses.

A mint account is the account type in Solana’s Token Program and Token Extensions Program that represents a token on the network and stores its global metadata. The address of the mint account acts as the token’s unique identifier. For example, the USDC stablecoin’s mint address is EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v, viewable on Solana Explorer.

Mint account data#

Both the Token Program and the Token Extensions Program share the same base layout for mint data:

/// Mint data.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Mint {
/// Optional authority used to mint new tokens. The mint authority may only
/// be provided during mint creation. If no mint authority is present
/// then the mint has a fixed supply and no further tokens may be
/// minted.
pub mint_authority: COption<Pubkey>,
/// Total supply of tokens.
pub supply: u64,
/// Number of base 10 digits to the right of the decimal place.
pub decimals: u8,
/// Is `true` if this structure has been initialized
pub is_initialized: bool,
/// Optional authority to freeze token accounts.
pub freeze_authority: COption<Pubkey>,
}

Usage#

The token_interface module from the anchor-spl crate provides types that work with both the Token Program and the Token Extensions Program. Declaring a mint with InterfaceAccount<'info, Mint> and Interface<'info, TokenInterface> lets the same program accept either token program at runtime:

lib.rs
15 collapsed lines
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{Mint, 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(())
}
}
#[derive(Accounts)]
pub struct CreateMint<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(
init,
payer = signer,
mint::decimals = 6,
mint::authority = signer.key(),
mint::freeze_authority = signer.key(),
)]
pub mint: InterfaceAccount<'info, Mint>,
pub token_program: Interface<'info, TokenInterface>,
pub system_program: Program<'info, System>,
}

Account types#

The InterfaceAccount wrapper accepts accounts owned by either token program. Anchor deserializes the account data into the Mint struct regardless of which program created it.

Account constraints#

The following constraints, used in combination, create and initialize a new mint account:

ConstraintDescription
initCreates the account via a CPI to the System Program, allocating space and transferring ownership to the appropriate token program.
payerThe account that pays the rent for the new account.
mint::decimalsThe number of decimal places for the token. Setting 6 means one token equals 1,000,000 base units.
mint::authorityThe account permitted to mint new tokens. Required.
mint::freeze_authorityThe account permitted to freeze token accounts. Optional. A frozen token account cannot be the source or destination of transfer, burn, or other token program instructions.
lib.rs
19 collapsed lines
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{Mint, 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(())
}
}
#[derive(Accounts)]
pub struct CreateMint<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(
init,
payer = signer,
mint::decimals = 6,
mint::authority = signer.key(),
mint::freeze_authority = signer.key(),
)]
4 collapsed lines
pub mint: InterfaceAccount<'info, Mint>,
pub token_program: Interface<'info, TokenInterface>,
pub system_program: Program<'info, System>,
}

Adding the seeds and bump constraints creates the mint at a Program Derived Address (PDA) instead. The mint address can then be derived from the same seeds at any time:

lib.rs
19 collapsed lines
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{Mint, 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(())
}
}
#[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
)]
4 collapsed lines
pub mint: InterfaceAccount<'info, Mint>,
pub token_program: Interface<'info, TokenInterface>,
pub system_program: Program<'info, System>,
}
Note (Reusing the PDA as authority)

The same PDA can serve as both the mint address and the mint::authority. When the program is the authority, it can sign CPIs to mint new tokens, all from a single deterministic address.

Examples#

Two approaches create a mint account, both running entirely through account constraints. The first generates a keypair client-side and uses its public key as the mint address. The second derives the mint address from a Program Derived Address.

Create mint using keypair#

A new keypair is generated client-side and passed in as the mint address. Use this when a deterministic address is not required.

lib.rs
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{Mint, 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(())
}
}
#[derive(Accounts)]
pub struct CreateMint<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(
init,
payer = signer,
mint::decimals = 6,
mint::authority = signer.key(),
mint::freeze_authority = signer.key(),
)]
pub mint: InterfaceAccount<'info, Mint>,
pub token_program: Interface<'info, TokenInterface>,
pub system_program: Program<'info, System>,
}
test.ts
import * as anchor from '@anchor-lang/core'
import { Program } from '@anchor-lang/core'
import { TokenExample } from '../target/types/token_example'
import { TOKEN_2022_PROGRAM_ID, getMint } from '@solana/spl-token'
describe('token-example', () => {
anchor.setProvider(anchor.AnchorProvider.env())
const program = anchor.workspace.TokenExample as Program<TokenExample>
const mint = anchor.web3.Keypair.generate()
it('Is initialized!', async () => {
const tx = await program.methods
.createMint()
.accounts({
mint: mint.publicKey,
tokenProgram: TOKEN_2022_PROGRAM_ID,
})
.signers([mint])
.rpc({ commitment: 'confirmed' })
console.log('Your transaction signature', tx)
const mintAccount = await getMint(
program.provider.connection,
mint.publicKey,
'confirmed',
TOKEN_2022_PROGRAM_ID,
)
console.log('Mint Account', mintAccount)
})
})

Create mint using PDA#

A PDA derived from program-defined seeds serves as the mint address. The same seeds reproduce the address from any client without storing it externally.

lib.rs
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{Mint, 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(())
}
}
#[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>,
}
test.ts
import * as anchor from '@anchor-lang/core'
import { Program } from '@anchor-lang/core'
import { TokenExample } from '../target/types/token_example'
import { TOKEN_2022_PROGRAM_ID, getMint } from '@solana/spl-token'
describe('token-example', () => {
anchor.setProvider(anchor.AnchorProvider.env())
const program = anchor.workspace.TokenExample as Program<TokenExample>
const [mint, bump] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from('mint')],
program.programId,
)
it('Is initialized!', async () => {
const tx = await program.methods
.createMint()
.accounts({
tokenProgram: TOKEN_2022_PROGRAM_ID,
})
.rpc({ commitment: 'confirmed' })
console.log('Your transaction signature', tx)
const mintAccount = await getMint(
program.provider.connection,
mint,
'confirmed',
TOKEN_2022_PROGRAM_ID,
)
console.log('Mint Account', mintAccount)
})
})

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. The Token Program defines the layout as the following struct:

/// 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

In the source code, a token account is referred to as an Account type. Both the Token Program and Token Extension Program share 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 a specific mint (the token type the account holds units of) and an owner (the authority who can transfer tokens from the account).

For example, the USDC stablecoin on Solana has the mint address EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v. Circle (the USDC issuer) holds a token account at 3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa that can only hold units of the USDC token, with Circle’s wallet 7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE set as the owner authorized to transfer those tokens. You can view this token account on Solana Explorer.

Notation (Two senses of "owner")

The term “owner” is used in two different contexts.

  • The token account “owner” is an address stored in the owner field of the Account type defined by the Token Program. This address can transfer, burn, or delegate tokens from the account, and is sometimes called the “authority” of the token account to distinguish it from the program owner.
  • The program “owner” refers to the program that owns the account data on Solana, which for token accounts 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 a token account whose address is a PDA derived from and created by the Associated Token Program. An ATA serves as the default token account for a user to hold units of a specific token (mint).

Definition (Associated Token Account)

Only token accounts created through the Associated Token Program are referred to as associated token accounts. The address of an ATA is a PDA derived deterministically from the wallet, mint, and token program, which is what makes “the user’s USDC account” a single canonical address rather than one of many possible token accounts.

ATAs provide a deterministic way to find a user’s token account for any given mint. The derivation hashes the wallet, token program, and mint addresses against the Associated Token Program ID:

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
)
}

For any combination of wallet address and token mint there exists exactly one associated token account address, eliminating the need to track token account addresses separately.

Note

The Associated Token Program is a helper program that creates token accounts at deterministic PDA addresses. When creating an associated token account, it makes a CPI to either the Token Program or Token Extension Program. The resulting account is owned by the token program and uses the same Account type defined there. The Associated Token Program itself maintains no state.

Usage#

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

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 at PDAs derived from your program, use the token::mint, token::authority, and token::token_program constraints together with seeds and bump:

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. Anchor automatically deserializes the account data into the Account struct regardless of which token program created it. A token account field is declared as pub token_account: InterfaceAccount<'info, TokenAccount>.

Account constraints#

Anchor provides two sets of constraints for working with token accounts. Use the associated_token constraints for associated token accounts (ATAs), and the token constraints for token accounts that are not specifically ATAs (such as custom PDAs or token accounts whose addresses are public keys from a keypair). 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.

Combined as a single account attribute:

#[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.

When the address is the public key of a generated keypair, the constraints look like:

#[account(
init,
payer = <payer>,
token::mint = <mint>,
token::authority = <authority>,
token::token_program = <token_program>
)]
pub token_account: InterfaceAccount<'info, TokenAccount>,

When the address is a PDA, add seeds and bump:

#[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

The same PDA can serve as both the token::authority and the token account address. Using a PDA as the token::authority enables the program to “sign” CPI instructions to transfer tokens from the account, giving 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 init with init_if_needed:

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

Examples#

Two approaches create a token account, both running entirely through account constraints. The first creates an Associated Token Account (ATA), the standard approach for a user to hold units of a specific token. The second uses a Program Derived Address (PDA) as the token account address, giving the program deterministic token account addresses. Setting the authority to that same PDA also lets the program sign CPIs to transfer tokens from the account.

Create associated token account#

Creates 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>,
}
test.ts
import * as anchor from '@anchor-lang/core'
import { Program } from '@anchor-lang/core'
import { TokenExample } from '../target/types/token_example'
import {
TOKEN_2022_PROGRAM_ID,
getAssociatedTokenAddress,
getMint,
getAccount,
} from '@solana/spl-token'
describe('token-example', () => {
anchor.setProvider(anchor.AnchorProvider.env())
const program = anchor.workspace.TokenExample as Program<TokenExample>
const [mint, bump] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from('mint')],
program.programId,
)
it('Is initialized!', async () => {
const tx = await program.methods
.createMint()
.accounts({
tokenProgram: TOKEN_2022_PROGRAM_ID,
})
.rpc({ commitment: 'confirmed' })
console.log('Your transaction signature', tx)
const mintAccount = await getMint(
program.provider.connection,
mint,
'confirmed',
TOKEN_2022_PROGRAM_ID,
)
console.log('Mint Account', mintAccount)
})
it('Create token account', async () => {
const tx = await program.methods
.createTokenAccount()
.accounts({
mint: mint,
tokenProgram: TOKEN_2022_PROGRAM_ID,
})
.rpc({ commitment: 'confirmed' })
console.log('Your transaction signature', tx)
const associatedTokenAccount = await getAssociatedTokenAddress(
mint,
program.provider.publicKey,
false,
TOKEN_2022_PROGRAM_ID,
)
const tokenAccount = await getAccount(
program.provider.connection,
associatedTokenAccount,
'confirmed',
TOKEN_2022_PROGRAM_ID,
)
console.log('Token Account', tokenAccount)
})
})

Create token account using PDA#

Creates a token account using a Program Derived Address (PDA) as the token account’s address:

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>,
}
test.ts
import * as anchor from '@anchor-lang/core'
import { Program } from '@anchor-lang/core'
import { TokenExample } from '../target/types/token_example'
import {
TOKEN_2022_PROGRAM_ID,
getAssociatedTokenAddress,
getMint,
getAccount,
} from '@solana/spl-token'
describe('token-example', () => {
anchor.setProvider(anchor.AnchorProvider.env())
const program = anchor.workspace.TokenExample as Program<TokenExample>
const [mint, mintBump] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from('mint')],
program.programId,
)
const [token, tokenBump] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from('token')],
program.programId,
)
it('Is initialized!', async () => {
const tx = await program.methods
.createMint()
.accounts({
tokenProgram: TOKEN_2022_PROGRAM_ID,
})
.rpc({ commitment: 'confirmed' })
console.log('Your transaction signature', tx)
const mintAccount = await getMint(
program.provider.connection,
mint,
'confirmed',
TOKEN_2022_PROGRAM_ID,
)
console.log('Mint Account', mintAccount)
})
it('Create token account', async () => {
const tx = await program.methods
.createTokenAccount()
.accounts({
mint: mint,
tokenProgram: TOKEN_2022_PROGRAM_ID,
})
.rpc({ commitment: 'confirmed' })
console.log('Your transaction signature', tx)
const tokenAccount = await getAccount(
program.provider.connection,
token,
'confirmed',
TOKEN_2022_PROGRAM_ID,
)
console.log('Token Account', tokenAccount)
})
})

Mint tokens

Learn how to mint tokens in Solana programs using Anchor. Covers creating new tokens via cross program invocations (CPI) to the Token Program with code examples.

How to mint tokens#

Minting tokens creates new units of a token by invoking the mint_to() instruction on a token program. Only the address specified as the mint authority on the mint account can mint new tokens, and the instruction also requires a token account for that mint as the destination.

Note

The Token Program and Token Extension Program share similar implementations to achieve the same functionality.

Examples#

To mint tokens through an Anchor program, make a cross program invocation (CPI) to the mint_to instruction on either the Token Program or Token Extension Program. The program acts as an intermediary, passing along the required accounts, instruction data, and signatures to the token program.

Mint tokens via CPI#

Use the token_interface::mint_to() function to make a CPI to either the Token Program or Token Extension Program. The function takes a MintTo struct specifying the mint, to (destination token account), and authority (mint authority) accounts, along with the amount of tokens to mint in base units adjusted by decimals (e.g. with 2 decimals, an amount of 100 equals 1 token).

The mint authority passed to the instruction must match the mint_authority stored on the mint account, and must sign the transaction:

lib.rs
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{self, Mint, MintTo, TokenAccount, TokenInterface};
declare_id!("3pX5NKLru1UBDVckynWQxsgnJeUN3N1viy36Gk9TSn8d");
#[program]
pub mod token_example {
use super::*;
pub fn mint_tokens(ctx: Context<MintTokens>, amount: u64) -> Result<()> {
let cpi_accounts = MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.signer.to_account_info(),
};
let cpi_program_id = ctx.accounts.token_program.key();
let cpi_context = CpiContext::new(cpi_program_id, cpi_accounts);
token_interface::mint_to(cpi_context, amount)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct MintTokens<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(mut)]
pub mint: InterfaceAccount<'info, Mint>,
#[account(mut)]
pub token_account: InterfaceAccount<'info, TokenAccount>,
pub token_program: Interface<'info, TokenInterface>,
}

At minimum, the following accounts are required:

#[derive(Accounts)]
pub struct MintTokens<'info> {
// The mint authority
#[account(mut)]
pub signer: Signer<'info>,
// The mint account
#[account(mut)]
pub mint: InterfaceAccount<'info, Mint>,
// The destination token account
#[account(mut)]
pub token_account: InterfaceAccount<'info, TokenAccount>,
// The token program
pub token_program: Interface<'info, TokenInterface>,
}

Within the instruction logic, populate the MintTo struct with the required accounts and pass it to token_interface::mint_to() through a CpiContext:

pub fn mint_tokens(ctx: Context<MintTokens>, amount: u64) -> Result<()> {
// Create the MintTo struct with the accounts required for the CPI
let cpi_accounts = MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.signer.to_account_info(),
};
// The program being invoked in the CPI
let cpi_program_id = ctx.accounts.token_program.key();
// Combine the accounts and program into a "CpiContext"
let cpi_context = CpiContext::new(cpi_program_id, cpi_accounts);
// Make CPI to mint_to instruction on the token program
token_interface::mint_to(cpi_context, amount)?;
Ok(())
}

Mint tokens with PDA mint authority via CPI#

A mint account can use a Program Derived Address (PDA) as its mint authority, allowing the program itself to mint tokens by “signing” the CPI with the PDA’s seeds. This pattern fits cases where the program, rather than an external wallet, controls minting.

lib.rs
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token_interface::{self, Mint, MintTo, 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 mint_tokens(ctx: Context<MintTokens>, amount: u64) -> Result<()> {
let signer_seeds: &[&[&[u8]]] = &[&[b"mint", &[ctx.bumps.mint]]];
let cpi_accounts = MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.mint.to_account_info(),
};
let cpi_program_id = ctx.accounts.token_program.key();
let cpi_context = CpiContext::new(cpi_program_id, cpi_accounts).with_signer(signer_seeds);
token_interface::mint_to(cpi_context, amount)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct CreateMint<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(
init,
payer = signer,
mint::decimals = 6,
mint::authority = mint,
mint::freeze_authority = mint,
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 MintTokens<'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>,
#[account(
mut,
seeds = [b"mint"],
bump
)]
pub mint: InterfaceAccount<'info, Mint>,
pub token_program: Interface<'info, TokenInterface>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub system_program: Program<'info, System>,
}
test.ts
import * as anchor from '@anchor-lang/core'
import { Program } from '@anchor-lang/core'
import { TokenExample } from '../target/types/token_example'
import {
TOKEN_2022_PROGRAM_ID,
getAssociatedTokenAddress,
getMint,
getAccount,
} from '@solana/spl-token'
describe('token-example', () => {
anchor.setProvider(anchor.AnchorProvider.env())
const program = anchor.workspace.TokenExample as Program<TokenExample>
const [mint, mintBump] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from('mint')],
program.programId,
)
const [token, tokenBump] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from('token')],
program.programId,
)
it('Is initialized!', async () => {
const tx = await program.methods
.createMint()
.accounts({
tokenProgram: TOKEN_2022_PROGRAM_ID,
})
.rpc({ commitment: 'confirmed' })
console.log('Your transaction signature', tx)
const mintAccount = await getMint(
program.provider.connection,
mint,
'confirmed',
TOKEN_2022_PROGRAM_ID,
)
console.log('Mint Account', mintAccount)
})
it('Mint Tokens', async () => {
const tx = await program.methods
.mintTokens(new anchor.BN(100))
.accounts({
tokenProgram: TOKEN_2022_PROGRAM_ID,
})
.rpc({ commitment: 'confirmed' })
console.log('Your transaction signature', tx)
const associatedTokenAccount = await getAssociatedTokenAddress(
mint,
program.provider.publicKey,
false,
TOKEN_2022_PROGRAM_ID,
)
const tokenAccount = await getAccount(
program.provider.connection,
associatedTokenAccount,
'confirmed',
TOKEN_2022_PROGRAM_ID,
)
console.log('Token Account', tokenAccount)
})
})

The mint authority is set to a PDA derived from the seed b"mint", so the program itself controls minting through this PDA:

#[derive(Accounts)]
pub struct CreateMint<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(
init,
payer = signer,
mint::decimals = 6,
mint::authority = mint,
mint::freeze_authority = mint,
seeds = [b"mint"],
bump
)]
pub mint: InterfaceAccount<'info, Mint>,
pub token_program: Interface<'info, TokenInterface>,
pub system_program: Program<'info, System>,
}

The program signs the CPI by reconstructing the PDA’s seeds (with the bump from ctx.bumps) and passing them to with_signer() on the CpiContext:

pub fn mint_tokens(ctx: Context<MintTokens>, amount: u64) -> Result<()> {
let signer_seeds: &[&[&[u8]]] = &[&[b"mint", &[ctx.bumps.mint]]];
let cpi_accounts = MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.mint.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.key();
let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(signer_seeds);
token_interface::mint_to(cpi_context, amount)?;
Ok(())
}
Note

The same PDA serves as both the mint address and the mint authority.

Transfer tokens

Learn how to transfer tokens between token accounts through cross program invocations (CPIs) in Anchor.

Transferring tokens moves units between two token accounts that share a mint. The Token Program and Token Extension Program expose this through the transfer_checked() instruction. Only the address registered as the source token account’s authority can transfer tokens out of it.

Note

The Token Program and Token Extension Program share the same implementation for transfer_checked().

Transfer tokens via CPI#

An Anchor program transfers tokens by issuing a cross program invocation to transfer_checked() on either token program. The program acts as an intermediary, forwarding the required accounts, instruction data, and signatures.

The token_interface::transfer_checked() helper takes a TransferChecked struct and an amount in base units adjusted by mint::decimals (an amount of 100 with 2 decimals represents one whole token). The struct holds four accounts:

  • mint is the mint specifying the token type
  • from is the source token account
  • to is the destination token account
  • authority is the owner of from
lib.rs
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{self, Mint, TokenAccount, TokenInterface, TransferChecked};
declare_id!("3pX5NKLru1UBDVckynWQxsgnJeUN3N1viy36Gk9TSn8d");
#[program]
pub mod token_example {
use super::*;
pub fn transfer_tokens(ctx: Context<TransferTokens>, amount: u64) -> Result<()> {
let decimals = ctx.accounts.mint.decimals;
let cpi_accounts = TransferChecked {
mint: ctx.accounts.mint.to_account_info(),
from: ctx.accounts.sender_token_account.to_account_info(),
to: ctx.accounts.recipient_token_account.to_account_info(),
authority: ctx.accounts.signer.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_context = CpiContext::new(cpi_program, cpi_accounts);
token_interface::transfer_checked(cpi_context, amount, decimals)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct TransferTokens<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(mut)]
pub mint: InterfaceAccount<'info, Mint>,
#[account(mut)]
pub sender_token_account: InterfaceAccount<'info, TokenAccount>,
#[account(mut)]
pub recipient_token_account: InterfaceAccount<'info, TokenAccount>,
pub token_program: Interface<'info, TokenInterface>,
}

The TransferTokens accounts struct declares the source authority, the mint, the source and destination token accounts, and the token program:

lib.rs
25 collapsed lines
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{self, Mint, TokenAccount, TokenInterface, TransferChecked};
declare_id!("3pX5NKLru1UBDVckynWQxsgnJeUN3N1viy36Gk9TSn8d");
#[program]
pub mod token_example {
use super::*;
pub fn transfer_tokens(ctx: Context<TransferTokens>, amount: u64) -> Result<()> {
let decimals = ctx.accounts.mint.decimals;
let cpi_accounts = TransferChecked {
mint: ctx.accounts.mint.to_account_info(),
from: ctx.accounts.sender_token_account.to_account_info(),
to: ctx.accounts.recipient_token_account.to_account_info(),
authority: ctx.accounts.signer.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_context = CpiContext::new(cpi_program, cpi_accounts);
token_interface::transfer_checked(cpi_context, amount, decimals)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct TransferTokens<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(mut)]
pub mint: InterfaceAccount<'info, Mint>,
#[account(mut)]
pub sender_token_account: InterfaceAccount<'info, TokenAccount>,
#[account(mut)]
pub recipient_token_account: InterfaceAccount<'info, TokenAccount>,
pub token_program: Interface<'info, TokenInterface>,
}

The instruction body builds a CpiContext from the TransferChecked accounts and the token program, then calls transfer_checked():

lib.rs
9 collapsed lines
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{self, Mint, TokenAccount, TokenInterface, TransferChecked};
declare_id!("3pX5NKLru1UBDVckynWQxsgnJeUN3N1viy36Gk9TSn8d");
#[program]
pub mod token_example {
use super::*;
pub fn transfer_tokens(ctx: Context<TransferTokens>, amount: u64) -> Result<()> {
let decimals = ctx.accounts.mint.decimals;
let cpi_accounts = TransferChecked {
mint: ctx.accounts.mint.to_account_info(),
from: ctx.accounts.sender_token_account.to_account_info(),
to: ctx.accounts.recipient_token_account.to_account_info(),
authority: ctx.accounts.signer.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_context = CpiContext::new(cpi_program, cpi_accounts);
token_interface::transfer_checked(cpi_context, amount, decimals)?;
Ok(())
}
14 collapsed lines
}
#[derive(Accounts)]
pub struct TransferTokens<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(mut)]
pub mint: InterfaceAccount<'info, Mint>,
#[account(mut)]
pub sender_token_account: InterfaceAccount<'info, TokenAccount>,
#[account(mut)]
pub recipient_token_account: InterfaceAccount<'info, TokenAccount>,
pub token_program: Interface<'info, TokenInterface>,
}

Transfer tokens with a PDA owner via CPI#

A token account can be owned by a Program Derived Address (PDA), letting the program itself authorize transfers by signing the CPI with the PDA’s seeds. This pattern is useful when transfer eligibility depends on conditions enforced by the program.

lib.rs
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token_interface::{self, Mint, MintTo, TokenAccount, TokenInterface, TransferChecked},
};
declare_id!("3pX5NKLru1UBDVckynWQxsgnJeUN3N1viy36Gk9TSn8d");
#[program]
pub mod token_example {
use super::*;
pub fn create_and_mint_tokens(ctx: Context<CreateAndMintTokens>, amount: u64) -> Result<()> {
let signer_seeds: &[&[&[u8]]] = &[&[b"mint", &[ctx.bumps.mint]]];
let cpi_accounts = MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.mint.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(signer_seeds);
token_interface::mint_to(cpi_context, amount)?;
Ok(())
}
pub fn transfer_tokens(ctx: Context<TransferTokens>) -> Result<()> {
let signer_seeds: &[&[&[u8]]] = &[&[b"token", &[ctx.bumps.sender_token_account]]];
let amount = ctx.accounts.sender_token_account.amount;
let decimals = ctx.accounts.mint.decimals;
let cpi_accounts = TransferChecked {
mint: ctx.accounts.mint.to_account_info(),
from: ctx.accounts.sender_token_account.to_account_info(),
to: ctx.accounts.recipient_token_account.to_account_info(),
authority: ctx.accounts.sender_token_account.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(signer_seeds);
token_interface::transfer_checked(cpi_context, amount, decimals)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct CreateAndMintTokens<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(
init,
payer = signer,
mint::decimals = 6,
mint::authority = mint,
mint::freeze_authority = mint,
seeds = [b"mint"],
bump
)]
pub mint: InterfaceAccount<'info, Mint>,
#[account(
init,
payer = signer,
token::mint = mint,
token::authority = token_account,
seeds = [b"token"],
bump
)]
pub token_account: InterfaceAccount<'info, TokenAccount>,
pub token_program: Interface<'info, TokenInterface>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct TransferTokens<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(
mut,
seeds = [b"mint"],
bump
)]
pub mint: InterfaceAccount<'info, Mint>,
#[account(
mut,
token::mint = mint,
token::authority = sender_token_account,
seeds = [b"token"],
bump
)]
pub sender_token_account: InterfaceAccount<'info, TokenAccount>,
#[account(
init_if_needed,
payer = signer,
associated_token::mint = mint,
associated_token::authority = signer,
associated_token::token_program = token_program,
)]
pub recipient_token_account: InterfaceAccount<'info, TokenAccount>,
pub token_program: Interface<'info, TokenInterface>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub system_program: Program<'info, System>,
}
test.ts
import * as anchor from '@anchor-lang/core'
import { Program } from '@anchor-lang/core'
import { TokenExample } from '../target/types/token_example'
import {
TOKEN_2022_PROGRAM_ID,
getAssociatedTokenAddress,
getMint,
getAccount,
} from '@solana/spl-token'
describe('token-example', () => {
anchor.setProvider(anchor.AnchorProvider.env())
const program = anchor.workspace.TokenExample as Program<TokenExample>
const [mint, mintBump] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from('mint')],
program.programId,
)
const [token, tokenBump] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from('token')],
program.programId,
)
it('Is initialized!', async () => {
const tx = await program.methods
.createAndMintTokens(new anchor.BN(100))
.accounts({
tokenProgram: TOKEN_2022_PROGRAM_ID,
})
.rpc({ commitment: 'confirmed' })
console.log('Your transaction signature', tx)
const mintAccount = await getMint(
program.provider.connection,
mint,
'confirmed',
TOKEN_2022_PROGRAM_ID,
)
console.log('Mint Account', mintAccount)
})
it('Transfer Tokens', async () => {
const tx = await program.methods
.transferTokens()
.accounts({
tokenProgram: TOKEN_2022_PROGRAM_ID,
})
.rpc({ commitment: 'confirmed' })
console.log('Your transaction signature', tx)
const associatedTokenAccount = await getAssociatedTokenAddress(
mint,
program.provider.publicKey,
false,
TOKEN_2022_PROGRAM_ID,
)
const recipientTokenAccount = await getAccount(
program.provider.connection,
associatedTokenAccount,
'confirmed',
TOKEN_2022_PROGRAM_ID,
)
const senderTokenAccount = await getAccount(
program.provider.connection,
token,
'confirmed',
TOKEN_2022_PROGRAM_ID,
)
console.log('Recipient Token Account', recipientTokenAccount)
console.log('Sender Token Account', senderTokenAccount)
})
})

The source token account’s token::authority is set to the PDA derived from b"token". The same PDA is used as both the address of the token account and its authority:

lib.rs
45 collapsed lines
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token_interface::{self, Mint, MintTo, TokenAccount, TokenInterface, TransferChecked},
};
declare_id!("3pX5NKLru1UBDVckynWQxsgnJeUN3N1viy36Gk9TSn8d");
#[program]
pub mod token_example {
use super::*;
pub fn create_and_mint_tokens(ctx: Context<CreateAndMintTokens>, amount: u64) -> Result<()> {
let signer_seeds: &[&[&[u8]]] = &[&[b"mint", &[ctx.bumps.mint]]];
let cpi_accounts = MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.mint.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(signer_seeds);
token_interface::mint_to(cpi_context, amount)?;
Ok(())
}
pub fn transfer_tokens(ctx: Context<TransferTokens>) -> Result<()> {
let signer_seeds: &[&[&[u8]]] = &[&[b"token", &[ctx.bumps.sender_token_account]]];
let amount = ctx.accounts.sender_token_account.amount;
let decimals = ctx.accounts.mint.decimals;
let cpi_accounts = TransferChecked {
mint: ctx.accounts.mint.to_account_info(),
from: ctx.accounts.sender_token_account.to_account_info(),
to: ctx.accounts.recipient_token_account.to_account_info(),
authority: ctx.accounts.sender_token_account.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(signer_seeds);
token_interface::transfer_checked(cpi_context, amount, decimals)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct CreateAndMintTokens<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(
init,
payer = signer,
mint::decimals = 6,
mint::authority = mint,
mint::freeze_authority = mint,
seeds = [b"mint"],
bump
)]
pub mint: InterfaceAccount<'info, Mint>,
#[account(
init,
payer = signer,
token::mint = mint,
token::authority = token_account,
seeds = [b"token"],
bump
)]
pub token_account: InterfaceAccount<'info, TokenAccount>,
pub token_program: Interface<'info, TokenInterface>,
pub system_program: Program<'info, System>,
}
31 collapsed lines
#[derive(Accounts)]
pub struct TransferTokens<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(
mut,
seeds = [b"mint"],
bump
)]
pub mint: InterfaceAccount<'info, Mint>,
#[account(
mut,
token::mint = mint,
token::authority = sender_token_account,
seeds = [b"token"],
bump
)]
pub sender_token_account: InterfaceAccount<'info, TokenAccount>,
#[account(
init_if_needed,
payer = signer,
associated_token::mint = mint,
associated_token::authority = signer,
associated_token::token_program = token_program,
)]
pub recipient_token_account: InterfaceAccount<'info, TokenAccount>,
pub token_program: Interface<'info, TokenInterface>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub system_program: Program<'info, System>,
}

To authorize the transfer, the program reconstructs the PDA’s seeds (including the bump from ctx.bumps) and attaches them via with_signer() on the CpiContext:

lib.rs
26 collapsed lines
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token_interface::{self, Mint, MintTo, TokenAccount, TokenInterface, TransferChecked},
};
declare_id!("3pX5NKLru1UBDVckynWQxsgnJeUN3N1viy36Gk9TSn8d");
#[program]
pub mod token_example {
use super::*;
pub fn create_and_mint_tokens(ctx: Context<CreateAndMintTokens>, amount: u64) -> Result<()> {
let signer_seeds: &[&[&[u8]]] = &[&[b"mint", &[ctx.bumps.mint]]];
let cpi_accounts = MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.mint.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(signer_seeds);
token_interface::mint_to(cpi_context, amount)?;
Ok(())
}
pub fn transfer_tokens(ctx: Context<TransferTokens>) -> Result<()> {
let signer_seeds: &[&[&[u8]]] = &[&[b"token", &[ctx.bumps.sender_token_account]]];
let amount = ctx.accounts.sender_token_account.amount;
let decimals = ctx.accounts.mint.decimals;
let cpi_accounts = TransferChecked {
mint: ctx.accounts.mint.to_account_info(),
from: ctx.accounts.sender_token_account.to_account_info(),
to: ctx.accounts.recipient_token_account.to_account_info(),
authority: ctx.accounts.sender_token_account.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(signer_seeds);
token_interface::transfer_checked(cpi_context, amount, decimals)?;
Ok(())
}
59 collapsed lines
}
#[derive(Accounts)]
pub struct CreateAndMintTokens<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(
init,
payer = signer,
mint::decimals = 6,
mint::authority = mint,
mint::freeze_authority = mint,
seeds = [b"mint"],
bump
)]
pub mint: InterfaceAccount<'info, Mint>,
#[account(
init,
payer = signer,
token::mint = mint,
token::authority = token_account,
seeds = [b"token"],
bump
)]
pub token_account: InterfaceAccount<'info, TokenAccount>,
pub token_program: Interface<'info, TokenInterface>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct TransferTokens<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(
mut,
seeds = [b"mint"],
bump
)]
pub mint: InterfaceAccount<'info, Mint>,
#[account(
mut,
token::mint = mint,
token::authority = sender_token_account,
seeds = [b"token"],
bump
)]
pub sender_token_account: InterfaceAccount<'info, TokenAccount>,
#[account(
init_if_needed,
payer = signer,
associated_token::mint = mint,
associated_token::authority = signer,
associated_token::token_program = token_program,
)]
pub recipient_token_account: InterfaceAccount<'info, TokenAccount>,
pub token_program: Interface<'info, TokenInterface>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub system_program: Program<'info, System>,
}
Note

The same PDA serves as both the address of the source token account and its owner.

Token-2022 and extensions

Enable token extensions to add optional features to token mints and accounts using the Token Extensions Program (Token 2022) in an Anchor program.

What are token extensions?#

The Token Extensions Program (Token 2022) provides additional features through instructions referred to as extensions. Extensions are optional functionality added to a token mint or token account. The implementation lives in the program’s extension module.

Each extension adds specific state that is usually initialized during mint or token account creation. Multiple extensions can be enabled simultaneously on the same account, but most extensions cannot be added after an account is initialized, so all desired extensions must be selected during the initial account creation.

The exceptions, which require the account to be initialized before they are added, are:

  • cpi-guard
  • memo-transfer
  • token-group
  • token-member
  • token-metadata
Note

Some extensions are incompatible with each other and cannot be enabled simultaneously on the same token mint or token account. For example, NonTransferable cannot be combined with TransferFeeConfig, since they have conflicting behaviors.

The Token Extensions Program defines an ExtensionType enum that specifies all available extensions. Each variant represents a different extension with unique functionality:

extension/mod.rs
/// Extensions that can be applied to mints or accounts. Mint extensions must
/// only be applied to mint accounts, and account extensions must only be
/// applied to token holding accounts.
#[repr(u16)]
#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive, IntoPrimitive)]
pub enum ExtensionType {
/// Used as padding if the account size would otherwise be 355, same as a
/// multisig
Uninitialized,
/// Includes transfer fee rate info and accompanying authorities to withdraw
/// and set the fee
TransferFeeConfig,
/// Includes withheld transfer fees
TransferFeeAmount,
/// Includes an optional mint close authority
MintCloseAuthority,
/// Auditor configuration for confidential transfers
ConfidentialTransferMint,
/// State for confidential transfers
ConfidentialTransferAccount,
/// Specifies the default Account::state for new Accounts
DefaultAccountState,
/// Indicates that the Account owner authority cannot be changed
ImmutableOwner,
/// Require inbound transfers to have memo
MemoTransfer,
/// Indicates that the tokens from this mint can't be transferred
NonTransferable,
/// Tokens accrue interest over time,
InterestBearingConfig,
/// Locks privileged token operations from happening via CPI
CpiGuard,
/// Includes an optional permanent delegate
PermanentDelegate,
/// Indicates that the tokens in this account belong to a non-transferable
/// mint
NonTransferableAccount,
/// Mint requires a CPI to a program implementing the "transfer hook"
/// interface
TransferHook,
/// Indicates that the tokens in this account belong to a mint with a
/// transfer hook
TransferHookAccount,
/// Includes encrypted withheld fees and the encryption public that they are
/// encrypted under
ConfidentialTransferFeeConfig,
/// Includes confidential withheld transfer fees
ConfidentialTransferFeeAmount,
/// Mint contains a pointer to another account (or the same account) that
/// holds metadata
MetadataPointer,
/// Mint contains token-metadata
TokenMetadata,
/// Mint contains a pointer to another account (or the same account) that
/// holds group configurations
GroupPointer,
/// Mint contains token group configurations
TokenGroup,
/// Mint contains a pointer to another account (or the same account) that
/// holds group member configurations
GroupMemberPointer,
/// Mint contains token group member configurations
TokenGroupMember,
/// Mint allowing the minting and burning of confidential tokens
ConfidentialMintBurn,
/// Tokens whose UI amount is scaled by a given amount
ScaledUiAmount,
/// Tokens where minting / burning / transferring can be paused
Pausable,
/// Indicates that the account belongs to a pausable mint
PausableAccount,
/// Test variable-length mint extension
#[cfg(test)]
VariableLenMintTest = u16::MAX - 2,
/// Padding extension used to make an account exactly Multisig::LEN, used
/// for testing
#[cfg(test)]
AccountPaddingTest,
/// Padding extension used to make a mint exactly Multisig::LEN, used for
/// testing
#[cfg(test)]
MintPaddingTest,
}

Each extension adds specialized functionality by including additional state that must be initialized when creating a mint or token account. All extension specific state is stored in the in the tlv_data field, which follows the base account data type. The tlv_data (containing extension state) must be further deserialized according to the specific extension types enabled for that account:

/// Encapsulates immutable base state data (mint or account) with possible
/// extensions, where the base state is Pod for zero-copy serde.
#[derive(Debug, PartialEq)]
pub struct PodStateWithExtensions<'data, S: BaseState + Pod> {
/// Unpacked base data
pub base: &'data S,
/// Slice of data containing all TLV data, deserialized on demand
tlv_data: &'data [u8],
}

Examples#

The anchor-spl crate provides a token_2022_extensions module that contains helper functions and types for working with token extension instructions.

You can find examples for how to work with Token Extensions in an Anchor program in this program examples repository.

Note

Note that while the anchor-spl crate provides helper functions for working with Token Extensions, not all extension instructions have been fully implemented yet. You may need to manually implement CPI calls for some extension instructions.

Esc

Start typing to search the docs.