
托管服务
托管服务是一种强大的金融工具,可以在两方之间实现安全的代币交换。
可以将其视为一个数字保险箱,其中一方用户可以锁定代币 A,等待另一方用户存入代币 B,然后完成交换。
这创造了一个无需信任的环境,双方都不需要担心对方会退出交易。
在本次挑战中,我们将通过三个简单但强大的指令来实现这一概念:
创建(Make):创建者(第一位用户)定义交易条款,并将约定数量的代币 A 存入一个安全的保险库。这就像将您的物品放入保险箱并设定交换条款。
接受(Take):接受者(第二位用户)通过将承诺数量的代币 B 转移给创建者来接受报价,并作为回报,获得锁定的代币 A。这是双方完成各自交易的一刻。
退款(Refund):如果创建者改变主意或未找到合适的接受者,他们可以取消报价并取回代币 A。这就像在交易失败时从保险箱中取回您的物品。
注意:如果您不熟悉 Anchor,建议先阅读 Anchor for Dummies,以熟悉我们将在此程序中使用的核心概念。
Installation
让我们从创建一个新的 Anchor 工作区开始:
anchor init blueshift_anchor_escrow
cd blueshift_anchor_escrow接下来,我们在 anchor-lang crate 上启用 init-if-needed,并添加 anchor-spl crate:
cargo add anchor-lang --features init-if-needed
cargo add anchor-spl由于我们使用了 anchor-spl,因此还需要更新 programs/blueshift_anchor_escrow/Cargo.toml 文件,在 idl-build 功能中包含 anchor-spl/idl-build。
打开 Cargo.toml,您会看到一个现有的 idl-build 行,如下所示:
idl-build = ["anchor-lang/idl-build"]将其修改为同时添加 anchor-spl/idl-build:
idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]现在您可以打开新生成的文件夹,准备开始编码了!
Template
这次,由于程序相当复杂,我们将其拆分为小型、专注的模块,而不是将所有内容都塞入 lib.rs 中。
文件夹结构大致如下:
src
├── instructions
│ ├── make.rs
│ ├── mod.rs
│ ├── refund.rs
│ └── take.rs
├── errors.rs
├── lib.rs
└── state.rs而 lib.rs 将大致如下:
use anchor_lang::prelude::*;
mod state;
mod errors;
mod instructions;
use instructions::*;
declare_id!("22222222222222222222222222222222222222222222");
#[program]
pub mod blueshift_anchor_escrow {
use super::*;
#[instruction(discriminator = 0)]
pub fn make(ctx: Context<Make>, seed: u64, receive: u64, amount: u64) -> Result<()> {
//...
}
#[instruction(discriminator = 1)]
pub fn take(ctx: Context<Take>) -> Result<()> {
//...
}
#[instruction(discriminator = 2)]
pub fn refund(ctx: Context<Refund>) -> Result<()> {
//...
}
}如您所见,我们为指令实现了自定义的 discriminator。因此,请确保使用 0.31.0 或更新版本的 Anchor。
State
我们将进入 state.rs,其中存储了所有 Escrow 的数据。为此,我们将为其提供一个自定义 discriminator,并将结构体包装到 #[account] 宏中,如下所示:
use anchor_lang::prelude::*;
#[derive(InitSpace)]
#[account(discriminator = 1)]
pub struct Escrow {
pub seed: u64,
pub maker: Pubkey,
pub mint_a: Pubkey,
pub mint_b: Pubkey,
pub receive: u64,
pub bump: u8,
}每个字段的作用:
seed:在种子派生过程中使用的随机数,因此一个创建者可以使用相同的代币对打开多个托管账户;存储在链上,以便我们始终可以重新派生 PDA。
maker:创建托管账户的钱包;需要用于退款和接收付款。
mint_a 和 mint_b:交换中“给出”和“获取”两侧的 SPL 铸币地址。
receive:创建者希望获得的代币 B 的数量。(金库的余额本身显示了存入的代币 A 的数量,因此我们不存储该信息。)
bump:缓存的 bump 字节;动态派生它会消耗计算资源,因此我们将其保存一次。
我们可以加入更多信息,但额外的字节意味着额外的租金。仅存储必要内容可以保持存款成本低,同时仍然让程序执行所需的每一条规则。
最后,我们添加了#[derive(InitSpace)]宏,这样我们就不需要手动计算这个结构的租金。
Errors
现在我们可以转到errors.rs文件,在那里我们将添加一些稍后会用到的错误,如下所示:
use anchor_lang::prelude::*;
#[error_code]
pub enum EscrowError {
#[msg("Invalid amount")]
InvalidAmount,
#[msg("Invalid maker")]
InvalidMaker,
#[msg("Invalid mint a")]
InvalidMintA,
#[msg("Invalid mint b")]
InvalidMintB,
}每个枚举都映射到一个清晰、易于理解的消息,当约束或require!()失败时,Anchor 会显示这些消息。