Programa de entrega contra pago (DvP)

El programa DvP es el programa estándar para la entrega contra pago en Solana, mantenido por la Solana Foundation y auditado por Cantina. Dos partes acuerdan una operación, cada una envía su tramo a una cuenta de escrow con una transferencia de tokens ordinaria, y una autoridad de liquidación liquida ambos tramos en una sola transacción, de modo que o bien se mueven los dos o no se mueve ninguno. La autoridad de liquidación es una tercera dirección indicada en la operación, como el mercado o el agente que la organizó. Ninguna de las partes escribe ni despliega un programa, y una wallet o un custodio que pueda enviar una transferencia de tokens estándar (TransferChecked) puede fondear un tramo sin ninguna integración específica de DvP. Hasta la liquidación, cualquiera de las partes puede retirar su propio tramo o deshacer la operación.

Lee el registro antes de actuar sobre él

Crear un registro de operación no requiere permisos, por lo que un registro no es prueba de acuerdo. Cada parte lee los términos almacenados antes de fondear, y la autoridad de liquidación antes de liquidar (consulta verificación).

Cómo funciona

Cada operación obtiene un registro propiedad del programa y dos cuentas de escrow, una por tramo. La parte del tramo de activo (user_a) y la parte del tramo de efectivo (user_b) fondean cada una un tramo transfiriendo tokens a la cuenta de escrow de ese tramo. La autoridad de liquidación es el único firmante que puede liquidar. La liquidación transfiere el importe acordado de cada tramo a la otra parte, devuelve cualquier excedente a la parte propietaria del tramo y cierra el registro y ambos escrows. La atomicidad proviene de las transacciones de Solana: ambas transferencias están en una sola transacción, así que o bien surten efecto las dos o no lo hace ninguna.

Todo reembolso, recuperación y devolución de excedente va a la associated token account de la propia parte: la de user_a para mint_a y la de user_b para mint_b. Un custodio o un sistema de tesorería puede fondear un tramo desde su propia cuenta, pero todo lo que se devuelva llega a la cuenta de la parte, no de vuelta al custodio. Además, cada operación deja una cuenta marcadora permanente (consulta Estado).

Guías

Las páginas de pasos llevan una operación desde su creación hasta su liquidación, con TypeScript y Rust para cada instrucción.

Una demo en devnet ejecuta una operación de principio a fin: crea la operación, fondea ambos escrows con transferencias de tokens estándar y liquida ambos tramos en una sola transacción.

Cómo elegir entre el programa y la delegación

Entrega contra pago (DvP) mediante delegación liquida el mismo intercambio sin un programa: cada parte delega su posición en un agente de liquidación, que mueve ambos tramos en una sola transacción.

  • El programa no necesita delegación, por lo que no se aplica el límite de un delegado por token account, y la autoridad de liquidación no puede redirigir los fondos. Cada operación depende de un programa actualizable y de su autoridad de actualización.
  • El patrón de delegación no depende de ningún programa y funciona con mints que incluyen Scaled UI Amount. El programa rechaza esos mints, lo que excluye cualquier mint creado a partir de la plantilla tokenized-security de Mosaic (consulta restricciones de configuración del mint).

Roles

El programa es simétrico. En su interfaz, las dos partes son user_a, que entrega mint_a, y user_b, que entrega mint_b. El README y los clientes generados llaman a mint_a el tramo de activo y a mint_b el tramo de efectivo, y estas páginas siguen esa convención, pero nada en el programa exige que el tramo de efectivo sea un instrumento de efectivo. Dos activos, o dos stablecoins, se liquidan de la misma manera.

RolQuiénFirmaNotas
Parte del tramo de activouser_a, entrega mint_aSu propia transferencia de fondeo; Reclamar, Rechazar, RescatarUna wallet con keypair, o una smart wallet basada en PDA, como una bóveda multisig. Una cuenta multisig de SPL Token o una program account no puede ser parte: Crear falla con PartyNotSignerCapable
Parte del tramo de efectivouser_b, entrega mint_bSu propia transferencia de fondeo; Reclamar, Rechazar, RescatarEl mismo requisito
Autoridad de liquidaciónUna tercera dirección indicada en la creaciónLiquidar, CancelarEl único firmante que puede liquidar. No puede redirigir los fondos, que quedan fijados en la creación. Recibe el rent de las cuentas cerradas cuando liquida o cancela
Pagador de rentQuien firme CreateDvpCrearAporta el rent (el depósito en SOL que mantiene abierta una cuenta) para el registro, el tombstone del nonce y ambos escrows. No se registra como destinatario de rent: el rent de las cuentas cerradas va a quien cierre la operación

ID del programa

dvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq
ClústerProgramaAutoridad de actualizaciónÚltimo slot desplegado
mainnet-betadvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZqDXtFpbPjcn2hxPnw79x1Pfoj35vXh5AsWBkS37YnXMVv452.058.374
devnetdvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq5EX1zFeJWj4rGYZv432WnYw8CbaSUeTdxPx4r573UgYU479.376.863

El programa es actualizable en ambos clústeres. Las cifras anteriores son las que indicó solana program show el 2 de octubre de 2026; ejecuta el mismo comando para ver los valores actuales. La compilación de devnet es anterior al commit con el que están escritas las páginas de pasos (consulta Instalación), con el mismo conjunto de instrucciones y errores.

Ciclo de vida

Una operación está abierta desde CreateDvp hasta que Liquidar, Cancelar o Rechazar la cierra. El fondeo no tiene una instrucción propia: un tramo está fondeado cuando su cuenta de escrow contiene al menos el importe acordado. Reclamar actúa por tramo y deja la operación abierta, de modo que un problema con un tramo no obliga a la otra parte a cancelar toda la operación.

Estado

Cada operación tiene una cuenta SwapDvp de 458 bytes, propiedad del programa. Su dirección se deriva de la autoridad de liquidación, las dos partes, los dos mints y un nonce (las seeds ["dvp", settlement_authority, user_a, user_b, mint_a, mint_b, nonce]). Los importes y los plazos se almacenan dentro de la cuenta y no afectan a la dirección, por lo que hay que leerlos en lugar de inferirlos. Una pequeña cuenta marcadora, el tombstone del nonce en ["nonce", swap_dvp], se crea junto con la operación y nunca se cierra, así que cada combinación de partes, mints, autoridad y nonce solo puede usarse para una operación.

CampoTipoSignificado
bumpu8Bump del PDA
user_a, user_bPubkeyLas dos partes
mint_a, mint_bPubkeyLos mints del tramo de activo y del tramo de efectivo
settlement_authorityPubkeyEl único firmante que puede liquidar o cancelar
token_program_a, token_program_bPubkeyEl Token Program propietario de cada mint; una operación puede combinar tramos de SPL Token y de Token-2022
amount_a, amount_bu64El importe acordado de cada tramo, en la unidad más pequeña del mint (unidades base)
expiry_timestampi64Pasada esta hora de la red (el reloj onchain), Liquidar se rechaza. Reclamar, Cancelar y Rechazar siguen funcionando
nonceu64Distingue operaciones que comparten las demás seeds. De un solo uso
ref_string[u8; 64]Una referencia opaca del cliente, rellenada con ceros; el programa nunca la lee
user_a_settlement_destinationPubkeyRecibe el tramo de efectivo en Liquidar; por defecto es user_a
user_b_settlement_destinationPubkeyRecibe el tramo de activo en Liquidar; por defecto es user_b
mint_a_authority, mint_b_authorityPubkeyLas autoridades de los mints registradas en la creación; Liquidar falla si alguna ha cambiado
earliest_settlement_timestampOption<i64>Si se define, Liquidar se rechaza antes de esta hora de la red

La lista de campos se toma del IDL del programa en el commit indicado en Crear una operación.

Instrucciones

InstrucciónFirmanteEfecto
CreateDvpCualquier pagadorCrea el registro SwapDvp, su tombstone del nonce y ambas cuentas de escrow. No mueve tokens
ReclaimDvpuser_a o user_bDevuelve el propio tramo del firmante desde el escrow. La operación sigue abierta y el tramo puede volver a fondearse
SettleDvpAutoridad de liquidaciónTransfiere amount_a al destino de user_b y amount_b al destino de user_a, devuelve cualquier excedente a la parte del tramo y cierra el registro y ambos escrows
CancelDvpAutoridad de liquidaciónReembolsa a user_a lo que contenga el escrow A y a user_b lo que contenga el escrow B, y cierra el registro y ambos escrows
RejectDvpuser_a o user_bMismo efecto que Cancelar, firmado por una de las partes
RecoverDvpuser_a o user_bUna vez cerrada la operación: vacía un escrow recreado hacia la token account del propio firmante y lo cierra. Cubre un depósito que llegó después de Liquidar, Cancelar o Rechazar

Todo reembolso va a la associated token account de la propia parte para el mint de ese tramo, sin importar quién fondeó el escrow.

Las cuentas y los argumentos de cada instrucción están en la página de ese paso.

Errores

CódigoErrorCuándo
0SignerNotPartyReclamar, Rechazar o Rescatar firmado por una dirección que no es user_a ni user_b
1DvpExpiredLiquidar después de expiry_timestamp
2SettlementAuthorityMismatchLiquidar o Cancelar firmado por una dirección distinta de la autoridad de liquidación
3SettlementTooEarlyLiquidar antes de earliest_settlement_timestamp
4LegNotFundedLiquidar mientras un escrow contiene menos que su importe acordado
5ExpiryNotInFutureCrear con un vencimiento igual o anterior a la hora actual de la red
6EarliestAfterExpiryCrear con una hora mínima de liquidación posterior al vencimiento
7SelfDvpCrear con user_a igual a user_b
8SameMintCrear con mint_a igual a mint_b
9ZeroAmountCrear con un importe cero en cualquiera de los tramos
10BlockedMintExtensionCrear o Liquidar con un mint que incluye TransferFee, InterestBearing, ScaledUiAmount o NonTransferable
11SettlementAuthorityIsPartyCrear con la autoridad de liquidación igual a una de las partes
12SettlementAuthorityExecutableCrear con una autoridad de liquidación ejecutable
13NonceAlreadyUsedCrear reutilizando un (seeds, nonce) que ya tiene un tombstone
14ExpiryTooFarInFutureCrear con un vencimiento a más de un año vista
15RefStringTooLongCrear con una referencia de más de 64 bytes
16DvpStillOpenRescatar mientras la operación sigue abierta; usa Reclamar
17DvpNeverCreatedRecover con seeds que no tienen tombstone
18EscrowPreloadedWithLamportsCreate cuando un escrow no nativo contiene lamports por encima de su mínimo exento de rent
19SettlementDestinationIsSwapDvpCreate con un destino de liquidación igual al registro de la operación
20SwapDvpPreloadedWithLamportsCreate cuando la dirección del registro contiene lamports por encima de su reserva de rent
21RecipientAtaMismatchUna token account del destinatario o de reembolso ya no pertenece a la wallet y al mint esperados
22PartyNotSignerCapableCreate con una parte que no es una cuenta propiedad del sistema y no ejecutable
23MintAuthorityChangedSettle cuando la mint authority de un tramo difiere del valor capturado en la creación

Eventos e indexación

El programa no emite eventos on-chain. Un indexador o un sistema de conciliación lee el estado de la cuenta SwapDvp y los saldos de tokens de los dos escrows, y usa ref_string para correlacionar una operación con su orden off-chain. Dado que el registro se cierra en la liquidación, un sistema que necesite los términos a posteriori los almacena cuando los lee, o los reconstruye a partir de la transacción que creó la operación.

Tokens compatibles

Una operación puede combinar un tramo de SPL Token con un tramo de Token-2022; cada tramo lleva su propio token program. Los tramos de SOL envuelto (wrapped SOL) son compatibles.

Extensión de Token-2022 en el mint de un tramoComportamiento del programa
TransferFee, InterestBearing, ScaledUiAmount, NonTransferableRechazado en Create y Settle con BlockedMintExtension
PermanentDelegate, Pausable, DefaultAccountState, una freeze authority, MintCloseAuthorityAceptado; cada autoridad puede actuar sobre los tokens en escrow
ConfidentialTransferAceptado; los importes de ese tramo son públicos
TransferHookCompatible, hasta 32 cuentas adicionales por tramo
MemoTransfer en una cuenta de destino o de reembolsoAceptado; el programa emite el memo requerido antes de la transferencia

Una comisión de transferencia dejaría al lado receptor por debajo del importe acordado. Interest Bearing y Scaled UI Amount no modifican los importes en bruto, pero su tasa o multiplicador puede cambiar entre la creación y la liquidación de una operación, lo que modifica cómo se muestran los importes acordados. NonTransferable dejaría bloqueados ambos tramos.

Las autoridades aceptadas pueden mover, congelar o detener los tokens en escrow durante toda la vida de la operación (véase más abajo). El escrow no puede configurarse para recibir transferencias confidenciales, por lo que los importes en escrow y liquidados en un tramo confidencial son públicos.

En un mint con hook, el cliente resuelve las cuentas adicionales del hook y las añade a Settle, Cancel, Reject, Reclaim y Recover. El límite de 32 por tramo incluye el programa del hook y su cuenta de validación. Si la hook authority amplía la lista de cuentas del hook por encima de ese límite, todas las transferencias de ese tramo fallan, incluidas Reclaim, Cancel, Reject y Recover, hasta que la autoridad lo revierta. El límite de cuentas de la transacción puede alcanzarse antes que el límite por tramo cuando ambos tramos llevan hooks.

Cuando una cuenta de destino o de reembolso exige memos, el programa llama al programa Memo antes de la transferencia, por lo que toda instrucción que mueve tokens fuera del escrow requiere una cuenta memo_program.

Restricciones de configuración del mint

Scaled UI Amount. La plantilla tokenized-security de Mosaic, el kit de herramientas de emisión de la Solana Foundation, siempre añade Scaled UI Amount, por lo que el programa no acepta como tramo un mint creado a partir de esa plantilla. Un mint destinado a este programa puede crearse sin la extensión (véase Token Extensions); DvP mediante delegación liquida a través de una autoridad delegada y no aplica esta comprobación.

Mints congelados por defecto. El escrow de cada operación es una associated token account propiedad de una Program Derived Address que es nueva para esa operación. En un mint con Default Account State configurado como congelado, el escrow se crea congelado, y una transferencia de fondeo hacia él falla hasta que se descongela. Con Token ACL, eso significa que el emisor o su agente de transferencias admite el escrow de la operación antes del fondeo: ya sea añadiendo la dirección del registro de la operación (el propietario del escrow) a la lista de permitidos, de modo que cualquiera pueda descongelar el escrow cuando la configuración de Token ACL del mint habilite la descongelación sin permisos, o bien haciendo que la freeze authority lo descongele directamente. Un escrow congelado también bloquea Settle, Reclaim, Cancel, Reject y Recover en ese tramo, por lo que la freeze authority, y en un mint con hook la hook authority, es una parte de confianza durante toda la vida de la operación. La vía del escrow congelado se describe aquí y no se ejercita en los fragmentos de código de devnet.

Límites

  • Sin emparejamiento, descubrimiento de precios ni libro de órdenes. Las partes acuerdan los términos fuera de la cadena y una de ellas los registra.
  • Sin compensación (netting) y solo bilateral: un registro es una operación entre dos partes.
  • Sin ejecuciones parciales. Settle mueve exactamente el importe acordado de cada tramo y devuelve cualquier excedente a la parte del tramo.
  • Sin tramo fuera de la cadena. Ambos tramos son token accounts en Solana; un tramo en efectivo sobre infraestructuras existentes se liquida por separado y se concilia.
  • Sin comprobación de elegibilidad, lista de permitidos ni KYC. Los controles propios del mint aplican la elegibilidad de los titulares.
  • Sin liquidación automática. La autoridad de liquidación tiene que firmar.
  • Sin eventos y sin comisión de protocolo más allá de las comisiones de transacción y el rent.

El programa elimina el riesgo de principal entre las dos partes: ningún tramo se mueve a menos que se muevan ambos. No elimina el riesgo de crédito del emisor ni de reembolso en ninguno de los tokens, la capacidad de la freeze authority, la pause authority o la permanent delegate authority de un mint para actuar sobre los tokens en escrow (véase tokens compatibles), ni la dependencia de que la autoridad de liquidación esté disponible para firmar.

Verificación antes del fondeo y la liquidación

CreateDvp no requiere permisos: solo firma el pagador, y las partes y la autoridad de liquidación no. Por lo tanto, cualquiera puede crear un registro que nombre a cualquier parte y cualquier término. Antes de fondear, y de nuevo antes de liquidar, lee la cuenta SwapDvp y confirma:

  1. La cuenta es propiedad del programa DvP y tiene exactamente 458 bytes. Una cuenta que no es propiedad del programa puede rellenarse con bytes que se decodifiquen como un registro real.
  2. user_a, user_b, mint_a, mint_b y settlement_authority coinciden con la operación acordada.
  3. amount_a, amount_b, expiry_timestamp y earliest_settlement_timestamp coinciden con los términos acordados.
  4. Ambos destinos de liquidación son las direcciones acordadas. Un registro falsificado puede redirigir los fondos.

Fondear los tramos enumera los lectores con comprobación de los clientes generados y muestra la verificación en código.

Considera una operación como liquidada cuando la transacción Settle alcanza finalized. Ese es un nivel de compromiso de la red; que además marque la firmeza de la liquidación a efectos legales depende de los acuerdos de las partes y de los regímenes aplicables.

Versionado

La cuenta SwapDvp no incluye ningún campo de versión. Su estructura está fijada en 458 bytes (SwapDvp::LEN), y los lectores con comprobación rechazan una cuenta de cualquier otra longitud. El IDL lleva su propia versión, 0.1.0 a fecha de 2 de octubre de 2026.

El programa es actualizable en ambos clústeres (véase Program ID). Un registro de operación existe solo hasta que se liquida, se cancela o se rechaza, así que consulta el repositorio para ver cómo trata una actualización las operaciones abiertas, y regenera los clientes a partir del IDL de la compilación desplegada.

Repositorio y auditoría

El código fuente, el IDL, los clientes generados y las pruebas están en solana-foundation/dvp bajo la licencia MIT. El programa es un programa Pinocchio no_std; los clientes se generan a partir del IDL con Codama. Los informes de vulnerabilidades se envían mediante el enlace de aviso de seguridad del repositorio, conforme a su SECURITY.md.

El programa ha sido auditado por Cantina.

Relacionado

Is this page helpful?