SettleDvp lo firma la autoridad de liquidación indicada en el registro de la operación. En
una sola transacción mueve amount_a del tramo de activo al destino de la parte
del tramo de efectivo y amount_b del tramo de efectivo al destino de la parte del tramo de activo,
devuelve cualquier excedente de cualquiera de los escrows a la parte de ese tramo, y cierra el registro
y ambos escrows. Si alguna de esas transferencias no puede completarse, la instrucción
falla y no se mueve nada.
Esta página continúa desde crear una operación
y financiar los tramos; el cliente, los firmantes,
trade y las token accounts de las partes se mantienen.
Revalidar antes de firmar
La autoridad de liquidación firma la transacción que mueve ambos tramos, por lo que realiza la misma lectura que cada parte antes de firmar: propietario, tamaño, partes, mints, importes, vencimiento y ambos destinos (consulta financiar los tramos). El programa, por su parte, vuelve a comprobar en la liquidación que cada mint sigue perteneciendo al token program registrado en la creación, que no tiene ninguna extensión bloqueada y que tiene la misma autoridad de mint que cuando se creó la operación; así, un mint recreado con una extensión bloqueada, un token program distinto o una autoridad de mint diferente no puede liquidarse.
Crear las cuentas de destino
Settle escribe en cuatro token accounts que el programa no crea:
| Cuenta | Recibe | Propietario |
|---|---|---|
userADestinationAtaB | El tramo de efectivo (amount_b) | user_a_settlement_destination |
userBDestinationAtaA | El tramo de activo (amount_a) | user_b_settlement_destination |
userAAtaA | Cualquier excedente del tramo de activo | user_a |
userBAtaB | Cualquier excedente del tramo de efectivo | user_b |
Las cuentas de excedente solo se usan cuando un escrow contiene más que el importe acordado, pero cualquiera que tenga el mint puede crear un excedente enviando unas pocas unidades base al escrow. Si la cuenta de reembolso no existe, toda la instrucción Settle falla, así que crea las cuatro de forma idempotente en la misma transacción.
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 al programa cuántas de las cuentas añadidas después de la
lista fija pertenecen al transfer hook del tramo de activo; el resto corresponden al del tramo
de efectivo. Para mints sin transfer hooks, pasa cero y no añadas nada. La program account
del programa memo es obligatoria para la instrucción y solo se usa para destinos
que requieren un 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
El IDL generado solo enumera las cuentas fijas, por lo que los builders generados
no conocen las cuentas de los hooks. Resuelve las extra account metas de cada mint con hook del
mismo modo que lo harías para una transferencia directa de ese token y luego añádelas a la
instrucción: primero las cuentas del tramo de activo y después las del tramo de efectivo, con
legAExtrasCount establecido en el número del primer grupo. En TypeScript, expande
las metas resueltas en el array accounts de la instrucción; en Rust, usa
add_remaining_accounts en el builder. El límite es de 32 cuentas por tramo,
incluidos el programa del hook y la cuenta de validación; el límite de cuentas de la transacción
puede alcanzarse antes cuando ambos tramos llevan hooks. El programa elimina el indicador de firmante
de todas las cuentas reenviadas, de modo que un hook no puede obtener la firma de la
autoridad de liquidación por esta vía.
Registrar el resultado
Considera la operación liquidada cuando la transacción Settle alcance finalized. El
registro y ambos escrows dejan de existir tras la liquidación, por lo que un sistema que necesite
las condiciones más adelante debe guardarlas a partir de su lectura previa a la liquidación o de la
transacción de creación. La rent de las cuentas cerradas ha pasado a la autoridad de
liquidación.
Errores de Settle
LegNotFunded significa que un escrow contiene menos que su importe acordado; DvpExpired
y SettlementTooEarly significan que se perdió la ventana de tiempo; MintAuthorityChanged
y BlockedMintExtension significan que un mint cambió después de la creación;
RecipientAtaMismatch significa que falta una token account de destino o de reembolso, o que
ya no pertenece al propietario y al mint esperados, normalmente porque se omitió el paso
de creación de cuentas anterior. En todos los casos no se ha movido nada, y las
partes pueden deshacer la operación, sujetas a las autoridades del mint (consulta
deshacer una operación).
Próximos pasos
- Deshacer una operación: recuperar los tramos financiados cuando una operación no se liquida
Is this page helpful?