Rust
Pinocchio Escrow

Pinocchio Escrow

143 Graduates

Забрати

Інструкція take завершує обмін:

  • Переміщує Токен A зі сховища до отримувача.

  • Закриває тепер порожній токен-акаунт сховища.

  • Переміщує узгоджену кількість Токену B від отримувача до творця.

  • Закриває програмний акаунт escrow.

Необхідні облікові записи

Нижче наведені облікові записи, які потрібні для контексту:

  • taker: особа, яка хоче прийняти угоду. Має бути підписантом і змінюваною.

  • maker: творець ескроу. Має бути змінюваним.

  • escrow: обліковий запис ескроу, який ми ініціалізуємо. Має бути змінюваним.

  • mint_a: токен, який ми депонуємо в ескроу

  • mint_b: токен, який ми хочемо отримати

  • vault: пов'язаний обліковий запис токена, що належить ескроу. Має бути змінюваним

  • 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]

Дані інструкції

Усі дані, які нам потрібні для виконання логіки, вже містяться в обліковому записі Escrow або в облікових записах, які ми десеріалізуємо. З цієї причини нам не потрібні жодні instruction_data для цієї інструкції.

Логіка інструкції

Ми починаємо з ініціалізації необхідних облікових записів у реалізації TryFrom, після того як ми десеріалізували облікові записи.

Для цього кроку ми переконуємося, що обидва рахунки — рахунок Токену А покупця та рахунок Токену B продавця — ініціалізовані за допомогою AssociatedTokenAccount::init_if_needed, оскільки ми не впевнені, що вони вже існують

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 після завершення обміну.

Замість трьох окремих токен-CPI-викликів для завершення обміну ми можемо скористатися batch-інструкцією в p-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, щоб підтримувати batch-операції p-token.

Blueshift © 2026Commit: 3c44267