//! An example of an escrow program, inspired by PaulX tutorial seen here //! https://paulx.dev/blog/2021/01/14/programming-on-solana-an-introduction/ //! This example has some changes to implementation, but more or less should be the same overall //! Also gives examples on how to use some newer anchor features and CPI //! //! User (Initializer) constructs an escrow deal: //! - SPL token (X) they will offer and amount //! - SPL token (Y) count they want in return and amount //! - Program will take ownership of initializer's token X account //! //! Once this escrow is initialised, either: //! 1. User (Taker) can call the exchange function to exchange their Y for X //! - This will close the escrow account and no longer be usable //! OR //! 2. If no one has exchanged, the initializer can close the escrow account //! - Initializer will get back ownership of their token X account use anchor_lang::prelude::*; use anchor_spl::token::{self, SetAuthority, TokenAccount, Transfer}; use spl_token::instruction::AuthorityType; declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); #[program] pub mod escrow { use super::*; pub fn initialize_escrow( ctx: Context, initializer_amount: u64, taker_amount: u64, ) -> ProgramResult { ctx.accounts.escrow_account.initializer_key = *ctx.accounts.initializer.key; ctx.accounts .escrow_account .initializer_deposit_token_account = *ctx .accounts .initializer_deposit_token_account .to_account_info() .key; ctx.accounts .escrow_account .initializer_receive_token_account = *ctx .accounts .initializer_receive_token_account .to_account_info() .key; ctx.accounts.escrow_account.initializer_amount = initializer_amount; ctx.accounts.escrow_account.taker_amount = taker_amount; let (pda, _bump_seed) = Pubkey::find_program_address(&[b"escrow"], ctx.program_id); token::set_authority(ctx.accounts.into(), AuthorityType::AccountOwner, Some(pda))?; Ok(()) } pub fn cancel_escrow(ctx: Context) -> ProgramResult { let (_pda, bump_seed) = Pubkey::find_program_address(&[b"escrow"], ctx.program_id); let seeds = &[&b"escrow"[..], &[bump_seed]]; token::set_authority( ctx.accounts .into_set_authority_context() .with_signer(&[&seeds[..]]), AuthorityType::AccountOwner, Some(ctx.accounts.escrow_account.initializer_key), )?; Ok(()) } pub fn exchange(ctx: Context) -> ProgramResult { // Transferring from initializer to taker let (_pda, bump_seed) = Pubkey::find_program_address(&[b"escrow"], ctx.program_id); let seeds = &[&b"escrow"[..], &[bump_seed]]; token::transfer( ctx.accounts .into_transfer_to_taker_context() .with_signer(&[&seeds[..]]), ctx.accounts.escrow_account.initializer_amount, )?; token::transfer( ctx.accounts.into_transfer_to_initializer_context(), ctx.accounts.escrow_account.taker_amount, )?; token::set_authority( ctx.accounts .into_set_authority_context() .with_signer(&[&seeds[..]]), AuthorityType::AccountOwner, Some(ctx.accounts.escrow_account.initializer_key), )?; Ok(()) } } #[derive(Accounts)] #[instruction(initializer_amount: u64)] pub struct InitializeEscrow<'info> { #[account(signer)] pub initializer: AccountInfo<'info>, #[account( mut, constraint = initializer_deposit_token_account.amount >= initializer_amount )] pub initializer_deposit_token_account: Account<'info, TokenAccount>, pub initializer_receive_token_account: Account<'info, TokenAccount>, #[account(zero)] pub escrow_account: Account<'info, EscrowAccount>, pub token_program: AccountInfo<'info>, } #[derive(Accounts)] pub struct Exchange<'info> { #[account(signer)] pub taker: AccountInfo<'info>, #[account(mut)] pub taker_deposit_token_account: Account<'info, TokenAccount>, #[account(mut)] pub taker_receive_token_account: Account<'info, TokenAccount>, #[account(mut)] pub pda_deposit_token_account: Account<'info, TokenAccount>, #[account(mut)] pub initializer_receive_token_account: Account<'info, TokenAccount>, #[account(mut)] pub initializer_main_account: AccountInfo<'info>, #[account( mut, constraint = escrow_account.taker_amount <= taker_deposit_token_account.amount, constraint = escrow_account.initializer_deposit_token_account == *pda_deposit_token_account.to_account_info().key, constraint = escrow_account.initializer_receive_token_account == *initializer_receive_token_account.to_account_info().key, constraint = escrow_account.initializer_key == *initializer_main_account.key, close = initializer_main_account )] pub escrow_account: Account<'info, EscrowAccount>, pub pda_account: AccountInfo<'info>, pub token_program: AccountInfo<'info>, } #[derive(Accounts)] pub struct CancelEscrow<'info> { pub initializer: AccountInfo<'info>, #[account(mut)] pub pda_deposit_token_account: Account<'info, TokenAccount>, pub pda_account: AccountInfo<'info>, #[account( mut, constraint = escrow_account.initializer_key == *initializer.key, constraint = escrow_account.initializer_deposit_token_account == *pda_deposit_token_account.to_account_info().key, close = initializer )] pub escrow_account: Account<'info, EscrowAccount>, pub token_program: AccountInfo<'info>, } #[account] pub struct EscrowAccount { pub initializer_key: Pubkey, pub initializer_deposit_token_account: Pubkey, pub initializer_receive_token_account: Pubkey, pub initializer_amount: u64, pub taker_amount: u64, } impl<'info> From<&mut InitializeEscrow<'info>> for CpiContext<'_, '_, '_, 'info, SetAuthority<'info>> { fn from(accounts: &mut InitializeEscrow<'info>) -> Self { let cpi_accounts = SetAuthority { account_or_mint: accounts .initializer_deposit_token_account .to_account_info() .clone(), current_authority: accounts.initializer.clone(), }; let cpi_program = accounts.token_program.clone(); CpiContext::new(cpi_program, cpi_accounts) } } impl<'info> CancelEscrow<'info> { fn into_set_authority_context(&self) -> CpiContext<'_, '_, '_, 'info, SetAuthority<'info>> { let cpi_accounts = SetAuthority { account_or_mint: self.pda_deposit_token_account.to_account_info().clone(), current_authority: self.pda_account.clone(), }; CpiContext::new(self.token_program.clone(), cpi_accounts) } } impl<'info> Exchange<'info> { fn into_set_authority_context(&self) -> CpiContext<'_, '_, '_, 'info, SetAuthority<'info>> { let cpi_accounts = SetAuthority { account_or_mint: self.pda_deposit_token_account.to_account_info().clone(), current_authority: self.pda_account.clone(), }; CpiContext::new(self.token_program.clone(), cpi_accounts) } } impl<'info> Exchange<'info> { fn into_transfer_to_taker_context(&self) -> CpiContext<'_, '_, '_, 'info, Transfer<'info>> { let cpi_accounts = Transfer { from: self.pda_deposit_token_account.to_account_info().clone(), to: self.taker_receive_token_account.to_account_info().clone(), authority: self.pda_account.clone(), }; CpiContext::new(self.token_program.clone(), cpi_accounts) } } impl<'info> Exchange<'info> { fn into_transfer_to_initializer_context( &self, ) -> CpiContext<'_, '_, '_, 'info, Transfer<'info>> { let cpi_accounts = Transfer { from: self.taker_deposit_token_account.to_account_info().clone(), to: self .initializer_receive_token_account .to_account_info() .clone(), authority: self.taker.clone(), }; CpiContext::new(self.token_program.clone(), cpi_accounts) } }