Anchor is a framework for Solana's Sealevel runtime providing several convenient developer tools.If you're familiar with developing in Ethereum's Solidity, Truffle, web3.js, then the experience will be familiar. Although the DSL syntax and semantics are targeted at Solana, the high level flow of writing RPC request handlers, emitting an IDL, and generating clients from IDL is the same.
sha256("account:<MyAccountName>")[..8] || borsh(account_struct)
avm
:anchor
version:Reminder: you can find the full code base for this example here. However, I would strongly recommend you to go through the copy-paste with me to get familiar with the flow.
Vault
that has both a PDA key and a PDA authority.Initializer
can send a transaction to the escrow program to initialize the Vault. In this transaction, two new accounts: Vault
and EscrowAccount
, will be created and tokens (Token A) to be exchanged will be transfered from Initializer
to Vault
.Initializer
can also send a transaction to the escrow program to cancel the demand of escrow. The tokens will be transfered back to the Initialzer
and both Vault
and EscrowAccount
will be closed in this case.Taker
can send a transaction to the escrow to exchange Token B for Token A. First, tokens (Token B) will be transfered from Taker
to Initializer
. Afterward, the tokens (Token A) kept in the Vault will be transfered to Taker
. Finally, both Vault
and EscrowAccount
will be closed.Cargo.toml
Anchor.toml
package.json
tsconfig.json
Cargo.toml
:program_id
(Optional)program_id
defined by declare_id!
macro in lib.rs
:program_id
, which is the public key of the deploy key.program_id
with this new value:#[program]
keyword is what makes the magic happen. In argument ctx
, notice that we have to use a type Initialize
for Context<T>
generic. Initialize
can be considered as a wrapper for instructions. This wrapper is enhanced by Anchor via derived macro (#[derive(account)]
). We will see how it works real quick.#[account]
section.EscrowAccount
initializer_key
Pubkey
initializer_deposit_token_account
Pubkey
initializer_receive_token_account
Pubkey
initializer_amount
u64
taker_amount
u64
EscrowAccount
, we need the following accounts to initialize it.Initialize
AccountInfo
InitialEscrow
instruction. To be stored in EscrowAccount
Account<TokenAccount>
EscrowAccount
Account<TokenAccount>
EscrowAccount
AccountInfo
TokenProgram
Box<Account<EscrowAccount>>
EscrowAccount
Account<TokenAccount>
Vault
, which is created by Anchor via constraints. (Will be explained in part 3)Account<Mint>
AccountInfo
Sysvar<Rent>
Cancel
AccountInfo
EscrowAccount
Account<TokenAccount>
Account<TokenAccount>
AccountInfo
Box<Account<EscrowAccount>>
EscrowAccount
. Have to check if the EscrowAccount
follows certain constraints.AccountInfo
TokenProgram
Exchange
AccountInfo
Exchange
instructionAccount<TokenAccount>
Account<TokenAccount>
Account<TokenAccount>
Account<TokenAccount>
AccountInfo
Box<Account<EscrowAccount>>
EscrowAccount
. Have to check if the EscrowAccount
follows certain constraints.Account<TokenAccount>
AccountInfo
AccountInfo
TokenProgram
Notice the lifetime anotation used in generic
AccountInfo
and Account
. So what is the difference? I suppose it's proper to use Account
over AccountInfo
when you want Anchor to deserialize the data for convenience. In that case, you can access the account data via a trivial method call. For example: ctx.accounts.vault_account.mint
initialize
, what happens is that the input accounts are assigned to EscrowAccount
fields one by one. Then, a program derived address, or PDA, is derived to be going to become new authority of initializer_deposit_token_account
.cancel
, it just simply reset the authority from PDA back to the initializer.exchange
, 3 things happen:pda_deposit_token_account
to taker_receive_token_account
.taker_deposit_token_account
to initializer_receive_token_account
.pda_deposit_token_account
gets set back to the initializer
.tokens::transfer
, token::close_account
and token::set_authority
. It might look a bit overwhelmed in the first place. However, the purpose behind these functions are clear and simple:If you are familiar of Solidity, you can map this concept to solidity modifier.
#[account(signer)]
#[account(mut)]
#[account(constraint = <expression\>)]
#[account(close = <target\>)]
You can think of IDL as ABI if you are familiar with Ethereum and Solidity.
tests/anchor-escrow.ts
to implement the tests.Note:target/types/anchor_escrow
is generated by runninganchor build
. Make sure you build the program first.
Initialize program state
is used for program state setup such as minting tokens. As a result, there should be only 3 test cases corresponding to 3 functions of the program.initialize
, exchange
and cancel
initialize
: