Solana 中的 Escrow 程序

本文详细介绍了 Solana 区块链上的 Escrow 程序的概念和实现,Escrow 程序通过智能合约持有资产直到满足预定义的条件,从而实现无需信任的交易。文章还提供了一个使用 Anchor 框架构建 Solana Escrow 程序的完整示例,包括初始化、存款、交换、取消和退款五个关键指令。

Solana 托管程序是一个基本概念,用于促进 Solana 区块链上各方之间无需信任的交易。与需要信任的传统交易不同,托管通过持有资产直到满足预定义条件,从而消除了对中间人的需求。

托管程序用于为区块链上的点对点交易带来安全性、透明性和自动化。

以下是使用它的原因:

无需信任的交易

托管使各方能够在不信任彼此或第三方的情况下进行交易。智能合约持有双方的资产,直到满足交易条件,确保任何一方都不能作弊。

条件交易

仅当满足特定条件时才释放资产,例如:

  • 双方都已存入其资产
  • 经过了超时期限
  • 提供了特定的批准签名
  • 满足了外部预言机条件

自动结算

托管支持自动化的交易逻辑,例如:

  • 同步资产互换
  • 如果未满足条件则退款
  • 为促进者收取费用
  • 多步骤交易流程

风险缓解

托管合约可防止:

  • 一方未履行其交易
  • 抢先交易攻击
  • 双重支付尝试
  • 恶意行为者利用信任

为什么要学习托管程序?

我们学习它们是因为它们是 Solana 上安全、无需信任的应用程序的基础。

以下是它如此重要的原因:

DeFi 和交易中的核心概念

托管是许多去中心化应用程序的核心:

  • 去中心化交易所 (DEX)
  • NFT 市场
  • 点对点交易平台
  • 拍卖系统

理解无需信任的系统

托管教会你如何构建系统,在这些系统中,代码而非信任决定了各方之间的交互。

真实世界的用例

了解托管可以帮助你理解真实的项目如何促进安全交易,这对于任何交易应用程序来说都是关键部分。

智能合约安全性

学习托管对于理解安全交易模式、状态管理和攻击预防至关重要。

常见误解

托管只是临时存储

托管不是简单的存储帐户。它们是复杂的智能合约,可执行复杂的业务逻辑并确保公平交易。

你总是能拿回你的钱

如果在指定的时间范围内未满足条件,则托管可能具有不同的解决机制——并非所有托管都保证退款。

托管对于简单交易来说太复杂了

即使是基本的点对点交易也可以从托管模式中受益,尤其是在处理有价值的资产或不信任的各方时。

托管消除了所有交易风险

虽然托管降低了交易对手风险,但它们并不能消除所有风险,例如智能合约错误、预言机故障或经济攻击。

使用 Anchor 构建你的第一个 Solana 托管

现在我们了解了什么是托管以及它们为何如此重要,让我们深入研究实际的实现。我们将构建一个全面的代币托管,通过五个关键指令演示所有核心概念:initialize(初始化)、deposit(存入)、exchange(交换)、cancel(取消)和 refund(退款)。

先决条件

在我们开始编码之前,请确保你已具备:

  • Rust 和 Cargo 已安装
  • Solana CLI 工具
  • Anchor 框架 ( cargo install --git https://github.com/coral-xyz/anchor avm --locked --force)
  • 对 Solana 帐户和 PDA 的基本理解

项目设置

首先,让我们创建一个新的 Anchor 项目:

anchor init token_escrow
cd token_escrow

程序结构

让我们从主程序文件开始。打开 programs/escrow/src/lib.rs

use anchor_lang::prelude::*;

declare_id!("ABagojQQU4h1roF1U2ZC2vqvMVrWcBx2gCq1Gy95KEvJ");

pub mod instructions;
pub mod states;

pub use instructions::*;

#[program]
pub mod escrow {
    use super::*;

    pub fn make(ctx: Context<Make>, seed: u64, amount: u64, receive: u64) -> Result<()> {
        ctx.accounts.init_escrow(seed, &ctx.bumps, receive)?;
        ctx.accounts.deposit(amount)
    }

    pub fn refund(ctx: Context<Refund>) -> Result<()> {
        ctx.accounts.refund_and_close_vault()
    }

    pub fn take(ctx: Context<Take>) -> Result<()> {
        ctx.accounts.deposit()?;
        ctx.accounts.transfer_and_close_vault()
    }
}

定义我们的托管状态

创建 programs/escrow/src/states/mod.rs

use anchor_lang::prelude::*;

#[account]
#[derive(InitSpace)]
pub struct Escrow {
    pub seed: u64,      // For indexing multiple escrows
    pub maker: Pubkey,  // Creator of the escrow
    pub mint_a: Pubkey, // Token the maker is offering
    pub mint_b: Pubkey, // Token the maker expects
    pub receive: u64,   // Amount of Token B expected
    pub bump: u8,       // PDA bump
}

#[derive(InitSpace)] macro 会自动计算我们的帐户所需的空间,从而更容易管理帐户大小。

指令 1:Make (创建托管)

创建 programs/escrow/src/instructions/make.rs

use anchor_lang::prelude::*;
use anchor_spl::{
    associated_token::AssociatedToken,
    token_interface::{ Mint, TokenAccount, TransferChecked, TokenInterface, transfer_checked}
};

use crate::states::Escrow;

#[derive(Accounts)]
#[instruction(seed: u64)]
pub struct Make<'info> {
    /// The escrow creator who deposits tokens and pays for account creation.
    /// Must be mutable to deduct lamports for rent and transaction fees.
    /// 托管的创建者,他们存入代币并支付帐户创建费用。
    /// 必须是可变的,以扣除租金和交易费用的 lamports。
    #[account(mut)]
    pub maker: Signer<'info>,

    /// Token mint for the asset being deposited into escrow (Token A).
    /// Validates compatibility with the specified token program.
    /// 用于存入托管的资产的代币 铸币(代币 A)。
    /// 验证与指定代币程序的兼容性。
    #[account(\
        mint::token_program = token_program\
    )]
    pub mint_a: InterfaceAccount<'info, Mint>,

    /// Token mint for the asset expected in return (Token B).
    /// Used for validation and stored in escrow state for future verification.
    /// 预期返回的资产的代币 铸币(代币 B)。
    /// 用于验证,并存储在托管状态中以供将来验证。
    #[account(\
        mint::token_program = token_program\
    )]
    pub mint_b: InterfaceAccount<'info, Mint>,

    /// Maker's token account holding Token A to be escrowed.
    /// Must have sufficient balance for the deposit amount.
    /// 制造商的代币帐户,持有要托管的代币 A。
    /// 必须有足够的余额来支付存款金额。
    #[account(\
        mut,\
        associated_token::mint = mint_a,\
        associated_token::authority = maker,\
        associated_token::token_program = token_program\
    )]
    pub maker_ata_a: InterfaceAccount<'info, TokenAccount>,

    /// Escrow state account storing trade parameters and metadata.
    /// - Contains maker, token mints, expected amount, and bump seed
    /// - Derived from maker's pubkey and user-provided seed for uniqueness
    /// 托管状态帐户,存储交易参数和元数据。
    /// - 包含 制造商, 代币 铸币, 预期金额, 和 bump seed
    /// - 来源于 制造商 的pubkey 和 用户提供的seed 保证唯一性
    #[account(\
        init,\
        payer = maker,\
        seeds = [b"escrow", maker.key().as_ref(), seed.to_le_bytes().as_ref()],\
        bump,\
        space = 8 + Escrow::INIT_SPACE,\
    )]
    pub escrow: Account<'info, Escrow>,

    /// Vault token account that holds escrowed Token A.
    /// - Owned by the escrow PDA to prevent unauthorized access
    /// - Created as ATA for deterministic address derivation
    /// 用于保存托管的代币 A 的 Vault 代币帐户。
    /// - 由托管的 PDA 拥有,以防止未经授权的访问
    /// - 创建为 ATA 以实现确定性地址推导
    #[account(\
        init,\
        payer = maker,\
        associated_token::mint = mint_a,\
        associated_token::authority = escrow,\
        associated_token::token_program = token_program\
    )]
    pub vault: InterfaceAccount<'info, TokenAccount>,

    /// Token program interface for SPL token operations.
    /// Supports both Token Program and Token-2022 for flexibility.
    /// 用于 SPL 代币操作的代币程序接口。
    /// 支持代币程序和 Token-2022 以实现灵活性。
    pub token_program: Interface<'info, TokenInterface>,

    /// Associated Token Program for creating and managing ATAs.
    /// 用于创建和管理 ATA 的关联代币程序。
    pub associated_token_program: Program<'info, AssociatedToken>,

    /// System Program required for account creation and rent payments.
    /// 帐户创建和租金支付所需的系统程序。
    pub system_program: Program<'info, System>,
}

impl<'info> Make<'info> {
    /// Initializes the escrow account with trade parameters.
    ///
    /// Stores all necessary information for future trade execution including
    /// maker identity, token mints, expected receive amount, and PDA bump.
    /// 使用交易参数初始化托管帐户。
    ///
    /// 存储将来交易执行所需的所有必要信息,包括
    /// 制造商身份、代币 铸币、预期接收金额和 PDA bump。
    pub fn init_escrow(&mut self, seed: u64, bump: &MakeBumps, receive: u64) -> Result<()>{
        self.escrow.set_inner(Escrow {
            seed,
            maker: self.maker.key(),
            mint_a: self.mint_a.key(),
            mint_b: self.mint_b.key(),
            receive,
            bump: bump.escrow
        });

        Ok(())
    }

    /// Deposits Token A from maker's account into the escrow vault.
    ///
    /// Uses transfer_checked for enhanced security with decimal validation.
    /// Tokens remain locked until trade completion or refund.
    /// 将代币 A 从 制造商的帐户存入托管 vault。
    ///
    /// 使用 transfer_checked 进行增强的安全性,并进行十进制验证。
    /// 代币将保持锁定状态,直到交易完成或退款。
    pub fn deposit(&mut self, amount: u64) -> Result<()> {
        let decimals = self.mint_a.decimals;
        let cpi_program = self.token_program.to_account_info();

        let cpi_accounts = TransferChecked{
            mint: self.mint_a.to_account_info(),
            from: self.maker_ata_a.to_account_info(),
            to: self.vault.to_account_info(),
            authority: self.maker.to_account_info(),
        };

        let cpi_context = CpiContext::new(cpi_program, cpi_accounts);

        // Transfer tokens with decimal validation for security
        // 传输代币,并进行十进制验证以确保安全
        transfer_checked(cpi_context, amount, decimals)
    }
}

要点:

  • 我们立即将 制造商的代币转账到由托管 PDA 控制的 vault
  • 托管帐户使用用户提供的 seed 存储所有交易参数,以确保唯一性
  • 我们使用 transfer_checked 进行增强的安全性,并进行十进制验证
  • vault 授权机构是托管 PDA,确保只有我们的程序可以移动锁定的代币

指令 2:Take(完成交易)

创建 programs/escrow/src/instructions/take.rs

use anchor_lang::prelude::*;
use anchor_spl::{
    associated_token::AssociatedToken,
    token_interface::{
        close_account, transfer_checked, CloseAccount, Mint, TokenAccount, TokenInterface,
        TransferChecked,
    },
};

use crate::states::Escrow;

/// Instruction for completing an escrow trade.
///
/// Process:
/// 1. Taker sends Token B to maker
/// 2. Escrowed Token A is transferred to taker
/// 3. Vault and escrow accounts are closed
/// 用于完成托管交易的指令。
///
/// 流程:
/// 1. 交易接受者将代币 B 发送给 制造商
/// 2. 托管的代币 A 被转移给交易接受者
/// 3. Vault 和托管帐户已关闭
#[derive(Accounts)]
pub struct Take<'info> {
    /// The trade counterparty who provides Token B and receives Token A.
    /// Must be mutable to receive tokens and pay for account creation.
    /// 提供代币 B 并接收代币 A 的交易对手。
    /// 必须是可变的,才能接收代币并支付帐户创建费用。
    #[account(mut)]
    pub taker: Signer<'info>,

    /// Original escrow creator who receives Token B.
    /// Must be mutable to receive rent refunds from closed accounts.
    /// 接收代币 B 的原始托管创建者。
    /// 必须是可变的,才能从已关闭的帐户接收租金退款。
    #[account(mut)]
    pub maker: SystemAccount<'info>,

    /// Token mint for the asset taker provides (Token B).
    /// Must match the mint stored in escrow state.
    /// 交易接受者提供的资产的代币 铸币(代币 B)。
    /// 必须与托管状态中存储的 铸币 相匹配。
    #[account(\
        mint::token_program = token_program\
    )]
    pub mint_b: InterfaceAccount<'info, Mint>,

    /// Token mint for the asset taker receives (Token A).
    /// Must match the mint stored in escrow state.
    /// 交易接受者接收的资产的代币 铸币(代币 A)。
    /// 必须与托管状态中存储的 铸币 相匹配。
    #[account(\
        mint::token_program = token_program\
    )]
    pub mint_a: InterfaceAccount<'info, Mint>,

    /// Taker's token account holding Token B for payment.
    /// Must have sufficient balance for the required amount.
    /// 交易接受者的代币帐户,持有代币 B 用于付款。
    /// 必须有足够的余额来支付所需的金额。
    #[account(\
        mut,\
        associated_token::mint = mint_b,\
        associated_token::authority = taker,\
        associated_token::token_program = token_program\
    )]
    pub taker_ata_b: Box<InterfaceAccount<'info, TokenAccount>>,

    /// Maker's token account to receive Token B.
    /// Created automatically if it doesn't exist (taker pays rent).
    /// 制造商的代币帐户,用于接收代币 B。
    /// 如果它不存在,则会自动创建(交易接受者支付租金)。
    #[account(\
        init_if_needed,\
        payer = taker,\
        associated_token::mint = mint_b,\
        associated_token::authority = maker,\
        associated_token::token_program = token_program\
    )]
    pub maker_ata_b: Box<InterfaceAccount<'info, TokenAccount>>,

    /// Taker's token account to receive Token A.
    /// Created automatically if it doesn't exist (taker pays rent).
    /// 交易接受者的代币帐户,用于接收代币 A。
    /// 如果它不存在,则会自动创建(交易接受者支付租金)。
    #[account(\
        init_if_needed,\
        payer = taker,\
        associated_token::mint = mint_a,\
        associated_token::authority = taker,\
        associated_token::token_program = token_program\
    )]
    pub taker_ata_a: Box<InterfaceAccount<'info, TokenAccount>>,

    /// Escrow state account containing trade parameters.
    /// - Validates token mints and maker identity match stored values
    /// - Rent is returned to maker upon closing
    /// - Uses stored bump for PDA verification
    /// 托管状态帐户,包含交易参数。
    /// - 验证代币 铸币 和 制造商身份是否与存储的值匹配
    /// - 关闭后,租金将退还给 制造商
    /// - 使用存储的 bump 进行 PDA 验证
    #[account(\
        mut,\
        close = maker,\
        has_one = mint_a,\
        has_one = maker,\
        has_one = mint_b,\
        seeds = [b"escrow", maker.key().as_ref(), escrow.seed.to_le_bytes().as_ref()],\
        bump = escrow.bump,\
    )]
    pub escrow: Account<'info, Escrow>,

    /// Vault holding escrowed Token A.
    /// Will be emptied and closed during trade execution.
    /// Vault 持有托管的代币 A。
    /// 将在交易执行期间清空并关闭。
    #[account(\
        mut,\
        associated_token::mint = mint_a,\
        associated_token::authority = escrow,\
        associated_token::token_program = token_program\
    )]
    pub vault: InterfaceAccount<'info, TokenAccount>,

    pub token_program: Interface<'info, TokenInterface>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub system_program: Program<'info, System>,
}

impl<'info> Take<'info> {
    /// Transfers Token B from taker to maker as payment.
    ///
    /// Uses the expected receive amount stored in escrow state.
    /// This completes the taker's side of the trade agreement.
    /// 将代币 B 从交易接受者转移到 制造商作为付款。
    ///
    /// 使用托管状态中存储的预期接收金额。
    /// 这完成了交易接受者在交易协议中的部分。
    pub fn deposit(&mut self) -> Result<()> {
        let decimals = self.mint_b.decimals;
        let cpi_program = self.token_program.to_account_info();

        let cpi_accounts = TransferChecked {
            mint: self.mint_b.to_account_info(),
            from: self.taker_ata_b.to_account_info(),
            to: self.maker_ata_b.to_account_info(),
            authority: self.taker.to_account_info(),
        };

        let cpi_context = CpiContext::new(cpi_program, cpi_accounts);

        // Transfer the exact amount expected by the maker
        // 转移 制造商期望的确切金额
        transfer_checked(cpi_context, self.escrow.receive, decimals)?;

        Ok(())
    }

    /// Transfers escrowed Token A to taker and closes the vault.
    ///
    /// Completes the trade by releasing all escrowed tokens to the taker
    /// and cleaning up the vault account (rent goes to maker).
    /// 将托管的代币 A 转移给交易接受者并关闭 vault。
    ///
    /// 通过将所有托管的代币释放给交易接受者来完成交易
    /// 并清理 vault 帐户(租金转给 制造商)。
    pub fn transfer_and_close_vault(&mut self) -> Result<()> {
        // Create PDA signer seeds for vault authority
        // 为 vault 授权机构创建 PDA 签名者种子
        let signer_seeds: &[&[&[u8]]; 1] = &[&[\
            b"escrow",\
            self.maker.key.as_ref(),\
            &self.escrow.seed.to_le_bytes()[..],\
            &[self.escrow.bump],\
        ]];

        let decimals = self.mint_a.decimals;
        let cpi_program = self.token_program.to_account_info();

        let cpi_accounts = TransferChecked {
            from: self.vault.to_account_info(),
            mint: self.mint_a.to_account_info(),
            to: self.taker_ata_a.to_account_info(),
            authority: self.escrow.to_account_info(),
        };

        let cpi_context = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds);

        // Transfer all escrowed tokens to taker
        // 将所有托管的代币转账给交易接受者
        transfer_checked(cpi_context, self.vault.amount, decimals)?;

        // Close vault account and refund rent to maker
        // 关闭 vault 帐户并将租金退还给 制造商
        let close_accounts = CloseAccount {
            account: self.vault.to_account_info(),
            destination: self.maker.to_account_info(),
            authority: self.escrow.to_account_info(),
        };

        let close_cpi_ctx = CpiContext::new_with_signer(
            self.token_program.to_account_info(),
            close_accounts,
            signer_seeds,
        );

        close_account(close_cpi_ctx)?;

        Ok(())
    }
}

要点:

  • 我们首先将交易接受者的代币转账给 制造商作为付款
  • 然后,我们将托管的代币释放给交易接受者
  • 这两个转移是原子性的——如果一个失败,则整个交易都将失败
  • 如果代币帐户不存在,我们将使用 init_if_needed 自动创建它们
  • 我们关闭 vault 和托管帐户以进行清理并退还租金

指令 3:Refund(取消托管)

创建 programs/escrow/src/instructions/refund.rs

use anchor_lang::prelude::*;
use anchor_spl::{
    associated_token::AssociatedToken,
    token_interface::{
        close_account, transfer_checked, CloseAccount, Mint, TokenAccount, TokenInterface,
        TransferChecked,
    },
};

use crate::states::Escrow;

#[derive(Accounts)]
pub struct Refund<'info> {
    /// The original escrow creator requesting refund of their tokens.
    /// Must be mutable to receive refunded tokens and rent.
    /// 请求退还其代币的原始托管创建者。
    /// 必须是可变的,才能接收退还的代币和租金。
    #[account(mut)]
    pub maker: Signer<'info>,

    /// Token mint for the escrowed asset (Token A).
    /// Must match the mint stored in escrow state.
    /// 托管资产的代币 铸币(代币 A)。
    /// 必须与托管状态中存储的 铸币 相匹配。
    #[account(\
        mint::token_program = token_program\
    )]
    pub mint_a: InterfaceAccount<'info, Mint>,

    /// Maker's token account to receive refunded Token A.
    /// Must be the same account that originally funded the escrow.
    /// 制造商的代币帐户,用于接收退还的代币 A。
    /// 必须是最初为托管提供资金的同一帐户。
    #[account(\
        mut,\
        associated_token::mint = mint_a,\
        associated_token::authority = maker,\
        associated_token::token_program = token_program\
    )]
    pub maker_ata_a: InterfaceAccount<'info, TokenAccount>,

    /// Escrow state account to be closed after refund.
    /// - Validates maker identity and token mint match stored values
    /// - Rent is returned to maker upon closing
    /// - Uses stored bump for PDA verification
    /// 托管状态帐户将在退款后关闭。
    /// - 验证 制造商 身份和代币 铸币 是否与存储的值匹配
    /// - 关闭后,租金将退还给 制造商
    /// - 使用存储的 bump 进行 PDA 验证
    #[account(\
        mut,\
        close = maker,\
        has_one = mint_a,\
        has_one = maker,\
        seeds = [b"escrow", maker.key().as_ref(), escrow.seed.to_le_bytes().as_ref()],\
        bump = escrow.bump,\
    )]
    pub escrow: Account<'info, Escrow>,

    /// Vault holding escrowed Token A to be refunded.
    /// Will be emptied and closed during refund process.
    /// vault 持有要退还的托管的代币 A。
    /// 将在退款过程中清空并关闭。
    #[account(\
        mut,\
        associated_token::mint = mint_a,\
        associated_token::authority = escrow,\
        associated_token::token_program = token_program\
    )]
    pub vault: InterfaceAccount<'info, TokenAccount>,

    pub token_program: Interface<'info, TokenInterface>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub system_program: Program<'info, System>,
}

impl<'info> Refund<'info> {
    /// Refunds all escrowed tokens to maker and closes the vault.
    ///
    /// Returns the entire vault balance to the original maker,
    /// effectively canceling the escrow and cleaning up accounts.
    /// 将所有托管的代币退还给 制造商并关闭 vault。
    ///
    /// 将整个 vault 余额退还给原始 制造商,
    /// 有效地取消托管并清理帐户。
    pub fn refund_and_close_vault(&mut self) -> Result<()> {
        // Create PDA signer seeds for vault authority
        // 为 vault 授权机构创建 PDA 签名者种子
        let signer_seeds: &[&[&[u8]]; 1] = &[&[\
            b"escrow",\
            self.maker.key.as_ref(),\
            &self.escrow.seed.to_le_bytes()[..],\
            &[self.escrow.bump]\
        ]];

        let decimals = self.mint_a.decimals;
        let cpi_program = self.token_program.to_account_info();

        let cpi_accounts = TransferChecked{
            mint: self.mint_a.to_account_info(),
            from: self.vault.to_account_info(),
            to: self.maker_ata_a.to_account_info(),
            authority: self.escrow.to_account_info(),
        };

        let cpi_context = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds);

        // Return all escrowed tokens to maker
        // 将所有托管的代币退还给 制造商
        transfer_checked(cpi_context, self.vault.amount, decimals)?;

        // Close vault account and refund rent to maker
        // 关闭 vault 帐户并将租金退还给 制造商
        let close_accounts = CloseAccount {
            account: self.vault.to_account_info(),
            destination: self.maker.to_account_info(),
            authority: self.escrow.to_account_info(),
        };

        let close_cpi_ctx = CpiContext::new_with_signer(
            self.token_program.to_account_info(),
            close_accounts,
            signer_seeds
        );

        close_account(close_cpi_ctx)?;

        Ok(())
    }
}

要点:

  • 只有原始 制造商才能请求退款
  • 我们将所有托管的代币退还给 制造商
  • 我们关闭 vault 和托管帐户以进行清理
  • 约束系统确保仅允许有效的退款

指令模块

创建 programs/escrow/src/instructions/mod.rs

pub mod make;
pub mod take;
pub mod refund;

pub use make::*;
pub use take::*;
pub use refund::*;

测试我们的托管

现在让我们创建全面的测试。以下是基于你的实现的测试文件:


import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import * as spl from "@solana/spl-token";
import {
  Keypair,
  LAMPORTS_PER_SOL,
  PublicKey,
  SystemProgram
} from "@solana/web3.js";
import { Escrow } from "../target/types/escrow";

describe("escrow", () => {
  const provider = anchor.getProvider();
  const program = anchor.workspace.escrow as Program<Escrow>;
  const programId = program.programId;
  const tokenProgram = spl.TOKEN_2022_PROGRAM_ID;

  console.log("RPC:", provider.connection.rpcEndpoint);

  const SEED = new anchor.BN(1);

  const confirm = async (signature: string): Promise<string> => {
    const block = await provider.connection.getLatestBlockhash();
    await provider.connection.confirmTransaction({ signature, ...block });
    return signature;
  };

  const log = async (signature: string): Promise<string> => {
    console.log(
      `Your transaction signature: https://explorer.solana.com/transaction/${signature}?cluster=devnet`
    );
    return signature;
  };

  const maker = Keypair.generate();
  const taker = Keypair.generate();
  const mintA = Keypair.generate();
  const mintB = Keypair.generate();

  const [makerAtaA, makerAtaB, takerAtaA, takerAtaB] = [maker, taker]
    .map((a) =>
      [mintA, mintB].map((m) =>
        spl.getAssociatedTokenAddressSync(
          m.publicKey,
          a.publicKey,
          false,
          tokenProgram
        )
      )
    )
    .flat();

  const escrow = PublicKey.findProgramAddressSync(
    [\
      Buffer.from("escrow"),\
      maker.publicKey.toBuffer(),\
      SEED.toArrayLike(Buffer, "le", 8),\
    ],
    programId
  )[0];

  const vault = spl.getAssociatedTokenAddressSync(
    mintA.publicKey,
    escrow,
    true,
    tokenProgram
  );

  const accounts = {
    maker: maker.publicKey,
    taker: taker.publicKey,
    mintA: mintA.publicKey,
    mintB: mintB.publicKey,
    makerAtaA,
    makerAtaB,
    takerAtaA,
    takerAtaB,
    escrow,
    vault,
    tokenProgram,
  };

  it("Airdrop & create mint", async () => {
    const lamports = await spl.getMinimumBalanceForRentExemptMint(
      provider.connection as any
    );

    const tx = new anchor.web3.Transaction();

    // Transfer 1 SOL to maker and taker
    // 将 1 SOL 转移到 制造商和交易接受者
    tx.instructions.push(
      ...[maker, taker].map((a) =>
        SystemProgram.transfer({
          fromPubkey: provider.publicKey,
          toPubkey: a.publicKey,
          lamports: 1 * LAMPORTS_PER_SOL,
        })
      )
    );

    // Create mintA and mintB
    // 创建 mintA 和 mintB
    tx.instructions.push(
      ...[mintA, mintB].map((m) =>
        SystemProgram.createAccount({
          fromPubkey: provider.publicKey,
          newAccountPubkey: m.publicKey,
          lamports,
          space: spl.MINT_SIZE,
          programId: tokenProgram,
        })
      )
    );

    // Init mints, create ATAs, and mint tokens
    // 初始化 铸币, 创建 ATAs, 和 铸币 tokens
    tx.instructions.push(
      ...[\
        { mint: mintA.publicKey, authority: maker.publicKey, ata: makerAtaA },\
        { mint: mintB.publicKey, authority: taker.publicKey, ata: takerAtaB },\
      ].flatMap((x) => [\
        spl.createInitializeMint2Instruction(\
          x.mint,\
          6,\
          x.authority,\
          null,\
          tokenProgram\
        ),\
        spl.createAssociatedTokenAccountIdempotentInstruction(\
          provider.publicKey,\
          x.ata,\
          x.authority,\
          x.mint,\
          tokenProgram\
        ),\
        spl.createMintToInstruction(\
          x.mint,\
          x.ata,\
          x.authority,\
          1e9,\
          undefined,\
          tokenProgram\
        ),\
      ])
    );

    await provider
      .sendAndConfirm(tx, [provider.wallet.payer, maker, taker, mintA, mintB])
      .then(log);
  });

  it("Make escrow", async () => {
    await program.methods
      .make(SEED, new anchor.BN(1e6), new anchor.BN(1e6))
      .accounts({ ...accounts })
      .signers([maker])
      .rpc()
      .then(confirm)
      .then(log);

    console.log("✅ Escrow created successfully");
  });

  it("Take escrow", async () => {
    await program.methods
      .take()

>- 原文链接: [blog.blockmagnates.com/e...](https://blog.blockmagnates.com/escrows-in-solana-6b74fdc17fa0)
>- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
blockmagnates
blockmagnates
The New Crypto Publication on The Block