Rozliczenie transakcji

SettleDvp podpisuje organ rozliczeniowy wskazany w rekordzie transakcji. W jednej transakcji przenosi amount_a z części aktywa na adres docelowy strony części gotówkowej oraz amount_b z części gotówkowej na adres docelowy strony części aktywa, zwraca ewentualną nadwyżkę z każdego escrow stronie danej części i zamyka rekord oraz oba escrow. Jeśli któryś z tych transferów nie może zostać zrealizowany, instrukcja kończy się niepowodzeniem i nic nie zostaje przeniesione.

Ta strona jest kontynuacją artykułów tworzenie transakcji i finansowanie części transakcji; klient, podpisujący, trade oraz token accounts stron są przenoszone dalej.

Ponowna weryfikacja przed podpisaniem

Organ rozliczeniowy podpisuje transakcję przenoszącą obie części, dlatego przed podpisaniem wykonuje ten sam odczyt co każda ze stron: właściciel, rozmiar, strony, mints, kwoty, termin ważności i oba adresy docelowe (zobacz finansowanie części transakcji). Sam program ponownie sprawdza podczas rozliczenia, czy każdy mint nadal należy do token program zapisanego przy tworzeniu, nie ma zablokowanego rozszerzenia i ma taki sam mint authority jak w chwili utworzenia transakcji. Dlatego mint odtworzony z zablokowanym rozszerzeniem, innym token program lub innym mint authority nie może zostać rozliczony.

Tworzenie kont odbiorców

Settle zapisuje dane w czterech token accounts, których program nie tworzy:

KontoOtrzymujeWłaściciel
userADestinationAtaBCzęść gotówkowa (amount_b)user_a_settlement_destination
userBDestinationAtaACzęść aktywa (amount_a)user_b_settlement_destination
userAAtaAEwentualna nadwyżka w części aktywauser_a
userBAtaBEwentualna nadwyżka w części gotówkowejuser_b

Konta nadwyżek są używane tylko wtedy, gdy escrow zawiera więcej niż uzgodniona kwota, ale każdy, kto posiada dany mint, może wywołać nadwyżkę, wysyłając do escrow kilka jednostek bazowych. Jeśli konto zwrotu nie istnieje, cała instrukcja Settle kończy się niepowodzeniem, dlatego utwórz wszystkie cztery idempotentnie w tej samej transakcji.

import {
findAssociatedTokenPda,
getCreateAssociatedTokenIdempotentInstruction,
} from "@solana-program/token-2022";
const destinationA = t.userASettlementDestination; // defaults to userA
const destinationB = t.userBSettlementDestination; // defaults to userB
const [userADestinationAtaB] = await findAssociatedTokenPda({
mint: mintB,
owner: destinationA,
tokenProgram: tokenProgramB,
});
const [userBDestinationAtaA] = await findAssociatedTokenPda({
mint: mintA,
owner: destinationB,
tokenProgram: tokenProgramA,
});
// userAAtaA and userBAtaB are from fund the legs.
const createRecipientAccounts = [
{ ata: userADestinationAtaB, owner: destinationA, mint: mintB, tokenProgram: tokenProgramB },
{ ata: userBDestinationAtaA, owner: destinationB, mint: mintA, tokenProgram: tokenProgramA },
{ ata: userAAtaA, owner: userA, mint: mintA, tokenProgram: tokenProgramA },
{ ata: userBAtaB, owner: userB, mint: mintB, tokenProgram: tokenProgramB },
].map(({ ata, owner, mint, tokenProgram }) =>
getCreateAssociatedTokenIdempotentInstruction({
payer: authoritySigner,
ata,
owner,
mint,
tokenProgram,
}),
);

Rozliczenie

legAExtrasCount informuje program, ile spośród kont dołączonych po stałej liście należy do transfer hooka części aktywa; pozostałe należą do części gotówkowej. W przypadku mints bez transfer hooków przekaż zero i niczego nie dołączaj. Memo program account jest wymagany przez instrukcję i jest używany tylko dla adresów docelowych wymagających memo.

import { MEMO_PROGRAM_ADDRESS } from "@solana-program/memo";
import { getSettleDvpInstruction } from "./dvp";
const settleInstruction = getSettleDvpInstruction({
settlementAuthority: authoritySigner,
swapDvp,
mintA,
mintB,
dvpAtaA: escrowA,
dvpAtaB: escrowB,
userADestinationAtaB,
userBDestinationAtaA,
userAAtaA,
userBAtaB,
tokenProgramA,
tokenProgramB,
memoProgram: MEMO_PROGRAM_ADDRESS,
legAExtrasCount: 0,
});
const { context: settleContext } = await client.sendTransaction([
...createRecipientAccounts,
settleInstruction,
]);
const settleSignature = settleContext.signature;
console.log("Submitted:", settleSignature);
// sendTransaction returns at the client's commitment (confirmed by default).
// Wait for finalized before recording the trade as settled.
let finalized = false;
for (let attempt = 0; attempt < 30 && !finalized; attempt++) {
const {
value: [status],
} = await client.rpc.getSignatureStatuses([settleSignature]).send();
if (status?.err) {
throw new Error("Settle transaction failed");
}
finalized = status?.confirmationStatus === "finalized";
if (!finalized) {
await new Promise((resolve) => setTimeout(resolve, 2_000));
}
}
if (!finalized) {
// A closed record means it settled; an open one is safe to settle again.
throw new Error("Settle not finalized after 60s; read the trade record");
}
console.log("Settled:", settleSignature);

Transfer hooks

Wygenerowany IDL wymienia tylko stałe konta, więc wygenerowane buildery nie znają kont hooków. Ustal dodatkowe account metas każdego mintu z hookiem tak samo, jak w przypadku bezpośredniego transferu tego tokena, a następnie dołącz je do instrukcji: najpierw konta części aktywa, potem części gotówkowej, z wartością legAExtrasCount równą liczbie kont w pierwszej grupie. W TypeScript rozwiń ustalone metas w tablicy accounts instrukcji; w Rust użyj add_remaining_accounts na builderze. Limit wynosi 32 konta na część, wliczając program hooka i konto walidacyjne; limit kont w transakcji może zadziałać wcześniej, gdy obie części mają hooki. Program usuwa flagę signera z każdego przekazywanego konta, więc hook nie może w ten sposób uzyskać podpisu organu rozliczeniowego.

Zapisanie wyniku

Uznaj transakcję za rozliczoną, gdy transakcja Settle osiągnie status finalized. Po rozliczeniu rekord i oba escrow już nie istnieją, więc system, który później potrzebuje warunków, zapisuje je z odczytu sprzed rozliczenia lub z transakcji tworzącej. Rent zamkniętych kont trafił do organu rozliczeniowego.

Błędy rozliczenia

LegNotFunded oznacza, że escrow zawiera mniej niż uzgodniona kwota; DvpExpired i SettlementTooEarly oznaczają, że termin został pominięty; MintAuthorityChanged i BlockedMintExtension oznaczają, że mint zmienił się po utworzeniu; RecipientAtaMismatch oznacza, że token account adresu docelowego lub zwrotu nie istnieje albo nie należy już do oczekiwanego właściciela i mintu, zwykle dlatego, że pominięto powyższy krok tworzenia kont. W każdym z tych przypadków nic nie zostało przeniesione, a strony mogą wycofać transakcję, z zastrzeżeniem uprawnień mintu (zobacz wycofanie transakcji).

Dalsze kroki

Is this page helpful?

Spis treści

Edytuj stronę