Is it possible to freeze a usdc token account of user? Au gave code example but u am not sure if it works
Is it possible to freeze a usdc token account of user? Au gave code example but u am not sure if it works
Loading saved threads...
Yellow Wolf · External communityPost link
External question — Solana Stack Exchange
Author: Yellow Wolf
Original post: https://solana.stackexchange.com/questions/24507
License: CC BY-SA 4.0 — https://creativecommons.org/licenses/by-sa/4.0/
Adaptation: HTML converted to plain text; contact email addresses removed.
so I tried to make a demo game where it uses USDC to be freeze as the time being transferred out, I wasn’t sure if this was possible, so I asked AI. Please check if the code is correct-
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{
self, Mint, TokenAccount, TokenInterface, TransferChecked, FreezeAccount, ThawAccount, SetAuthority, spl_token_2022::instruction::AuthorityType
};
declare_id!("YourGameProgramID11111111111111111111111");
/// 📌 GLOBAL CONSTANTS
pub mod constants {
use anchor_lang::prelude::Pubkey;
use anchor_lang::pubkey;
/// Mainnet Native USDC Mint Address (Legacy SPL)
/// (If you are testing on devnet, swap this out for your devnet USDC mint address)
pub const USDC_MINT_ADDRESS: Pubkey = pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
/// The ONLY off-chain backend server keypair allowed to resolve losses
pub const SERVER_OPERATOR_ADDRESS: Pubkey = pubkey!("3xxxxSERVERxxxOPERATORxxxDUMMYxxxADDRESSxxxx");
}
#[program]
pub mod betting_game_complete {
use super::*;
/// 1. USER DEPOSITS & SECURES THE BET
/// User calls this function to lock up a specified amount of USDC to play.
pub fn deposit_and_lock_bet(ctx: Context<DepositAndLockBet>, amount: u64) -> Result<()> {
let cpi_program = ctx.accounts.token_program.to_account_info();
// Step A: Move USDC from user's primary wallet into their custom Auxiliary Game Account
let transfer_cpi = TransferChecked {
from: ctx.accounts.user_main_usdc.to_account_info(),
to: ctx.accounts.auxiliary_game_account.to_account_info(),
authority: ctx.accounts.user.to_account_info(),
mint: ctx.accounts.usdc_mint.to_account_info(),
};
token_interface::transfer_checked(
CpiContext::new(cpi_program.clone(), transfer_cpi),
amount,
ctx.accounts.usdc_mint.decimals
)?;
// Step B: Set Program PDA as the FREEZE AUTHORITY
let set_freeze_cpi = SetAuthority {
account_or_mint: ctx.accounts.auxiliary_game_account.to_account_info(),
current_authority: ctx.accounts.user.to_account_info(),
};
token_interface::set_authority(
CpiContext::new(cpi_program.clone(), set_freeze_cpi),
AuthorityType::FreezeAccount,
Some(ctx.accounts.game_signer_pda.key()),
)?;
// Step C: Set Program PDA as the CLOSE AUTHORITY
let set_close_cpi = SetAuthority {
account_or_mint: ctx.accounts.auxiliary_game_account.to_account_info(),
current_authority: ctx.accounts.user.to_account_info(),
};
token_interface::set_authority(
CpiContext::new(cpi_program.clone(), set_close_cpi),
AuthorityType::CloseAccount,
Some(ctx.accounts.game_signer_pda.key()),
)?;
// Step D: Freeze the account instantly
let bump = ctx.bumps.game_signer_pda;
let signer_seeds: &[&[&[u8]]] = &[&[b"game_signer", &[bump]]];
let freeze_cpi = FreezeAccount {
account: ctx.accounts.auxiliary_game_account.to_account_info(),
mint: ctx.accounts.usdc_mint.to_account_info(),
authority: ctx.accounts.game_signer_pda.to_account_info(),
};
token_interface::freeze_account(CpiContext::new_with_signatures(cpi_program, freeze_cpi, signer_seeds))?;
Ok(())
}
/// 2. SERVER RESOLVES A PARTIAL LOSS (e.g., extracts 3 USDC out of 10)
/// Only your authorized backend server can call this instruction.
pub fn resolve_partial_loss(ctx: Context<ResolvePartialLoss>, loss_amount: u64) -> Result<()> {
let bump = ctx.bumps.game_signer_pda;
let signer_seeds: &[&[&[u8]]] = &[&[b"game_signer", &[bump]]];
let cpi_program = ctx.accounts.token_program.to_account_info();
// Step A: Server PDA Thaws the account temporarily
let thaw_cpi = ThawAccount {
account: ctx.accounts.auxiliary_game_account.to_account_info(),
mint: ctx.accounts.usdc_mint.to_account_info(),
authority: ctx.accounts.game_signer_pda.to_account_info(),
};
token_interface::thaw_account(CpiContext::new_with_signatures(cpi_program.clone(), thaw_cpi, signer_seeds))?;
// Step B: Unilaterally shift ownership to the Main Pool temporarily to execute transfer
let set_owner_cpi = SetAuthority {
account_or_mint: ctx.accounts.auxiliary_game_account.to_account_info(),
current_authority: ctx.accounts.game_signer_pda.to_account_info(),
};
token_interface::set_authority(
CpiContext::new_with_signatures(cpi_program.clone(), set_owner_cpi, signer_seeds),
AuthorityType::AccountOwner,
Some(ctx.accounts.main_pool_usdc.key()),
)?;
// Step C: Transfer exactly the `loss_amount` (e.g., 3 USDC) to the house pool
let transfer_cpi = TransferChecked {
from: ctx.accounts.auxiliary_game_account.to_account_info(),
to: ctx.accounts.main_pool_usdc.to_account_info(),
authority: ctx.accounts.main_pool_usdc.to_account_info(),
mint: ctx.accounts.usdc_mint.to_account_info(),
};
token_interface::transfer_checked(
CpiContext::new(cpi_program.clone(), transfer_cpi),
loss_amount,
ctx.accounts.usdc_mint.decimals
)?;
// Step D: Re-assign ownership back to the original User so it stays in their wallet balance
let return_owner_cpi = SetAuthority {
account_or_mint: ctx.accounts.auxiliary_game_account.to_account_info(),
current_authority: ctx.accounts.main_pool_usdc.to_account_info(),
};
token_interface::set_authority(
CpiContext::new(cpi_program.clone(), return_owner_cpi),
AuthorityType::AccountOwner,
Some(ctx.accounts.user_wallet.key()),
)?;
// Step E: Refreeze the account immediately to secure the remaining tokens
let freeze_cpi = FreezeAccount {
account: ctx.accounts.auxiliary_game_account.to_account_info(),
mint: ctx.accounts.usdc_mint.to_account_info(),
authority: ctx.accounts.game_signer_pda.to_account_info(),
};
token_interface::freeze_account(CpiContext::new_with_signatures(cpi_program, freeze_cpi, signer_seeds))?;
Ok(())
}
/// 3. USER WITHDRAWS REMAINING BALANCE
/// User signs this when they want to check out. It clears the position entirely.
pub fn thaw_and_withdraw(ctx: Context<ThawAndWithdraw>) -> Result<()> {
let bump = ctx.bumps.game_signer_pda;
let signer_seeds: &[&[&[u8]]] = &[&[b"game_signer", &[bump]]];
let cpi_program = ctx.accounts.token_program.to_account_info();
// Step A: Thaw the account using the Program PDA's authority
let thaw_cpi = ThawAccount {
account: ctx.accounts.auxiliary_game_account.to_account_info(),
mint: ctx.accounts.usdc_mint.to_account_info(),
authority: ctx.accounts.game_signer_pda.to_account_info(),
};
token_interface::thaw_account(CpiContext::new_with_signatures(cpi_program.clone(), thaw_cpi, signer_seeds))?;
// Step B: Transfer ALL remaining locked USDC back to the user's primary wallet ATA
let remaining_balance = ctx.accounts.auxiliary_game_account.amount;
let transfer_cpi = TransferChecked {
from: ctx.accounts.auxiliary_game_account.to_account_info(),
to: ctx.accounts.user_main_usdc.to_account_info(),
authority: ctx.accounts.user.to_account_info(),
mint: ctx.accounts.usdc_mint.to_account_info(),
};
token_interface::transfer_checked(
CpiContext::new(cpi_program.clone(), transfer_cpi),
remaining_balance,
ctx.accounts.usdc_mint.decimals
)?;
// Step C: Close the empty auxiliary account completely using Close Authority to return rent to user
let close_cpi = token_interface::CloseAccount {
account: ctx.accounts.auxiliary_game_account.to_account_info(),
destination: ctx.accounts.user.to_account_info(),
authority: ctx.accounts.game_signer_pda.to_account_info(),
};
token_interface::close_account(CpiContext::new_with_signatures(cpi_program, close_cpi, signer_seeds))?;
Ok(())
}
}
/// 📋 STRUCT 1: DEPOSIT ACCOUNTS
#[derive(Accounts)]
pub struct DepositAndLockBet<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(
constraint = usdc_mint.key() == constants::USDC_MINT_ADDRESS @ ErrorCode::InvalidMint
)]
pub usdc_mint: InterfaceAccount<'info, Mint>,
#[account(mut)]
pub user_main_usdc: InterfaceAccount<'info, TokenAccount>,
#[account(
init,
payer = user,
seeds = [b"game_bet", user.key().as_ref()],
bump,
token::mint = usdc_mint,
token::authority = user,
token::token_program = token_program,
)]
pub auxiliary_game_account: InterfaceAccount<'info, TokenAccount>,
#[account(seeds = [b"game_signer"], bump)]
pub game_signer_pda: AccountInfo<'info>,
pub token_program: Interface<'info, TokenInterface>,
pub system_program: Program<'info, System>,
}
/// 📋 STRUCT 2: RESOLVE LOSS ACCOUNTS (SERVER OPERATOR EXCLUSIVE)
#[derive(Accounts)]
pub struct ResolvePartialLoss<'info> {
#[account(
signer,
constraint = server_operator.key() == constants::SERVER_OPERATOR_ADDRESS @ ErrorCode::UnauthorizedServer
)]
pub server_operator: Signer<'info>,
#[account(mut)]
/// CHECK: The target user address
pub user_wallet: AccountInfo<'info>,
#[account(constraint = usdc_mint.key() == constants::USDC_MINT_ADDRESS)]
pub usdc_mint: InterfaceAccount<'info, Mint>,
#[account(
mut,
seeds = [b"game_bet", user_wallet.key().as_ref()],
bump,
)]
pub auxiliary_game_account: InterfaceAccount<'info, TokenAccount>,
#[account(mut, constraint = main_pool_usdc.owner == server_operator.key())]
pub main_pool_usdc: InterfaceAccount<'info, TokenAccount>,
#[account(seeds = [b"game_signer"], bump)]
pub game_signer_pda: AccountInfo<'info>,
pub token_program: Interface<'info, TokenInterface>,
}
/// 📋 STRUCT 3: WITHDRAW ACCOUNTS
#[derive(Accounts)]
pub struct ThawAndWithdraw<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(constraint = usdc_mint.key() == constants::USDC_MINT_ADDRESS)]
pub usdc_mint: InterfaceAccount<'info, Mint>,
#[account(mut)]
pub user_main_usdc: InterfaceAccount<'info, TokenAccount>,
#[account(
mut,
seeds = [b"game_bet", user.key().as_ref()],
bump,
)]
pub auxiliary_game_account: InterfaceAccount<'info, TokenAccount>,
#[account(seeds = b"game_signer", bump)]
pub game_signer_pda: AccountInfo<'info>,
pub token_program: Interface<'info, TokenInterface>,
}
/// 🚨 PROGRAM ERRORS
#[error_code]
pub enum ErrorCode {
#[msg("The token mint provided does not match the allowed USDC address.")]
InvalidMint,
#[msg("This instruction can only be signed by the official server wallet operator.")]
UnauthorizedServer,
}
Quote
Report
Post Reply
Quoted from Forex.com.bd-Editorial External question — Solana Stack Exchange Author: Yellow Wolf Source score (net votes, not local likes): 0 Original post: https://solana.stackexchange.com/questions/24507 License: CC BY-SA 4.0 — https://creativecommons.org/licenses/by-sa/4.0/ Adaptation: HTML converted to plain text; contact email addresses removed. so I tried to make a demo game where it uses USDC to be freeze as the time being transferred out, I wasn’t sure if this was possible, so I asked AI. Please check if the code is correct- use anchor_lang::prelude::*; use anchor_spl::token_interface::{ self, Mint, TokenAccount, TokenInterface, TransferChecked, FreezeAccount, ThawAccount, SetAuthority, spl_token_2022::instruction::AuthorityType }; declare_id!("YourGameProgramID11111111111111111111111"); /// 📌 GLOBAL CONSTANTS pub mod constants { use anchor_lang::prelude::Pubkey; use anchor_lang::pubkey; /// Mainnet Native USDC Mint Address (Legacy SPL) /// (If you are testing on devnet, swap this out for your devnet USDC mint address) pub const USDC_MINT_ADDRESS: Pubkey = pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); /// The ONLY off-chain backend server keypair allowed to resolve losses pub const SERVER_OPERATOR_ADDRESS: Pubkey = pubkey!("3xxxxSERVERxxxOPERATORxxxDUMMYxxxADDRESSxxxx"); } #[program] pub mod betting_game_complete { use super::*; /// 1. USER DEPOSITS & SECURES THE BET /// User calls this function to lock up a specified amount of USDC to play. pub fn deposit_and_lock_bet(ctx: Context<DepositAndLockBet>, amount: u64) -> Result<()> { let cpi_program = ctx.accounts.token_program.to_account_info(); // Step A: Move USDC from user's primary wallet into their custom Auxiliary Game Account let transfer_cpi = TransferChecked { from: ctx.accounts.user_main_usdc.to_account_info(), to: ctx.accounts.auxiliary_game_account.to_account_info(), authority: ctx.accounts.user.to_account_info(), mint: ctx.accounts.usdc_mint.to_account_info(), }; token_interface::transfer_checked( CpiContext::new(cpi_program.clone(), transfer_cpi), amount, ctx.accounts.usdc_mint.decimals )?; // Step B: Set Program PDA as the FREEZE AUTHORITY let set_freeze_cpi = SetAuthority { account_or_mint: ctx.accounts.auxiliary_game_account.to_account_info(), current_authority: ctx.accounts.user.to_account_info(), }; token_interface::set_authority( CpiContext::new(cpi_program.clone(), set_freeze_cpi), AuthorityType::FreezeAccount, Some(ctx.accounts.game_signer_pda.key()), )?; // Step C: Set Program PDA as the CLOSE AUTHORITY let set_close_cpi = SetAuthority { account_or_mint: ctx.accounts.auxiliary_game_account.to_account_info(), current_authority: ctx.accounts.user.to_account_info(), }; token_interface::set_authority( CpiContext::new(cpi_program.clone(), set_close_cpi), AuthorityType::CloseAccount, Some(ctx.accounts.game_signer_pda.key()), )?; // Step D: Freeze the account instantly let bump = ctx.bumps.game_signer_pda; let signer_seeds: &[&[&[u8]]] = &[&[b"game_signer", &[bump]]]; let freeze_cpi = FreezeAccount { account: ctx.accounts.auxiliary_game_account.to_account_info(), mint: ctx.accounts.usdc_mint.to_account_info(), authority: ctx.accounts.game_signer_pda.to_account_info(), }; token_interface::freeze_account(CpiContext::new_with_signatures(cpi_program, freeze_cpi, signer_seeds))?; Ok(()) } /// 2. SERVER RESOLVES A PARTIAL LOSS (e.g., extracts 3 USDC out of 10) /// Only your authorized backend server can call this instruction. pub fn resolve_partial_loss(ctx: Context<ResolvePartialLoss>, loss_amount: u64) -> Result<()> { let bump = ctx.bumps.game_signer_pda; let signer_seeds: &[&[&[u8]]] = &[&[b"game_signer", &[bump]]]; let cpi_program = ctx.accounts.token_program.to_account_info(); // Step A: Server PDA Thaws the account temporarily let thaw_cpi = ThawAccount { account: ctx.accounts.auxiliary_game_account.to_account_info(), mint: ctx.accounts.usdc_mint.to_account_info(), authority: ctx.accounts.game_signer_pda.to_account_info(), }; token_interface::thaw_account(CpiContext::new_with_signatures(cpi_program.clone(), thaw_cpi, signer_seeds))?; // Step B: Unilaterally shift ownership to the Main Pool temporarily to execute transfer let set_owner_cpi = SetAuthority { account_or_mint: ctx.accounts.auxiliary_game_account.to_account_info(), current_authority: ctx.accounts.game_signer_pda.to_account_info(), }; token_interface::set_authority( CpiContext::new_with_signatures(cpi_program.clone(), set_owner_cpi, signer_seeds), AuthorityType::AccountOwner, Some(ctx.accounts.main_pool_usdc.key()), )?; // Step C: Transfer exactly the `loss_amount` (e.g., 3 USDC) to the house pool let transfer_cpi = TransferChecked { from: ctx.accounts.auxiliary_game_account.to_account_info(), to: ctx.accounts.main_pool_usdc.to_account_info(), authority: ctx.accounts.main_pool_usdc.to_account_info(), mint: ctx.accounts.usdc_mint.to_account_info(), }; token_interface::transfer_checked( CpiContext::new(cpi_program.clone(), transfer_cpi), loss_amount, ctx.accounts.usdc_mint.decimals )?; // Step D: Re-assign ownership back to the original User so it stays in their wallet balance let return_owner_cpi = SetAuthority { account_or_mint: ctx.accounts.auxiliary_game_account.to_account_info(), current_authority: ctx.accounts.main_pool_usdc.to_account_info(), }; token_interface::set_authority( CpiContext::new(cpi_program.clone(), return_owner_cpi), AuthorityType::AccountOwner, Some(ctx.accounts.user_wallet.key()), )?; // Step E: Refreeze the account immediately to secure the remaining tokens let freeze_cpi = FreezeAccount { account: ctx.accounts.auxiliary_game_account.to_account_info(), mint: ctx.accounts.usdc_mint.to_account_info(), authority: ctx.accounts.game_signer_pda.to_account_info(), }; token_interface::freeze_account(CpiContext::new_with_signatures(cpi_program, freeze_cpi, signer_seeds))?; Ok(()) } /// 3. USER WITHDRAWS REMAINING BALANCE /// User signs this when they want to check out. It clears the position entirely. pub fn thaw_and_withdraw(ctx: Context<ThawAndWithdraw>) -> Result<()> { let bump = ctx.bumps.game_signer_pda; let signer_seeds: &[&[&[u8]]] = &[&[b"game_signer", &[bump]]]; let cpi_program = ctx.accounts.token_program.to_account_info(); // Step A: Thaw the account using the Program PDA's authority let thaw_cpi = ThawAccount { account: ctx.accounts.auxiliary_game_account.to_account_info(), mint: ctx.accounts.usdc_mint.to_account_info(), authority: ctx.accounts.game_signer_pda.to_account_info(), }; token_interface::thaw_account(CpiContext::new_with_signatures(cpi_program.clone(), thaw_cpi, signer_seeds))?; // Step B: Transfer ALL remaining locked USDC back to the user's primary wallet ATA let remaining_balance = ctx.accounts.auxiliary_game_account.amount; let transfer_cpi = TransferChecked { from: ctx.accounts.auxiliary_game_account.to_account_info(), to: ctx.accounts.user_main_usdc.to_account_info(), authority: ctx.accounts.user.to_account_info(), mint: ctx.accounts.usdc_mint.to_account_info(), }; token_interface::transfer_checked( CpiContext::new(cpi_program.clone(), transfer_cpi), remaining_balance, ctx.accounts.usdc_mint.decimals )?; // Step C: Close the empty auxiliary account completely using Close Authority to return rent to user let close_cpi = token_interface::CloseAccount { account: ctx.accounts.auxiliary_game_account.to_account_info(), destination: ctx.accounts.user.to_account_info(), authority: ctx.accounts.game_signer_pda.to_account_info(), }; token_interface::close_account(CpiContext::new_with_signatures(cpi_program, close_cpi, signer_seeds))?; Ok(()) } } /// 📋 STRUCT 1: DEPOSIT ACCOUNTS #[derive(Accounts)] pub struct DepositAndLockBet<'info> { #[account(mut)] pub user: Signer<'info>, #[account( constraint = usdc_mint.key() == constants::USDC_MINT_ADDRESS @ ErrorCode::InvalidMint )] pub usdc_mint: InterfaceAccount<'info, Mint>, #[account(mut)] pub user_main_usdc: InterfaceAccount<'info, TokenAccount>, #[account( init, payer = user, seeds = [b"game_bet", user.key().as_ref()], bump, token::mint = usdc_mint, token::authority = user, token::token_program = token_program, )] pub auxiliary_game_account: InterfaceAccount<'info, TokenAccount>, #[account(seeds = [b"game_signer"], bump)] pub game_signer_pda: AccountInfo<'info>, pub token_program: Interface<'info, TokenInterface>, pub system_program: Program<'info, System>, } /// 📋 STRUCT 2: RESOLVE LOSS ACCOUNTS (SERVER OPERATOR EXCLUSIVE) #[derive(Accounts)] pub struct ResolvePartialLoss<'info> { #[account( signer, constraint = server_operator.key() == constants::SERVER_OPERATOR_ADDRESS @ ErrorCode::UnauthorizedServer )] pub server_operator: Signer<'info>, #[account(mut)] /// CHECK: The target user address pub user_wallet: AccountInfo<'info>, #[account(constraint = usdc_mint.key() == constants::USDC_MINT_ADDRESS)] pub usdc_mint: InterfaceAccount<'info, Mint>, #[account( mut, seeds = [b"game_bet", user_wallet.key().as_ref()], bump, )] pub auxiliary_game_account: InterfaceAccount<'info, TokenAccount>, #[account(mut, constraint = main_pool_usdc.owner == server_operator.key())] pub main_pool_usdc: InterfaceAccount<'info, TokenAccount>, #[account(seeds = [b"game_signer"], bump)] pub game_signer_pda: AccountInfo<'info>, pub token_program: Interface<'info, TokenInterface>, } /// 📋 STRUCT 3: WITHDRAW ACCOUNTS #[derive(Accounts)] pub struct ThawAndWithdraw<'info> { #[account(mut)] pub user: Signer<'info>, #[account(constraint = usdc_mint.key() == constants::USDC_MINT_ADDRESS)] pub usdc_mint: InterfaceAccount<'info, Mint>, #[account(mut)] pub user_main_usdc: InterfaceAccount<'info, TokenAccount>, #[account( mut, seeds = [b"game_bet", user.key().as_ref()], bump, )] pub auxiliary_game_account: InterfaceAccount<'info, TokenAccount>, #[account(seeds = b"game_signer", bump)] pub game_signer_pda: AccountInfo<'info>, pub token_program: Interface<'info, TokenInterface>, } /// 🚨 PROGRAM ERRORS #[error_code] pub enum ErrorCode { #[msg("The token mint provided does not match the allowed USDC address.")] InvalidMint, #[msg("This instruction can only be signed by the official server wallet operator.")] UnauthorizedServer, }
Checking account access…