Rust
Pinocchio Escrow

Pinocchio Escrow

接受

take 指令完成交換的最後步驟:

  • 將 Token A 從保管庫移至接受者。

  • 關閉現在已空的 vault 代幣帳戶。

  • 將商定數量的 Token B 從接受者移至創建者。

  • 關閉 escrow 程式帳戶。

所需帳戶

以下是上下文所需的帳戶:

  • 接受者:希望接受交易的人。必須是簽署者且可變。

  • 創建者:托管的創建者。必須可變。

  • 托管:我們正在初始化的托管帳戶。必須可變。

  • mint_a:我們存入托管的代幣。

  • mint_b:我們希望接收的代幣。

  • 保管庫:由托管擁有的關聯代幣帳戶。必須可變。

  • taker_ata_a:由接受者擁有的 mint_a 的關聯代幣帳戶。必須可變。

  • taker_ata_b:由接受者擁有的 mint_b 的關聯代幣帳戶。必須可變。

  • maker_ata_b:由創建者擁有的 mint_b 的關聯代幣帳戶。必須可變。

  • system_program:系統程序。必須可執行。

  • token_program:代幣程序。必須可執行。

我們對其進行以下檢查:

rust
pub struct TakeAccounts<'a> {
  pub taker: &'a AccountInfo,
  pub maker: &'a AccountInfo,
  pub escrow: &'a AccountInfo,
  pub mint_a: &'a AccountInfo,
  pub mint_b: &'a AccountInfo,
  pub vault: &'a AccountInfo,
  pub taker_ata_a: &'a AccountInfo,
  pub taker_ata_b: &'a AccountInfo,
  pub maker_ata_b: &'a AccountInfo,
  pub system_program: &'a AccountInfo,
  pub token_program: &'a AccountInfo,
}

impl<'a> TryFrom<&'a [AccountInfo]> for TakeAccounts<'a> {
  type Error = ProgramError;

  fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
    let [taker, maker, escrow, mint_a, mint_b, vault, taker_ata_a, taker_ata_b, maker_ata_b, system_program, token_program, _] = accounts else {
      return Err(ProgramError::NotEnoughAccountKeys);
    };

    // Basic Accounts Checks
    SignerAccount::check(taker)?;
    ProgramAccount::check(escrow)?;
    MintInterface::check(mint_a)?;
    MintInterface::check(mint_b)?;
    AssociatedTokenAccount::check(taker_ata_b, taker, mint_b, token_program)?;
    AssociatedTokenAccount::check(vault, escrow, mint_a, token_program)?;

    // Return the accounts
    Ok(Self {
      taker,
      maker,
      escrow,
      mint_a,
      mint_b,
      taker_ata_a,
      taker_ata_b,
      maker_ata_b,
      vault,
      system_program,
      token_program,
    })
  }
}
Expand
[31 more lines]

指令數據

執行邏輯所需的所有數據已經存在於托管帳戶或我們正在反序列化的帳戶中。因此,這個指令不需要任何 instruction_data

指令邏輯

我們首先在 TryFrom 實現中初始化所需的帳戶,並在反序列化帳戶後進行操作。

在此步驟中,我們會使用AssociatedTokenAccount::init_if_needed來確保接收方的 Token A 帳戶和提供方的 Token B 帳戶已初始化,因為我們無法確定它們是否已存在。

rust
pub struct Take<'a> {
  pub accounts: TakeAccounts<'a>,
}

impl<'a> TryFrom<&'a [AccountInfo]> for Take<'a> {
  type Error = ProgramError;

  fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
    let accounts = TakeAccounts::try_from(accounts)?;

    // Initialize necessary accounts
    AssociatedTokenAccount::init_if_needed(
      accounts.taker_ata_a,
      accounts.mint_a,
      accounts.taker,
      accounts.taker,
      accounts.system_program,
      accounts.token_program,
    )?;

    AssociatedTokenAccount::init_if_needed(
      accounts.maker_ata_b,
      accounts.mint_b,
      accounts.taker,
      accounts.maker,
      accounts.system_program,
      accounts.token_program,
    )?;

    Ok(Self {
      accounts,
    })
  }
}
Expand
[19 more lines]

現在我們可以專注於邏輯本身,該邏輯將:

  • vault轉移代幣到taker_ata_a

  • 關閉現在已空的 vault 代幣帳戶並提取其中的租金。

  • taker_ata_b 轉移代幣到 maker_ata_b

  • 在交換完成後關閉 escrow 程式帳戶。

與其用三次獨立的 token CPI 呼叫來完成這次交換,我們可以利用 p-token 的 batch instruction,把所有 token 操作有效率地合併到一次 CPI 呼叫中。

rust
impl<'a> Take<'a> {
  pub const DISCRIMINATOR: &'a u8 = &1;

  const MAX_ACCOUNTS_LEN: usize = Transfer::MAX_ACCOUNTS_LEN * 2 + CloseAccount::MAX_ACCOUNTS_LEN;
  const MAX_DATA_LEN: usize = Batch::header_data_len(3) + Transfer::DATA_LEN * 2 + CloseAccount::DATA_LEN;

  pub fn process(&mut self) -> ProgramResult {
    let data = self.accounts.escrow.try_borrow_data()?;
    let escrow = Escrow::load(&data)?;

    // Check if the escrow is valid
    let escrow_key = create_program_address(&[b"escrow", self.accounts.maker.key(), &escrow.seed.to_le_bytes(), &escrow.bump], &crate::ID)?;
    if &escrow_key != self.accounts.escrow.key() {
      return Err(ProgramError::InvalidAccountOwner);
    }

    let seed_binding = escrow.seed.to_le_bytes();
    let bump_binding = escrow.bump;
    let escrow_seeds = [
      Seed::from(b"escrow"),
      Seed::from(self.accounts.maker.key().as_ref()),
      Seed::from(&seed_binding),
      Seed::from(&bump_binding),
    ];
    let signer = Signer::from(&escrow_seeds);

    let amount = TokenAccount::from_account_info(self.accounts.vault)?.amount();

    let batch_state = BatchState::new(Self::MAX_ACCOUNTS_LEN, Self::MAX_DATA_LEN);
    let mut batch = batch_state.as_batch()?;

    Transfer::new(
      self.accounts.vault,
      self.accounts.taker_ata_a,
      self.accounts.escrow,
      amount,
    )
    .into_batch(&mut batch)?;

    CloseAccount::new(
      self.accounts.vault,
      self.accounts.maker,
      self.accounts.escrow,
    )
    .into_batch(&mut batch)?;

    Transfer::new(
      self.accounts.taker_ata_b,
      self.accounts.maker_ata_b,
      self.accounts.taker,
      escrow.receive,
    )
    .into_batch(&mut batch)?;

    batch.invoke_signed(&[signer])?;

    // Close the Escrow program account
    drop(data);
    ProgramAccount::close(self.accounts.escrow, self.accounts.taker)?;

    Ok(())
  }
}
Expand
[48 more lines]

請確保 pinocchio-token 至少為 0.6.0 版本,以支援 p-token 的批次操作。

Blueshift © 2026Commit: 3c44267