Rust
Pinocchio Escrow

Pinocchio Escrow

18 Graduates

Take

The take instruction finalizes the swap:

  • Close the escrow record, sending its rent lamports back to the maker.
  • Move Token A from the vault to the taker, then close the vault.
  • Move the agreed amount of Token B from the taker to the maker.

Required Accounts

Below are the accounts the context needs:

  • taker: the person that wants to take the deal. Must be a signer and mutable.
  • maker: the creator of the escrow. Must be mutable.
  • escrow: the escrow account that we're initializing. Must be mutable.
  • mint_a: the token we're depositing in the escrow
  • mint_b: the token we want to receive
  • vault: the associated token account owned by the escrow. Must be mutable
  • taker_ata_a: the associated token account owned by the taker for mint_a. Must be mutable
  • maker_ata_b: the associated token account owned by the maker for mint_b. Must be mutable
  • taker_ata_b: the associated token account owned by the taker for mint_b. Must be mutable
  • associated_token_program: the associated token program. Must be executable
  • token_program: the token program. Must be executable
  • system_program: the system program. Must be executable

And we perform these checks on it:

rust
pub struct TakeAccounts<'a> {
  pub taker: &'a AccountInfo,
  pub maker: &'a AccountInfo,
  pub escrow: &'a AccountInfo,
  pub mint_a: &'a AccountInfo,
  pub mint_b: &'a AccountInfo,
  pub vault: &'a AccountInfo,
  pub taker_ata_a: &'a AccountInfo,
  pub taker_ata_b: &'a AccountInfo,
  pub maker_ata_b: &'a AccountInfo,
  pub system_program: &'a AccountInfo,
  pub token_program: &'a AccountInfo,
}
 
impl<'a> TryFrom<&'a [AccountInfo]> for TakeAccounts<'a> {
  type Error = ProgramError;
 
  fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
    let [taker, maker, escrow, mint_a, mint_b, vault, taker_ata_a, taker_ata_b, maker_ata_b, system_program, token_program, _] = accounts else {
      return Err(ProgramError::NotEnoughAccountKeys);
    };
 
    // Basic Accounts Checks
    SignerAccount::check(taker)?;
    ProgramAccount::check(escrow)?;
    MintInterface::check(mint_a)?;
    MintInterface::check(mint_b)?;
    AssociatedTokenAccount::check(taker_ata_b, taker, mint_b, token_program)?;
    AssociatedTokenAccount::check(vault, escrow, mint_a, token_program)?;
 
    // Return the accounts
    Ok(Self {
      taker,
      maker,
      escrow,
      mint_a,
      mint_b,
      taker_ata_a,
      taker_ata_b,
      maker_ata_b,
      vault,
      system_program,
      token_program,
    })
  }
}

Instruction Data

All the data that we need to perform the logic already lives in the Escrow account or on the accounts that we're deserializing. For this reason we don't need any instruction_data for this instruction.

Instruction Logic

We begin by initializing the required accounts in the TryFrom implementation, after we've deserialized the accounts.

For this step, we ensure both the taker's Token A account and the maker's Token B account are initialized using the AssociatedTokenAccount::init_if_needed since we're not sure that they already exists

rust
pub struct Take<'a> {
  pub accounts: TakeAccounts<'a>,
}
 
impl<'a> TryFrom<&'a [AccountInfo]> for Take<'a> {
  type Error = ProgramError;
  
  fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
    let accounts = TakeAccounts::try_from(accounts)?;
 
    // Initialize necessary accounts
    AssociatedTokenAccount::init_if_needed(
      accounts.taker_ata_a,
      accounts.mint_a,
      accounts.taker,
      accounts.taker,
      accounts.system_program,
      accounts.token_program,
    )?;
 
    AssociatedTokenAccount::init_if_needed(
      accounts.maker_ata_b,
      accounts.mint_b,
      accounts.taker,
      accounts.maker,
      accounts.system_program,
      accounts.token_program,
    )?;
 
    Ok(Self {
      accounts,
    })
  }
}

We can now focus on the logic itself that will:

  • Transfer the tokens from the taker_ata_b to the maker_ata_b.
  • Transfer the tokens from the vault to the taker_ata_a.
  • Close the now empty vault and withdraw the rent from the account.
rust
impl<'a> Take<'a> {
  pub const DISCRIMINATOR: &'a u8 = &1;
  
  pub fn process(&mut self) -> ProgramResult {
    let data = self.accounts.escrow.try_borrow_data()?;
    let escrow = Escrow::load(&data)?;
 
    // Check if the escrow is valid
    let escrow_key = create_program_address(&[b"escrow", self.accounts.maker.key(), &escrow.seed.to_le_bytes(), &escrow.bump], &crate::ID)?;
    if &escrow_key != self.accounts.escrow.key() {
      return Err(ProgramError::InvalidAccountOwner);
    }
    
    let seed_binding = escrow.seed.to_le_bytes();
    let bump_binding = escrow.bump;
    let escrow_seeds = [
      Seed::from(b"escrow"),
      Seed::from(self.accounts.maker.key().as_ref()),
      Seed::from(&seed_binding),
      Seed::from(&bump_binding),
    ];
    let signer = Signer::from(&escrow_seeds);
    
    let amount = TokenAccount::get_amount(self.accounts.vault);
    
    // Transfer from the Vault to the Taker
    Transfer {
      from: self.accounts.vault,
      to: self.accounts.taker_ata_a,
      authority: self.accounts.escrow,
      amount,
    }.invoke_signed(&[signer.clone()])?;
 
    // Close the Vault
    CloseAccount {
      account: self.accounts.vault,
      destination: self.accounts.maker,
      authority: self.accounts.escrow,
    }.invoke_signed(&[signer.clone()])?;
 
    // Transfer from the Taker to the Maker
    Transfer {
      from: self.accounts.taker_ata_b,
      to: self.accounts.maker_ata_b,
      authority: self.accounts.taker,
      amount: escrow.receive,
    }.invoke()?;
 
    // Close the Escrow
    drop(data);
    ProgramAccount::close(self.accounts.escrow, self.accounts.taker)?;
 
    Ok(())
  }
}
Next PageRefund
OR SKIP TO THE CHALLENGE
Ready to take the challenge?
Contents
View Source
Blueshift © 2025Commit: cc5f933