Make
We can now move to the make
instruction, that lives in the make.rs
and will perform this actions:
- Initialises the Escrow record and stores all the terms.
- Creates the Vault (an ATA for
mint_a
owned by theescrow
). - Moves the maker's Token A into that vault with a CPI to the SPL-Token program.
Accounts
The accounts needed in this context are:
maker
: the user that decide the terms and deposit themint_a
into theEscrow
escrow
: the account where all the terms of this exchange livesmint_a
: the token that themaker
is depositingmint_b
: the token that themaker
wants in exchangemaker_ata_a
: the token account associated with themaker
andmint_a
used to the deposit tokens in thevault
vault
: the token account associated with theescrow
andmint_a
where the tokens deposits gets parkedassociated_token_program
: the associated token program used to create the associated token accountstoken_program
: the token program used to CPI the transfersystem_program
: the system program used to create theEscrow
And with all the constraint it will look something like this:
#[instruction(seed: u64)]
pub struct Make<'info> {
#[account(mut)]
pub maker: Signer<'info>,
#[account(
init,
payer = maker,
space = Escrow::INIT_SPACE + Escrow::DISCRIMINATOR.len(),
seeds = [b"escrow", maker.key().as_ref(), seed.to_le_bytes().as_ref()],
bump,
)]
pub escrow: Account<'info, Escrow>,
/// Token Accounts
#[account(
mint::token_program = token_program
)]
pub mint_a: InterfaceAccount<'info, Mint>,
#[account(
mint::token_program = token_program
)]
pub mint_b: InterfaceAccount<'info, Mint>,
#[account(
mut,
associated_token::mint = mint_a,
associated_token::authority = maker,
associated_token::token_program = token_program
)]
pub maker_ata_a: InterfaceAccount<'info, TokenAccount>,
#[account(
init,
payer = maker,
associated_token::mint = mint_a,
associated_token::authority = escrow,
associated_token::token_program = token_program
)]
pub vault: InterfaceAccount<'info, TokenAccount>,
/// Programs
pub associated_token_program: Program<'info, AssociatedToken>,
pub token_program: Interface<'info, TokenInterface>,
pub system_program: Program<'info, System>,
}
Note: Since we supply only one token_program
to make sure that the CPI doesn't fail in the take
instruction, where we perform transfer with both mint, we need to check that the two mint are owned by the same program.
Logic
After initializing the Accounts, we can finally handle the logic by creating smaller helper function as an implementation of the account struct.
We start by populating the Escrow
using the set_inner()
helper, and we then move onto depositing the tokens through the transfer
CPI like this:
impl<'info> Make<'info> {
/// # Create the Escrow
fn populate_escrow(&mut self, seed: u64, amount: u64, bump: u8) -> Result<()> {
self.escrow.set_inner(Escrow {
seed,
maker: self.maker.key(),
mint_a: self.mint_a.key(),
mint_b: self.mint_b.key(),
receive: amount,
bump,
});
Ok(())
}
/// # Deposit the tokens
fn deposit_tokens(&self, amount: u64) -> Result<()> {
transfer_checked(
CpiContext::new(
self.token_program.to_account_info(),
TransferChecked {
from: self.maker_ata_a.to_account_info(),
mint: self.mint_a.to_account_info(),
to: self.vault.to_account_info(),
authority: self.maker.to_account_info(),
}), amount, self.mint_a.decimals
)?;
Ok(())
}
}
We can see that Anchor helps us in multiple ways:
set_inner()
: guarantees every field is populated.transfer_checked
: wraps the Token CPI just like the System helpers we used earlier.
And now we can move onto creating an handler
function where we perform some checks before using the helpers, like this:
pub fn handler(ctx: Context<Make>, seed: u64, receive: u64, amount: u64) -> Result<()> {
// Validate the amount
require_gte!(receive, 0, EscrowError::InvalidAmount);
require_gte!(amount, 0, EscrowError::InvalidAmount);
// Save the Escrow Data
ctx.accounts.populate_escrow(seed, receive, ctx.bumps.escrow)?;
// Deposit Tokens
ctx.accounts.deposit_tokens(amount)?;
Ok(())
}
Here we add two validation checks; one on the amount
and one on the receive
arguments to ensure we're not passing a zero value for either.