Rozliczenie wymiany

SettleDvp jest podpisywana przez organ rozliczający wskazany w rekordzie wymiany. W jednej transakcji przenosi amount_a nogi aktywa na adres docelowy strony nogi gotówkowej oraz amount_b nogi gotówkowej na adres docelowy strony nogi aktywa, zwraca ewentualną nadwyżkę z każdego escrow stronie danej nogi oraz zamyka rekord i 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 kontynuuje temat z tworzenia wymiany i finansowania nóg; klient, sygnatariusze, trade oraz token accounts stron pozostają bez zmian.

Ponowna weryfikacja przed podpisaniem

Organ rozliczający podpisuje transakcję przenoszącą obie nogi, 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 (zob. finansowanie nóg). Sam program ponownie sprawdza przy rozliczeniu, czy każdy mint nadal należy do token program zapisanego podczas tworzenia, nie ma zablokowanego rozszerzenia i ma tę samą mint authority co w chwili utworzenia wymiany, więc mint utworzony ponownie z zablokowanym rozszerzeniem, innym token program lub inną mint authority nie może zostać rozliczony.

Tworzenie kont odbiorców

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

KontoOtrzymujeWłaściciel
userADestinationAtaBNogę gotówkową (amount_b)user_a_settlement_destination
userBDestinationAtaANogę aktywa (amount_a)user_b_settlement_destination
userAAtaAEwentualną nadwyżkę z nogi aktywauser_a
userBAtaBEwentualną nadwyżkę z nogi 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łe 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 hook nogi aktywa; pozostałe należą do transfer hook nogi gotówkowej. W przypadku mints bez transfer hooks przekaż zero i nic nie dołączaj. Memo program account jest wymagany przez instrukcję i jest używany tylko dla adresów docelowych, które wymagają 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. Rozwiąż dodatkowe account metas każdego mintu z hookiem tak samo jak przy bezpośrednim transferze tego tokena, a następnie dołącz je do instrukcji: najpierw konta nogi aktywa, potem nogi gotówkowej, przy czym legAExtrasCount ustaw na liczbę kont w pierwszej grupie. W TypeScripcie rozwiń rozwiązane metas w tablicy accounts instrukcji; w Ruście użyj add_remaining_accounts w builderze. Limit wynosi 32 konta na nogę, łącznie z programem hooka i kontem walidacyjnym; limit kont w transakcji może zadziałać wcześniej, gdy obie nogi mają hooki. Program usuwa flagę sygnatariusza z każdego przekazywanego konta, więc hook nie może uzyskać podpisu organu rozliczającego tą drogą.

Zapisanie wyniku

Uznaj wymianę za rozliczoną, gdy transakcja Settle osiągnie status finalized. Rekord i oba escrow nie istnieją po rozliczeniu, 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 rozliczającego.

Błędy rozliczenia

LegNotFunded oznacza, że escrow zawiera mniej niż uzgodniona kwota; DvpExpired i SettlementTooEarly oznaczają, że okno czasowe zostało przegapione; MintAuthorityChanged i BlockedMintExtension oznaczają, że mint zmienił się po utworzeniu; RecipientAtaMismatch oznacza, że docelowy lub zwrotny token account nie istnieje albo nie należy już do oczekiwanego właściciela i mintu, zwykle dlatego, że pominięto opisany wyżej krok tworzenia kont. W każdym przypadku nic nie zostało przeniesione, a strony mogą cofnąć wymianę, z zastrzeżeniem uprawnień mintu (zob. cofnięcie wymiany).

Następne kroki

  • Cofnięcie wymiany: odzyskiwanie sfinansowanych nóg, gdy wymiana nie zostaje rozliczona

Is this page helpful?

Spis treści

Edytuj stronę