Take
Die take Anweisung schließt den Tausch ab:
Überträgt Token A vom Tresor zum Nehmer.
Schließt das nun leere Vault-Token-Konto.
Überträgt die vereinbarte Menge von Token B vom Nehmer zum Ersteller.
Schließt das Escrow-Programmkonto.
Required Accounts
Nachfolgend sind die Konten aufgeführt, die der Kontext benötigt:
taker: die Person, die das Angebot annehmen möchte. Muss ein Unterzeichner und veränderbar sein.
maker: der Ersteller des Escrows. Muss veränderbar sein.
escrow: das Escrow-Konto, das wir initialisieren. Muss veränderbar sein.
mint_a: der Token, den wir im Escrow hinterlegen
mint_b: der Token, den wir erhalten möchten
vault: das zugehörige Token-Konto im Besitz des Escrows. Muss veränderbar sein
taker_ata_a: das zugehörige Token-Konto im Besitz des Nehmers für mint_a. Muss veränderbar sein
taker_ata_b: das zugehörige Token-Konto im Besitz des Nehmers für mint_b. Muss veränderbar sein
maker_ata_b: das zugehörige Token-Konto im Besitz des Erstellers für mint_b. Muss veränderbar sein
system_program: das System-Programm. Muss ausführbar sein
token_program: das Token-Programm. Muss ausführbar sein
Und wir führen folgende Prüfungen daran durch:
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,
})
}
}Instruction Data
Alle Daten, die wir zur Durchführung der Logik benötigen, befinden sich bereits im Escrow-Konto oder in den Konten, die wir deserialisieren. Aus diesem Grund benötigen wir keine instruction_data für diese Anweisung.
Instruction Logic
Wir beginnen mit der Initialisierung der erforderlichen Konten in der TryFrom Implementierung, nachdem wir die Konten deserialisiert haben.
Für diesen Schritt stellen wir sicher, dass sowohl das Token-A-Konto des Nehmers als auch das Token-B-Konto des Erstellers mit AssociatedTokenAccount::init_if_needed initialisiert sind, da wir nicht sicher sind, ob diese bereits existieren
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,
})
}
}Wir können uns jetzt auf die eigentliche Logik konzentrieren, die:
Die Token vom
vaultzumtaker_ata_aüberträgt.Das nun leere
vaultToken-Konto schließt und die Miete vom Konto abhebt.Die Token vom
taker_ata_bzummaker_ata_büberträgt.Das
escrowProgrammkonto schließt, sobald der Tausch abgeschlossen ist.
Anstatt den Tausch mit drei separaten Token-CPI-Aufrufen abzuschließen, können wir die Batch-Instruktion in p-token nutzen, um alle Token-Operationen effizient in einem einzigen CPI-Aufruf auszuführen.
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(())
}
}