use anchor_lang::prelude::*; use anchor_spl::{ associated_token::AssociatedToken, token_2022::{mint_to, transfer_checked, MintTo, TransferChecked}, token_interface::{Mint, TokenAccount, TokenInterface}, }; use crate::{ constants::{ AUTHORITY_SEED, USED_NONCES_ACCOUNT_SIZE, USED_NONCES_PER_ACCOUNT, USED_NONCES_SEED, VAULT_SEED, }, error::ErrorCode, instructions::wormhole_cpi::*, state::{ message::{ finalize_transfer::{FinalizeTransferPayload, FinalizeTransferResponse}, Payload, SignedPayload, }, used_nonces::UsedNonces, }, }; #[derive(Accounts)] #[instruction(data: SignedPayload)] pub struct FinalizeTransfer<'info> { #[account( init_if_needed, space = USED_NONCES_ACCOUNT_SIZE as usize, payer = common.payer, seeds = [ USED_NONCES_SEED, &(data.payload.destination_nonce / USED_NONCES_PER_ACCOUNT as u64).to_le_bytes(), ], bump, )] pub used_nonces: AccountLoader<'info, UsedNonces>, #[account( mut, seeds = [AUTHORITY_SEED], bump = common.config.bumps.authority, )] pub authority: SystemAccount<'info>, /// CHECK: this can be any type of account pub recipient: UncheckedAccount<'info>, #[account( mint::token_program = token_program, )] pub mint: Box>, // if this account exists the mint registration is already sent #[account( mut, token::mint = mint, token::authority = authority, seeds = [ VAULT_SEED, mint.key().as_ref(), ], bump, token::token_program = token_program, )] pub vault: Option>>, #[account( init_if_needed, payer = common.payer, associated_token::mint = mint, associated_token::authority = recipient, token::token_program = token_program, )] pub token_account: Box>, pub common: WormholeCPI<'info>, pub associated_token_program: Program<'info, AssociatedToken>, pub system_program: Program<'info, System>, pub token_program: Interface<'info, TokenInterface>, } impl<'info> FinalizeTransfer<'info> { pub fn process(&mut self, data: FinalizeTransferPayload) -> Result<()> { UsedNonces::use_nonce( data.destination_nonce, &self.used_nonces, &mut self.common.config, self.authority.to_account_info(), self.common.payer.to_account_info(), &Rent::get()?, self.system_program.to_account_info(), )?; if let Some(vault) = &self.vault { // Native version. We have a proof of token registration by vault existence transfer_checked( CpiContext::new_with_signer( self.token_program.to_account_info(), TransferChecked { from: vault.to_account_info(), to: self.token_account.to_account_info(), authority: self.authority.to_account_info(), mint: self.mint.to_account_info(), }, &[&[AUTHORITY_SEED, &[self.common.config.bumps.authority]]], ), data.amount.try_into().unwrap(), self.mint.decimals, )?; } else { // Bridged version. May be a fake token with our authority set but it will be ignored on the near side require!( self.mint.mint_authority.contains(self.authority.key), ErrorCode::InvalidBridgedToken ); mint_to( CpiContext::new_with_signer( self.token_program.to_account_info(), MintTo { mint: self.mint.to_account_info(), to: self.token_account.to_account_info(), authority: self.authority.to_account_info(), }, &[&[AUTHORITY_SEED, &[self.common.config.bumps.authority]]], ), data.amount.try_into().unwrap(), )?; } let payload = FinalizeTransferResponse { token: self.mint.key(), amount: data.amount, fee_recipient: data.fee_recipient.unwrap_or_default(), transfer_id: data.transfer_id, } .serialize_for_near(())?; self.common.post_message(payload)?; Ok(()) } }