Rust
Escrow avec Pinocchio

Escrow avec Pinocchio

143 Graduates

Take

L'instruction take finalise l'échange :

  • Transfère le Jeton A du vault au preneur.

  • Ferme le compte de jetons du vault désormais vide.

  • Transfère le montant convenu du Jeton B du preneur au créateur.

  • Ferme le compte de programme escrow.

Comptes Nécessaires

Voici les comptes dont le contexte a besoin :

  • taker: la personne qui veut accepter l'offre. Doit être un signataire et mutable

  • maker: le créateur de l'escrow. Doit être mutable.

  • escrow: le compte d'escrow que nous initialisons. Doit être mutable

  • mint_a: le jeton que nous déposons dans l'escrow

  • mint_b: le jeton que nous souhaitons recevoir

  • vault: le compte de jetons associé appartenant à l'escrow. Doit être mutable

  • taker_ata_a: le compte de jetons associé appartenant au preneur pour le mint_a. Doit être mutable

  • taker_ata_b: le compte de jetons associé appartenant au preneur pour le mint_b. Doit être mutable

  • maker_ata_b: le compte de jetons associé appartenant au créateur pour le mint_b. Doit être mutable

  • system_program: le programme système. Doit être executable

  • token_program: le programme de jeton. Doit être executable

Et nous effectuons ces vérifications :

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]

Données d'Instruction

Toutes les données dont nous avons besoin pour exécuter la logique se trouvent déjà dans le compte Escrow ou dans les comptes que nous désérialisons. C'est pourquoi nous n'avons pas besoin de instruction_data pour cette instruction.

Logique d'Instruction

Tout d'abord, après avoir désérialisé les accounts dans l'implémentation du TryFrom, nous initialisons les comptes nécessaires.

Pour cette étape, nous nous assurons que le compte de Jeton A du preneur et que le compte de Jeton B du créateur sont initialisés en utilisant AssociatedTokenAccount::init_if_needed puisque nous ne sommes pas sûrs qu'ils existent déjà.

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]

Nous pouvons maintenant nous concentrer sur la logique elle-même :

  • Transférer les jetons du vault vers taker_ata_a.

  • Fermer le compte de jetons vault désormais vide et récupérer sa rente.

  • Transférer les jetons de taker_ata_b vers maker_ata_b.

  • Fermer le compte de programme escrow une fois l'échange terminé.

Plutôt que d'effectuer trois appels CPI de jetons séparés pour finaliser l'échange, nous pouvons tirer parti de l'instruction batch de p-token afin d'exécuter efficacement toutes les opérations sur jetons en un seul appel 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]

Assurez-vous que pinocchio-token est au moins en version 0.6.0 pour prendre en charge les opérations batch de p-token.

Blueshift © 2026Commit: 3c44267