Programme de livraison contre paiement (DvP)

Le programme DvP est le programme standard de livraison contre paiement sur Solana, maintenu par la Solana Foundation et audité par Cantina. Deux parties se mettent d'accord sur un échange, chacune envoie sa jambe vers un compte escrow par un simple transfert de tokens, et une autorité de règlement règle les deux jambes dans une seule transaction, de sorte que soit les deux s'exécutent, soit aucune. L'autorité de règlement est une troisième adresse désignée dans l'échange, comme la plateforme ou l'agent qui l'a organisé. Aucune des parties n'écrit ni ne déploie de programme, et un portefeuille ou un custodian capable d'envoyer un transfert de token standard (TransferChecked) peut financer une jambe sans intégration spécifique à DvP. Jusqu'au règlement, chaque partie peut récupérer sa propre jambe ou défaire l'échange.

Lisez l'enregistrement avant d'agir

La création d'un enregistrement d'échange est sans permission : un enregistrement ne prouve donc pas qu'il y a accord. Chaque partie lit les conditions stockées avant de financer, et l'autorité de règlement avant de régler (voir vérification).

Fonctionnement

Chaque échange dispose d'un enregistrement détenu par le programme et de deux comptes escrow, un par jambe. La partie de la jambe actif (user_a) et la partie de la jambe cash (user_b) financent chacune une jambe en transférant des tokens vers le compte escrow de cette jambe. L'autorité de règlement est le seul signataire pouvant régler. Le règlement transfère le montant convenu de chaque jambe à l'autre côté, renvoie tout surplus à la partie propriétaire de la jambe, et ferme l'enregistrement ainsi que les deux escrows. L'atomicité découle des transactions Solana : les deux transferts sont dans une seule transaction, donc soit les deux prennent effet, soit aucun.

Chaque remboursement, récupération et retour de surplus va vers l'associated token account de la partie : celui de user_a pour mint_a, celui de user_b pour mint_b. Un custodian ou un système de trésorerie peut financer une jambe depuis son propre compte, mais tout ce qui est restitué arrive sur le compte de la partie, et non chez le custodian. Chaque échange laisse aussi un compte marqueur permanent (voir État).

Guides

Les pages d'étapes conduisent un échange de sa création à son règlement, avec TypeScript et Rust pour chaque instruction.

Une démo devnet exécute un échange de bout en bout : elle crée l'échange, finance les deux escrows par des transferts de tokens standard, et règle les deux jambes dans une seule transaction.

Choisir entre le programme et la délégation

Livraison contre paiement (DvP) par délégation règle le même échange sans programme : chaque côté délègue sa position à un agent de règlement, qui déplace les deux jambes dans une seule transaction.

  • Le programme ne nécessite aucune délégation : la limite d'un seul délégué par token account ne s'applique donc pas, et l'autorité de règlement ne peut pas rediriger les produits. Chaque échange dépend d'un programme évolutif et de son autorité de mise à niveau.
  • Le modèle par délégation n'a aucune dépendance à un programme et fonctionne avec les mints qui portent Scaled UI Amount. Le programme rejette ces mints, ce qui exclut tout mint créé à partir du modèle tokenized-security de Mosaic (voir contraintes de configuration du mint).

Rôles

Le programme est symétrique. Dans son interface, les deux parties sont user_a, qui livre mint_a, et user_b, qui livre mint_b. Le README et les clients générés appellent mint_a la jambe actif et mint_b la jambe cash, et ces pages suivent cette convention, mais rien dans le programme n'exige que la jambe cash soit un instrument de trésorerie. Deux actifs, ou deux stablecoins, se règlent de la même manière.

RôleQuiSigneNotes
Partie de la jambe actifuser_a, livre mint_aSon propre transfert de financement ; Reclaim, Reject, RecoverUn portefeuille keypair, ou un smart wallet basé sur une PDA, comme un coffre multisig. Un compte multisig SPL Token ou un program account ne peut pas être une partie : Create échoue avec PartyNotSignerCapable
Partie de la jambe cashuser_b, livre mint_bSon propre transfert de financement ; Reclaim, Reject, RecoverMême exigence
Autorité de règlementUne troisième adresse désignée à la créationSettle, CancelLe seul signataire pouvant régler. Elle ne peut pas rediriger les produits, qui sont fixés à la création. Elle reçoit le rent des comptes fermés lorsqu'elle règle ou annule
Payeur du rentQuiconque signe CreateDvpCreateFinance le rent (le dépôt de SOL qui maintient un compte ouvert) pour l'enregistrement, la tombstone de nonce et les deux escrows. Non enregistré comme destinataire du rent : le rent des comptes fermés revient à celui qui clôture l'échange

ID du programme

dvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq
ClusterProgrammeAutorité de mise à niveauDernier slot déployé
mainnet-betadvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZqDXtFpbPjcn2hxPnw79x1Pfoj35vXh5AsWBkS37YnXMVv452,058,374
devnetdvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq5EX1zFeJWj4rGYZv432WnYw8CbaSUeTdxPx4r573UgYU479,376,863

Le programme est évolutif sur les deux clusters. Les valeurs ci-dessus sont celles que solana program show a renvoyées le 2 octobre 2026 ; exécutez la même commande pour obtenir les valeurs actuelles. Le build devnet est plus ancien que le commit sur lequel les pages d'étapes sont rédigées (voir Installation), avec le même jeu d'instructions et d'erreurs.

Cycle de vie

Un échange est ouvert depuis CreateDvp jusqu'à ce que Settle, Cancel ou Reject le ferme. Le financement n'a pas d'instruction propre : une jambe est financée lorsque son compte escrow contient au moins le montant convenu. Reclaim s'applique par jambe et laisse l'échange ouvert : un problème sur une jambe n'oblige donc pas l'autre partie à annuler l'échange entier.

État

Chaque échange possède un compte SwapDvp de 458 octets, détenu par le programme. Son adresse est dérivée de l'autorité de règlement, des deux parties, des deux mints et d'un nonce (les seeds ["dvp", settlement_authority, user_a, user_b, mint_a, mint_b, nonce]). Les montants et les dates sont stockés dans le compte et n'affectent pas l'adresse, ce qui explique pourquoi il faut les lire plutôt que les déduire. Un petit compte marqueur, la tombstone de nonce à ["nonce", swap_dvp], est créé avec l'échange et jamais fermé : chaque combinaison de parties, de mints, d'autorité et de nonce ne peut donc servir qu'à un seul échange.

ChampTypeSignification
bumpu8Bump de la PDA
user_a, user_bPubkeyLes deux parties
mint_a, mint_bPubkeyLes mints de la jambe actif et de la jambe cash
settlement_authorityPubkeyLe seul signataire pouvant régler ou annuler
token_program_a, token_program_bPubkeyLe Token Program qui possède chaque mint ; un échange peut mêler des jambes SPL Token et Token-2022
amount_a, amount_bu64Le montant convenu de chaque jambe, dans la plus petite unité du mint (unités de base)
expiry_timestampi64Après cette heure réseau (l'horloge on-chain), Settle est rejeté. Reclaim, Cancel et Reject fonctionnent toujours
nonceu64Distingue les échanges qui partagent les autres seeds. À usage unique
ref_string[u8; 64]Une référence client opaque, complétée par des zéros ; le programme ne la lit jamais
user_a_settlement_destinationPubkeyReçoit la jambe cash lors de Settle ; par défaut user_a
user_b_settlement_destinationPubkeyReçoit la jambe actif lors de Settle ; par défaut user_b
mint_a_authority, mint_b_authorityPubkeyLes autorités de mint capturées à la création ; Settle échoue si l'une d'elles a changé
earliest_settlement_timestampOption<i64>S'il est défini, Settle est rejeté avant cette heure réseau

La liste des champs provient de l'IDL du programme au commit indiqué sous Créer un échange.

Instructions

InstructionSignataireEffet
CreateDvpN'importe quel payeurCrée l'enregistrement SwapDvp, sa tombstone de nonce et les deux comptes escrow. Ne déplace aucun token
ReclaimDvpuser_a ou user_bRestitue depuis l'escrow la propre jambe du signataire. L'échange reste ouvert et la jambe peut être financée à nouveau
SettleDvpAutorité de règlementTransfère amount_a vers la destination de user_b et amount_b vers la destination de user_a, renvoie tout surplus à la partie de la jambe, ferme l'enregistrement et les deux escrows
CancelDvpAutorité de règlementRembourse à user_a ce que détient l'escrow A et à user_b ce que détient l'escrow B, ferme l'enregistrement et les deux escrows
RejectDvpuser_a ou user_bMême effet que Cancel, signé par une partie
RecoverDvpuser_a ou user_bUne fois l'échange fermé : vide un escrow recréé vers le token account du signataire et le ferme. Couvre un dépôt arrivé après Settle, Cancel ou Reject

Chaque remboursement va vers l'associated token account de la partie pour le mint de cette jambe, quel que soit celui qui a financé l'escrow.

Les comptes et arguments de chaque instruction figurent sur la page de l'étape correspondante.

Erreurs

CodeErreurQuand
0SignerNotPartyReclaim, Reject ou Recover signé par une adresse qui n'est ni user_a ni user_b
1DvpExpiredSettle après expiry_timestamp
2SettlementAuthorityMismatchSettle ou Cancel signé par une adresse autre que l'autorité de règlement
3SettlementTooEarlySettle avant earliest_settlement_timestamp
4LegNotFundedSettle alors qu'un escrow contient moins que son montant convenu
5ExpiryNotInFutureCreate avec une expiration égale ou antérieure à l'heure réseau actuelle
6EarliestAfterExpiryCreate avec une heure de règlement au plus tôt postérieure à l'expiration
7SelfDvpCreate avec user_a égal à user_b
8SameMintCreate avec mint_a égal à mint_b
9ZeroAmountCreate avec un montant nul sur l'une des jambes
10BlockedMintExtensionCreate ou Settle avec un mint portant TransferFee, InterestBearing, ScaledUiAmount ou NonTransferable
11SettlementAuthorityIsPartyCreate avec l'autorité de règlement égale à une partie
12SettlementAuthorityExecutableCreate avec une autorité de règlement exécutable
13NonceAlreadyUsedCreate réutilisant un (seeds, nonce) qui possède déjà une tombstone
14ExpiryTooFarInFutureCreate avec une expiration à plus d'un an
15RefStringTooLongCreate avec une référence de plus de 64 octets
16DvpStillOpenRecover alors que l'échange est encore ouvert ; utilisez Reclaim
17DvpNeverCreatedRecover avec des seeds sans tombstone
18EscrowPreloadedWithLamportsCreate lorsqu'un séquestre non natif détient des lamports au-delà de son minimum d'exemption de rent
19SettlementDestinationIsSwapDvpCreate avec une destination de règlement identique à l'enregistrement de l'opération
20SwapDvpPreloadedWithLamportsCreate lorsque l'adresse de l'enregistrement détient des lamports au-delà de sa réserve de rent
21RecipientAtaMismatchUn token account de destinataire ou de remboursement n'appartient plus au portefeuille et au mint attendus
22PartyNotSignerCapableCreate avec une partie qui n'est pas un compte non exécutable appartenant au système
23MintAuthorityChangedSettle lorsque l'autorité de mint d'une jambe diffère de la valeur capturée à la création

Événements et indexation

Le programme n'émet aucun événement on-chain. Un indexeur ou un système de rapprochement lit l'état du compte SwapDvp et les soldes de tokens des deux séquestres, et utilise ref_string pour relier une opération à son ordre hors chaîne. Comme l'enregistrement est clôturé au règlement, un système qui a besoin des conditions après coup les stocke lorsqu'il les lit, ou les reconstitue à partir de la transaction qui a créé l'opération.

Tokens pris en charge

Une opération peut associer une jambe SPL Token à une jambe Token-2022 ; chaque jambe porte son propre token program. Les jambes en SOL wrappé sont prises en charge.

Extension Token-2022 sur le mint d'une jambeComportement du programme
TransferFee, InterestBearing, ScaledUiAmount, NonTransferableRejeté lors de Create et Settle avec BlockedMintExtension
PermanentDelegate, Pausable, DefaultAccountState, une autorité de gel, MintCloseAuthorityAccepté ; chaque autorité peut agir sur les tokens mis sous séquestre
ConfidentialTransferAccepté ; les montants de cette jambe sont publics
TransferHookPris en charge, jusqu'à 32 comptes supplémentaires par jambe
MemoTransfer sur un compte de destination ou de remboursementAccepté ; le programme émet le mémo requis avant le transfert

Des frais de transfert laisseraient la partie réceptrice en deçà du montant convenu. Interest Bearing et Scaled UI Amount ne modifient pas les montants bruts, mais leur taux ou leur multiplicateur peut changer entre la création et le règlement d'une opération, ce qui modifie l'affichage des montants convenus. NonTransferable bloquerait les deux jambes.

Les autorités acceptées peuvent déplacer, geler ou suspendre les tokens sous séquestre pendant toute la durée de l'opération (voir ci-dessous). Le séquestre ne peut pas être configuré pour recevoir des transferts confidentiels ; les montants sous séquestre et réglés sur une jambe confidentielle sont donc publics.

Pour un mint avec hook, le client résout les comptes supplémentaires du hook et les ajoute aux instructions Settle, Cancel, Reject, Reclaim et Recover. Le plafond de 32 par jambe comprend le programme du hook et son compte de validation. Si l'autorité du hook étend la liste de comptes du hook au-delà de ce plafond, tous les transferts de cette jambe échouent, y compris Reclaim, Cancel, Reject et Recover, jusqu'à ce que l'autorité annule cette modification. La limite de comptes par transaction peut s'appliquer avant le plafond par jambe lorsque les deux jambes comportent des hooks.

Lorsqu'un compte de destination ou de remboursement exige des mémos, le programme appelle le programme Memo avant le transfert, c'est pourquoi toute instruction qui retire des tokens du séquestre prend un compte memo_program.

Contraintes de configuration du mint

Scaled UI Amount. Le modèle tokenized-security de Mosaic, la boîte à outils d'émission de la Solana Foundation, ajoute toujours Scaled UI Amount ; le programme n'accepte donc pas comme jambe un mint créé à partir de ce modèle. Un mint destiné à ce programme peut être créé sans l'extension (voir Token Extensions) ; le DvP par délégation règle via une autorité déléguée et n'applique pas cette vérification.

Mints gelés par défaut. Le séquestre de chaque opération est un associated token account détenu par une program-derived address nouvelle pour cette opération. Sur un mint dont Default Account State est défini sur gelé, le séquestre est créé gelé, et un transfert d'alimentation vers celui-ci échoue tant qu'il n'est pas dégelé. Avec Token ACL, cela signifie que l'émetteur ou son agent de transfert admet le séquestre de l'opération avant son alimentation : soit en ajoutant l'adresse de l'enregistrement de l'opération (le propriétaire du séquestre) à la liste d'autorisation, de sorte que n'importe qui peut dégeler le séquestre lorsque la configuration Token ACL du mint active le dégel sans permission, soit en faisant dégeler directement le séquestre par l'autorité de gel. Un séquestre gelé bloque également Settle, Reclaim, Cancel, Reject et Recover sur cette jambe ; l'autorité de gel, et sur un mint avec hook l'autorité du hook, est donc une partie de confiance pendant toute la durée de l'opération. Le cas du séquestre gelé est décrit ici et n'est pas exécuté dans les extraits devnet.

Limites

  • Pas de mise en correspondance, de découverte des prix ni de carnet d'ordres. Les parties conviennent des conditions hors du registre et l'une d'elles les enregistre.
  • Pas de compensation, et uniquement bilatéral : un enregistrement correspond à une opération entre deux parties.
  • Pas d'exécution partielle. Settle déplace exactement le montant convenu de chaque jambe et restitue tout excédent à la partie de la jambe.
  • Pas de jambe hors registre. Les deux jambes sont des token accounts sur Solana ; une jambe en espèces sur des rails existants est réglée séparément et rapprochée.
  • Pas de contrôle d'éligibilité, de liste d'autorisation ni de KYC. Les contrôles propres au mint appliquent l'éligibilité des détenteurs.
  • Pas de règlement automatique. L'autorité de règlement doit signer.
  • Pas d'événements, et aucuns frais de protocole en dehors des frais de transaction et de la rent.

Le programme supprime le risque en capital entre les deux parties : aucune jambe ne bouge si l'autre ne bouge pas. Il ne supprime pas le risque de crédit de l'émetteur ni le risque de remboursement sur l'un ou l'autre token, la capacité de l'autorité de gel, de pause ou de délégué permanent d'un mint à agir sur les tokens sous séquestre (voir tokens pris en charge), ni la dépendance à la disponibilité de l'autorité de règlement pour signer.

Vérification avant l'alimentation et le règlement

CreateDvp ne requiert aucune permission : seul le payeur signe, pas les parties ni l'autorité de règlement. N'importe qui peut donc créer un enregistrement nommant n'importe quelles parties et n'importe quelles conditions. Avant l'alimentation, puis de nouveau avant le règlement, lisez le compte SwapDvp et confirmez que :

  1. Le compte appartient au programme DvP et fait exactement 458 octets. Un compte qui n'appartient pas au programme peut être rempli d'octets qui se décodent comme un véritable enregistrement.
  2. user_a, user_b, mint_a, mint_b et settlement_authority correspondent à l'opération convenue.
  3. amount_a, amount_b, expiry_timestamp et earliest_settlement_timestamp correspondent aux conditions convenues.
  4. Les deux destinations de règlement sont les adresses convenues. Un enregistrement falsifié peut rediriger les fonds.

Alimenter les jambes répertorie les lecteurs avec vérification des clients générés et montre la vérification en code.

Considérez une opération comme réglée lorsque la transaction Settle atteint finalized. Il s'agit d'un niveau d'engagement du réseau ; savoir s'il marque aussi le caractère définitif du règlement à des fins juridiques dépend des accords entre les parties et des régimes applicables.

Gestion des versions

Le compte SwapDvp ne comporte aucun champ de version. Sa structure est fixée à 458 octets (SwapDvp::LEN), et les lecteurs avec vérification rejettent tout compte d'une autre longueur. L'IDL porte sa propre version, 0.1.0 au 2 octobre 2026.

Le programme est évolutif sur les deux clusters (voir ID du programme). Un enregistrement d'opération n'existe que jusqu'à son règlement, son annulation ou son rejet ; consultez donc le dépôt pour savoir comment une mise à niveau traite les opérations ouvertes, et régénérez les clients à partir de l'IDL de la version déployée.

Dépôt et audit

Le code source, l'IDL, les clients générés et les tests se trouvent dans solana-foundation/dvp sous licence MIT. Le programme est un programme Pinocchio no_std ; les clients sont générés à partir de l'IDL avec Codama. Les signalements de vulnérabilités passent par le lien d'avis de sécurité du dépôt, conformément à son SECURITY.md.

Le programme a été audité par Cantina.

Voir aussi

Is this page helpful?

© 2026 Fondation Solana. Tous droits réservés.