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
Checking account access…