Liquidar a negociação

SettleDvp é assinada pela autoridade de liquidação indicada no registo da negociação. Numa só transação, move amount_a da perna do ativo para o destino da parte da perna monetária e amount_b da perna monetária para o destino da parte da perna do ativo, devolve qualquer excedente de cada escrow à parte correspondente da perna e encerra o registo e ambos os escrows. Se alguma dessas transferências não puder ser concluída, a instrução falha e nada é movimentado.

Esta página dá continuidade a criar uma negociação e financiar as pernas; o cliente, os signatários, trade e as token accounts das partes são reaproveitados.

Revalidar antes de assinar

A autoridade de liquidação assina a transação que movimenta as duas pernas, por isso realiza a mesma leitura que cada parte antes de assinar: proprietário, tamanho, partes, mints, montantes, prazo de validade e os dois destinos (ver financiar as pernas). O programa volta a verificar, no momento da liquidação, que cada mint continua a pertencer ao token program registado na criação, não tem nenhuma extensão bloqueada e mantém a mesma mint authority de quando a negociação foi criada; assim, um mint recriado com uma extensão bloqueada, um token program diferente ou uma mint authority diferente não pode ser liquidado.

Criar as contas de destino

A liquidação escreve em quatro token accounts que o programa não cria:

ContaRecebeProprietário
userADestinationAtaBA perna monetária (amount_b)user_a_settlement_destination
userBDestinationAtaAA perna do ativo (amount_a)user_b_settlement_destination
userAAtaAQualquer excedente da perna do ativouser_a
userBAtaBQualquer excedente da perna monetáriauser_b

As contas de excedente só são usadas quando um escrow contém mais do que o montante acordado, mas qualquer pessoa que possua o mint pode criar um excedente enviando algumas unidades base para o escrow. Se a conta de reembolso não existir, toda a liquidação falha, pelo que deve criar as quatro de forma idempotente na mesma transação.

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,
}),
);

Liquidar

legAExtrasCount indica ao programa quantas das contas acrescentadas após a lista fixa pertencem ao transfer hook da perna do ativo; as restantes pertencem ao da perna monetária. Para mints sem transfer hooks, passe zero e não acrescente nada. A program account do programa memo é exigida pela instrução e só é usada para destinos que exigem um 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

O IDL gerado lista apenas as contas fixas, por isso os builders gerados não conhecem as contas dos hooks. Resolva as extra account metas de cada mint com hook da mesma forma que o faria numa transferência direta desse token e, em seguida, acrescente-as à instrução: primeiro as contas da perna do ativo e depois as da perna monetária, com legAExtrasCount definido com o número do primeiro grupo. Em TypeScript, espalhe (spread) as metas resolvidas no array accounts da instrução; em Rust, use add_remaining_accounts no builder. O limite é de 32 contas por perna, incluindo o programa do hook e a conta de validação; o limite de contas da transação pode ser atingido primeiro quando ambas as pernas têm hooks. O programa remove a flag de signatário de todas as contas reencaminhadas, pelo que um hook não consegue obter a assinatura da autoridade de liquidação por este caminho.

Registar o resultado

Considere a negociação liquidada quando a transação de liquidação atingir finalized. O registo e ambos os escrows deixam de existir após a liquidação, por isso um sistema que precise dos termos mais tarde deve guardá-los a partir da leitura anterior à liquidação ou da transação de criação. A rent das contas encerradas foi para a autoridade de liquidação.

Erros de liquidação

LegNotFunded significa que um escrow contém menos do que o montante acordado; DvpExpired e SettlementTooEarly significam que a janela de tempo foi perdida; MintAuthorityChanged e BlockedMintExtension significam que um mint foi alterado após a criação; RecipientAtaMismatch significa que uma token account de destino ou de reembolso não existe ou já não pertence ao proprietário e ao mint esperados, normalmente porque o passo de criação de contas acima foi ignorado. Em todos os casos nada foi movimentado e as partes podem reverter, sujeitas às autoridades do mint (ver reverter uma negociação).

Próximos passos

Is this page helpful?

Índice

Editar Página
© 2026 Fundação Solana. Todos os direitos reservados.