The Default Account State Extension
The DefaultAccountState
extension is a Mint extension that allows all newly created Token
accounts for that specific mint to be frozen by default. The Freeze Authority
of the mint can then thaw (unfreeze) these Token
accounts so they can become usable.
Initializing the Mint Account
Since Anchor
doesn't have any macros for the default_account_state
extension we're going to create a Mint
account using the raw CPIs.
Here's how to create a mint with the Default Account State extension:
use anchor_lang::prelude::*;
use anchor_lang::system_program::{create_account, CreateAccount};
use anchor_spl::{
token_2022::{
initialize_mint2,
spl_token_2022::{extension::ExtensionType, pod::PodMint, state::AccountState},
InitializeMint2,
},
token_interface::{
default_account_state_initialize, DefaultAccountStateInitialize, Mint, Token2022,
},
};
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(mut)]
pub mint_account: Signer<'info>,
pub token_program: Program<'info, Token2022>,
pub system_program: Program<'info, System>,
}
pub fn initialize_default_account_state_(
ctx: Context<Initialize>,
) -> Result<()> {
// Calculate space required for mint and extension data
let mint_size =
ExtensionType::try_calculate_account_len::<PodMint>(&[ExtensionType::DefaultAccountState])?;
// Calculate minimum lamports required for size of mint account with extensions
let lamports = (Rent::get()?).minimum_balance(mint_size);
// Invoke System Program to create new account with space for mint and extension data
create_account(
CpiContext::new(
ctx.accounts.system_program.to_account_info(),
CreateAccount {
from: ctx.accounts.payer.to_account_info(),
to: ctx.accounts.mint_account.to_account_info(),
},
),
lamports, // Lamports
mint_size as u64, // Space
&ctx.accounts.token_program.key(), // Owner Program
)?;
// Initialize the NonTransferable extension
// This instruction must come before the instruction to initialize the mint data
default_account_state_initialize(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
DefaultAccountStateInitialize {
token_program_id: ctx.accounts.token_program.to_account_info(),
mint: ctx.accounts.mint_account.to_account_info(),
},
),
&AccountState::Frozen, // default frozen token accounts
)?;
// Initialize the standard mint account data
initialize_mint2(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
InitializeMint2 {
mint: ctx.accounts.mint_account.to_account_info(),
},
),
2, // decimals
&ctx.accounts.payer.key(), // mint authority
Some(&ctx.accounts.payer.key()), // freeze authority
)?;
Ok(())
}
Thawing the Token Account
Having the DefaultAccountState
extension means that all Token
account that gets initialized, are going to be frozen
by default.
This means that it's not possible to Mint
, Transfer
or pretty much do anything with those Token
account if we don't thaw (unfreeze) them.
We can easily thaw a Token
account using the thaw_account()
instruction like this:
use anchor_lang::prelude::*;
use anchor_spl::{
token_interface::{thaw_account, ThawAccount, Mint, Token, Token2022}
};
#[derive(Accounts)]
pub struct ThawAccount<'info> {
#[account(mut)]
pub freeze_authority: Signer<'info>,
#[account(
mut,
mint::freeze_authority = freeze_authority,
)]
pub mint_account: InterfaceAccount<'info, Mint>,
pub token_account: InterfaceAccount<'info, Token>,
pub token_program: Program<'info, Token2022>,
pub system_program: Program<'info, System>,
}
pub fn thaw_account(
ctx: Context<UpdateState>,
) -> Result<()> {
thaw_account(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
ThawAccount {
account: ctx.accounts.token_account.to_account_info(),
mint: ctx.accounts.mint_account.to_account_info(),
freeze_authority: ctx.accounts.freeze_authority.to_account_info(),
},
),
&account_state,
)?;
Ok(())
}
Changing the Default Account State
Once we don't need to have greater control over token distribution and we want to let everybody freely trade or token, we can change the default account state using the default_account_state_update()
instruction like so:
use anchor_lang::prelude::*;
use anchor_spl::{
token_interface::{default_account_state_update, DefaultAccountStateUpdate, Mint, Token2022}
spl_token_2022::state::AccountState,
};
#[derive(Accounts)]
pub struct UpdateState<'info> {
#[account(mut)]
pub freeze_authority: Signer<'info>,
#[account(
mut,
mint::freeze_authority = freeze_authority,
)]
pub mint_account: InterfaceAccount<'info, Mint>,
pub token_program: Program<'info, Token2022>,
pub system_program: Program<'info, System>,
}
pub fn update_state(
ctx: Context<UpdateState>,
account_state: AnchorAccountState,
) -> Result<()> {
// Convert AnchorAccountState to spl_token_2022::state::AccountState
let account_state = account_state.to_spl_account_state();
default_account_state_update(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
DefaultAccountStateUpdate {
token_program_id: ctx.accounts.token_program.to_account_info(),
mint: ctx.accounts.mint_account.to_account_info(),
freeze_authority: ctx.accounts.freeze_authority.to_account_info(),
},
),
&account_state,
)?;
Ok(())
}
// Custom enum to implement AnchorSerialize and AnchorDeserialize
// This is required to pass the enum as an argument to the instruction
#[derive(AnchorSerialize, AnchorDeserialize)]
pub enum AnchorAccountState {
Uninitialized,
Initialized,
Frozen,
}
// Implement conversion from AnchorAccountState to spl_token_2022::state::AccountState
impl AnchorAccountState {
pub fn to_spl_account_state(&self) -> AccountState {
match self {
AnchorAccountState::Uninitialized => AccountState::Uninitialized,
AnchorAccountState::Initialized => AccountState::Initialized,
AnchorAccountState::Frozen => AccountState::Frozen,
}
}
}