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:
| Conta | Recebe | Proprietário |
|---|---|---|
userADestinationAtaB | A perna monetária (amount_b) | user_a_settlement_destination |
userBDestinationAtaA | A perna do ativo (amount_a) | user_b_settlement_destination |
userAAtaA | Qualquer excedente da perna do ativo | user_a |
userBAtaB | Qualquer excedente da perna monetária | user_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 userAconst destinationB = t.userBSettlementDestination; // defaults to userBconst [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
- Reverter uma negociação: recuperar as pernas financiadas quando uma negociação não é liquidada
Is this page helpful?