Das DvP-Programm ist das Standardprogramm für Lieferung gegen Zahlung (Delivery versus Payment) auf Solana.
Es wird von der Solana Foundation gepflegt und von Cantina auditiert. Zwei Parteien einigen sich auf
einen Trade, jede sendet ihr Leg per gewöhnlicher Token-Überweisung an ein Escrow-Konto, und eine
Settlement-Authority wickelt beide Legs in einer Transaktion ab, sodass
entweder beide ausgeführt werden oder keines. Die Settlement-Authority ist eine dritte Adresse,
die im Trade benannt wird, etwa die Handelsplattform oder der Agent, der ihn vermittelt hat. Keine der Parteien
schreibt oder deployt ein Programm, und ein Wallet oder Custodian, das bzw. der eine standardmäßige
Token-Überweisung (TransferChecked) senden kann, kann ein Leg ohne DvP-spezifische
Integration finanzieren. Bis zur Abwicklung kann jede Partei ihr eigenes Leg zurückholen oder den
Trade rückabwickeln.
Den Datensatz prüfen, bevor man darauf reagiert
Das Erstellen eines Trade-Datensatzes ist permissionless, daher ist ein Datensatz kein Beweis für eine Einigung. Jede Partei liest die gespeicherten Konditionen, bevor sie finanziert, und die Settlement-Authority vor der Abwicklung (siehe Verifizierung).
So funktioniert es
Jeder Trade erhält einen programmeigenen Datensatz und zwei Escrow-Konten, eines pro Leg. Die
Partei des Asset-Legs (user_a) und die Partei des Cash-Legs (user_b) finanzieren jeweils ein Leg, indem sie
Token auf das Escrow-Konto dieses Legs überweisen. Die Settlement-Authority
ist der einzige Signer, der abwickeln kann. Die Abwicklung überweist den vereinbarten
Betrag jedes Legs an die Gegenseite, gibt einen etwaigen Überschuss an die Partei zurück, der
das Leg gehört, und schließt den Datensatz sowie beide Escrows. Die Atomarität ergibt sich aus Solana-
Transaktionen: Beide Überweisungen sind in einer Transaktion enthalten,
sodass entweder beide wirksam werden oder keine.
Jede Rückerstattung, Rückforderung und Rückgabe von Überschüssen geht an das eigene associated
token account der Partei: das Konto von user_a für mint_a, das von user_b für mint_b. Ein
Custodian oder Treasury-System kann ein Leg von seinem eigenen Konto aus finanzieren, aber alles
Zurückgegebene landet im Konto der Partei, nicht beim Custodian. Jeder Trade
hinterlässt außerdem ein dauerhaftes Marker-Konto (siehe State).
Anleitungen
Die Schrittseiten führen einen Trade von der Erstellung bis zur Abwicklung, mit TypeScript und Rust für jede Anweisung.
Einen Trade erstellen
Die Clients installieren und die Konditionen des Trades mit CreateDvp erfassen
Die Legs finanzieren
Die gespeicherten Konditionen prüfen, dann jedes Leg mit einer einfachen Überweisung finanzieren
Den Trade abwickeln
Beide Legs in einer Transaktion als Settlement-Authority abwickeln
Einen Trade rückabwickeln
Ein Leg zurückfordern, den Trade stornieren oder ablehnen und verspätete Einzahlungen wiederherstellen
Eine Devnet-Demo führt einen Trade von Anfang bis Ende aus: Sie erstellt den Trade, finanziert beide Escrows mit standardmäßigen Token-Überweisungen und wickelt beide Legs in einer Transaktion ab.
Zwischen Programm und Delegation wählen
Lieferung gegen Zahlung (DvP) per Delegation wickelt denselben Austausch ohne Programm ab: Jede Seite delegiert ihre Position an einen Settlement-Agent, der beide Legs in einer Transaktion bewegt.
- Das Programm benötigt keine Delegation, sodass die Beschränkung auf einen Delegate pro Token account nicht greift und die Settlement-Authority Erlöse nicht umleiten kann. Jeder Trade hängt von einem upgradefähigen Programm und dessen Upgrade-Authority ab.
- Das Delegationsmuster hat keine Programmabhängigkeit und funktioniert mit Mints, die
Scaled UI Amount verwenden. Das Programm lehnt diese Mints ab, wodurch jeder
Mint, der aus Mosaics
tokenized-security-Vorlage erstellt wurde, ausgeschlossen ist (siehe Einschränkungen der Mint-Konfiguration).
Rollen
Das Programm ist symmetrisch. In seiner Schnittstelle sind die beiden Parteien user_a, der
mint_a liefert, und user_b, der mint_b liefert. Die README und die
generierten Clients nennen mint_a das Asset-Leg und mint_b das Cash-Leg, und
diese Seiten folgen dieser Konvention, aber nichts im Programm verlangt, dass das Cash-Leg
ein Cash-Instrument sein muss. Zwei Assets oder zwei Stablecoins werden auf dieselbe Weise
abgewickelt.
| Rolle | Wer | Signiert | Hinweise |
|---|---|---|---|
| Partei des Asset-Legs | user_a, liefert mint_a | Die eigene Finanzierungsüberweisung; Reclaim, Reject, Recover | Ein keypair-Wallet oder ein PDA-basiertes Smart Wallet wie ein Multisig-Vault. Ein SPL-Token-Multisig-Konto oder ein program account kann keine Partei sein: Create schlägt mit PartyNotSignerCapable fehl |
| Partei des Cash-Legs | user_b, liefert mint_b | Die eigene Finanzierungsüberweisung; Reclaim, Reject, Recover | Gleiche Anforderung |
| Settlement-Authority | Eine dritte Adresse, die bei der Erstellung benannt wird | Settle, Cancel | Der einzige Signer, der abwickeln kann. Sie kann Erlöse nicht umleiten, da diese bei der Erstellung festgelegt werden. Sie erhält den Rent der geschlossenen Konten, wenn sie abwickelt oder storniert |
| Rent-Zahler | Wer CreateDvp signiert | Create | Finanziert den Rent (die SOL-Einlage, die ein Konto offen hält) für den Datensatz, den Nonce-Tombstone und beide Escrows. Wird nicht als Rent-Empfänger erfasst: Der Rent geschlossener Konten geht an den, der den Trade schließt |
Program ID
dvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq
| Cluster | Programm | Upgrade-Authority | Zuletzt deployter slot |
|---|---|---|---|
| mainnet-beta | dvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq | DXtFpbPjcn2hxPnw79x1Pfoj35vXh5AsWBkS37YnXMVv | 452,058,374 |
| devnet | dvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq | 5EX1zFeJWj4rGYZv432WnYw8CbaSUeTdxPx4r573UgYU | 479,376,863 |
Das Programm ist auf beiden Clustern upgradefähig. Die obigen Angaben entsprechen dem, was
solana program show am 2. Oktober 2026 ausgegeben hat; führe denselben Befehl aus, um
aktuelle Werte zu erhalten. Der Devnet-Build ist älter als der Commit, auf den die Schrittseiten
ausgelegt sind (siehe Installation),
mit demselben Anweisungs- und Fehlersatz.
Lebenszyklus
Ein Trade ist ab CreateDvp offen, bis einer von Settle, Cancel oder Reject ihn
schließt. Die Finanzierung hat keine eigene Anweisung: Ein Leg gilt als finanziert, wenn sein Escrow-
Konto mindestens den vereinbarten Betrag enthält. Reclaim erfolgt pro Leg und lässt den
Trade offen, sodass ein Problem mit einem Leg die andere Partei nicht zwingt, den
gesamten Trade zu stornieren.
State
Jeder Trade hat ein SwapDvp-Konto mit 458 Bytes, das dem Programm gehört. Seine
Adresse wird aus der Settlement-Authority, den beiden Parteien, den beiden
Mints und einem Nonce abgeleitet (die Seeds
["dvp", settlement_authority, user_a, user_b, mint_a, mint_b, nonce]). Die
Beträge und Zeiten werden im Konto gespeichert und beeinflussen die Adresse nicht,
weshalb sie gelesen statt abgeleitet werden müssen. Ein kleines Marker-Konto,
der Nonce-Tombstone unter ["nonce", swap_dvp], wird mit dem Trade erstellt und
nie geschlossen, sodass jede Kombination aus Parteien, Mints, Authority und Nonce nur
für einen Trade verwendet werden kann.
| Feld | Typ | Bedeutung |
|---|---|---|
bump | u8 | PDA-Bump |
user_a, user_b | Pubkey | Die beiden Parteien |
mint_a, mint_b | Pubkey | Die Mints des Asset-Legs und des Cash-Legs |
settlement_authority | Pubkey | Der einzige Signer, der abwickeln oder stornieren kann |
token_program_a, token_program_b | Pubkey | Das Token Program, dem der jeweilige Mint gehört; ein Trade kann SPL-Token- und Token-2022-Legs mischen |
amount_a, amount_b | u64 | Der vereinbarte Betrag jedes Legs in der kleinsten Einheit des Mints (Basiseinheiten) |
expiry_timestamp | i64 | Nach dieser Netzwerkzeit (der Onchain-Uhr) wird Settle abgelehnt. Reclaim, Cancel und Reject funktionieren weiterhin |
nonce | u64 | Unterscheidet Trades, die die übrigen Seeds gemeinsam haben. Einmalig verwendbar |
ref_string | [u8; 64] | Eine opake Client-Referenz, mit Nullen aufgefüllt; das Programm liest sie nie |
user_a_settlement_destination | Pubkey | Erhält das Cash-Leg bei Settle; Standardwert ist user_a |
user_b_settlement_destination | Pubkey | Erhält das Asset-Leg bei Settle; Standardwert ist user_b |
mint_a_authority, mint_b_authority | Pubkey | Die bei der Erstellung erfassten Mint-Authorities; Settle schlägt fehl, wenn sich eine davon geändert hat |
earliest_settlement_timestamp | Option<i64> | Falls gesetzt, wird Settle vor dieser Netzwerkzeit abgelehnt |
Die Feldliste stammt aus dem IDL des Programms beim Commit, der unter Einen Trade erstellen genannt wird.
Anweisungen
| Anweisung | Signer | Wirkung |
|---|---|---|
CreateDvp | Beliebiger Zahler | Erstellt den SwapDvp-Datensatz, seinen Nonce-Tombstone und beide Escrow-Konten. Bewegt keine Token |
ReclaimDvp | user_a oder user_b | Gibt das eigene Leg des Signers aus dem Escrow zurück. Der Trade bleibt offen und das Leg kann erneut finanziert werden |
SettleDvp | Settlement-Authority | Überweist amount_a an das Ziel von user_b und amount_b an das Ziel von user_a, gibt einen etwaigen Überschuss an die Partei des Legs zurück, schließt den Datensatz und beide Escrows |
CancelDvp | Settlement-Authority | Erstattet alles, was Escrow A hält, an user_a und Escrow B an user_b, schließt den Datensatz und beide Escrows |
RejectDvp | user_a oder user_b | Gleiche Wirkung wie Cancel, von einer Partei signiert |
RecoverDvp | user_a oder user_b | Nachdem der Trade geschlossen wurde: leert ein neu erstelltes Escrow in das eigene token account des Signers und schließt es. Deckt eine Einzahlung ab, die nach Settle, Cancel oder Reject eingegangen ist |
Jede Rückerstattung geht an das eigene associated token account der Partei für den Mint dieses Legs, unabhängig davon, wer das Escrow finanziert hat.
Die Konten und Argumente jeder Anweisung finden sich auf der Seite des jeweiligen Schritts.
Fehler
| Code | Fehler | Wann |
|---|---|---|
| 0 | SignerNotParty | Reclaim, Reject oder Recover von einer Adresse signiert, die nicht user_a oder user_b ist |
| 1 | DvpExpired | Settle nach expiry_timestamp |
| 2 | SettlementAuthorityMismatch | Settle oder Cancel von einer anderen Adresse als der Settlement-Authority signiert |
| 3 | SettlementTooEarly | Settle vor earliest_settlement_timestamp |
| 4 | LegNotFunded | Settle, während ein Escrow weniger als seinen vereinbarten Betrag hält |
| 5 | ExpiryNotInFuture | Create mit einem Ablaufzeitpunkt zur oder vor der aktuellen Netzwerkzeit |
| 6 | EarliestAfterExpiry | Create mit einem frühestmöglichen Abwicklungszeitpunkt nach dem Ablauf |
| 7 | SelfDvp | Create mit user_a gleich user_b |
| 8 | SameMint | Create mit mint_a gleich mint_b |
| 9 | ZeroAmount | Create mit einem Betrag von null bei einem der Legs |
| 10 | BlockedMintExtension | Create oder Settle mit einem Mint, der TransferFee, InterestBearing, ScaledUiAmount oder NonTransferable verwendet |
| 11 | SettlementAuthorityIsParty | Create mit einer Settlement-Authority, die einer Partei entspricht |
| 12 | SettlementAuthorityExecutable | Create mit einer ausführbaren Settlement-Authority |
| 13 | NonceAlreadyUsed | Create mit Wiederverwendung eines (seeds, nonce), für das bereits ein Tombstone existiert |
| 14 | ExpiryTooFarInFuture | Create mit einem Ablaufzeitpunkt, der mehr als ein Jahr in der Zukunft liegt |
| 15 | RefStringTooLong | Create mit einer Referenz, die länger als 64 Bytes ist |
| 16 | DvpStillOpen | Recover, solange der Trade noch offen ist; stattdessen Reclaim verwenden |
| 17 | DvpNeverCreated | Recover mit Seeds ohne Tombstone |
| 18 | EscrowPreloadedWithLamports | Create, wenn ein nicht-nativer Escrow lamports oberhalb seines rent-exempt-Minimums hält |
| 19 | SettlementDestinationIsSwapDvp | Create mit einem Settlement-Ziel, das dem Trade-Record entspricht |
| 20 | SwapDvpPreloadedWithLamports | Create, wenn die Record-Adresse lamports oberhalb ihrer rent-Reserve hält |
| 21 | RecipientAtaMismatch | Ein token account für Empfänger oder Rückerstattung gehört nicht mehr zur erwarteten Wallet und zum erwarteten Mint |
| 22 | PartyNotSignerCapable | Create mit einer Partei, die kein systemeigenes, nicht ausführbares Konto ist |
| 23 | MintAuthorityChanged | Settle, wenn sich die Mint Authority eines Legs von dem bei der Erstellung erfassten Wert unterscheidet |
Events und Indexierung
Das Programm gibt keine Onchain-Events aus. Ein Indexer oder Abgleichssystem liest
den Zustand des SwapDvp-Kontos und die Token-Guthaben der beiden Escrows und nutzt
ref_string, um einen Trade der zugehörigen Offchain-Order zuzuordnen. Da der Record
bei der Abwicklung geschlossen wird, speichert ein System, das die Konditionen im
Nachhinein benötigt, diese beim Auslesen oder rekonstruiert sie aus der Transaktion,
die den Trade erstellt hat.
Unterstützte Tokens
Ein Trade kann ein SPL-Token-Leg mit einem Token-2022-Leg kombinieren; jedes Leg hat sein eigenes Token Program. Wrapped-SOL-Legs werden unterstützt.
| Token-2022-Extension auf dem Mint eines Legs | Verhalten des Programms |
|---|---|
| TransferFee, InterestBearing, ScaledUiAmount, NonTransferable | Wird bei Create und Settle mit BlockedMintExtension abgelehnt |
| PermanentDelegate, Pausable, DefaultAccountState, eine Freeze Authority, MintCloseAuthority | Akzeptiert; jede Authority kann auf die im Escrow gehaltenen Tokens zugreifen |
| ConfidentialTransfer | Akzeptiert; die Beträge auf diesem Leg sind öffentlich |
| TransferHook | Unterstützt, bis zu 32 zusätzliche Konten pro Leg |
| MemoTransfer auf einem Ziel- oder Rückerstattungskonto | Akzeptiert; das Programm gibt das erforderliche Memo vor dem Transfer aus |
Eine Transfer-Fee würde dazu führen, dass die empfangende Seite weniger als den vereinbarten Betrag erhält. Interest Bearing und Scaled UI Amount verändern die Rohbeträge nicht, doch ihr Zinssatz bzw. Multiplikator kann sich zwischen dem Erstellen und dem Abwickeln eines Trades ändern, wodurch sich die Anzeige der vereinbarten Beträge ändert. NonTransferable würde beide Legs blockieren.
Die akzeptierten Authorities können die im Escrow gehaltenen Tokens während der gesamten Laufzeit des Trades bewegen, einfrieren oder anhalten (siehe unten). Der Escrow kann nicht für den Empfang vertraulicher Transfers konfiguriert werden, daher sind die im Escrow gehaltenen und die abgewickelten Beträge auf einem vertraulichen Leg öffentlich.
Bei einem Mint mit Hook ermittelt der Client die zusätzlichen Konten des Hooks und hängt sie an Settle, Cancel, Reject, Reclaim und Recover an. Das Limit von 32 pro Leg umfasst das Hook-Programm und sein Validierungskonto. Wenn die Hook Authority die Kontenliste des Hooks über dieses Limit hinaus erweitert, schlägt jeder Transfer auf diesem Leg fehl, einschließlich Reclaim, Cancel, Reject und Recover, bis die Authority dies rückgängig macht. Das Kontolimit der Transaktion kann bereits vor dem Limit pro Leg greifen, wenn beide Legs Hooks haben.
Verlangt ein Ziel- oder Rückerstattungskonto Memos, ruft das Programm vor dem
Transfer das Memo-Programm auf. Deshalb benötigt jede Anweisung, die Tokens aus dem
Escrow bewegt, ein memo_program-Konto.
Einschränkungen bei der Mint-Konfiguration
Scaled UI Amount. Das tokenized-security-Template in
Mosaic, dem Issuance-Toolkit der Solana Foundation, fügt immer Scaled UI
Amount hinzu. Das Programm akzeptiert daher keinen aus diesem Template erstellten
Mint als Leg. Ein für dieses Programm vorgesehener Mint kann ohne die Extension
erstellt werden (siehe Token Extensions);
DvP mit Delegation wird über delegierte Authority abgewickelt und wendet
diese Prüfung nicht an.
Standardmäßig eingefrorene Mints. Der Escrow jedes Trades ist ein associated token account, der einer Program Derived Address gehört, die für diesen Trade neu ist. Bei einem Mint, dessen Default Account State auf „frozen“ gesetzt ist, wird der Escrow eingefroren erstellt, und ein Funding-Transfer dorthin schlägt fehl, bis er wieder freigegeben wird. Unter Token ACL bedeutet das, dass der Emittent oder sein Transfer Agent den Escrow des Trades vor dem Funding freigibt: entweder indem die Adresse des Trade-Records (der Eigentümer des Escrows) zur Allowlist hinzugefügt wird, sodass jeder den Escrow freigeben kann, sofern die Token-ACL-Konfiguration des Mints Permissionless Thaw aktiviert, oder indem die Freeze Authority ihn direkt freigibt. Ein eingefrorener Escrow blockiert auf diesem Leg auch Settle, Reclaim, Cancel, Reject und Recover. Die Freeze Authority, und bei einem Mint mit Hook die Hook Authority, ist somit für die Laufzeit des Trades eine vertrauenswürdige Partei. Der Pfad mit eingefrorenem Escrow wird hier beschrieben und in den Devnet-Snippets nicht ausgeführt.
Einschränkungen
- Kein Matching, keine Preisfindung und kein Orderbuch. Die Parteien vereinbaren die Konditionen außerhalb des Ledgers, und eine von ihnen zeichnet sie auf.
- Kein Netting und nur bilateral: Ein Record ist ein Trade zwischen zwei Parteien.
- Keine Teilausführungen. Settle bewegt genau den vereinbarten Betrag jedes Legs und gibt einen etwaigen Überschuss an die Partei des Legs zurück.
- Kein Off-Ledger-Leg. Beide Legs sind token accounts auf Solana; ein Cash-Leg über bestehende Rails wird separat abgewickelt und abgeglichen.
- Keine Berechtigungsprüfung, keine Allowlist und kein KYC. Die eigenen Kontrollen des Mints erzwingen die Berechtigung der Inhaber.
- Keine automatische Abwicklung. Die Settlement Authority muss signieren.
- Keine Events und kein Protokoll-Fee über Transaktions-Fees und rent hinaus.
Das Programm beseitigt das Principal-Risiko zwischen den beiden Parteien: Kein Leg wird bewegt, wenn nicht beide bewegt werden. Es beseitigt nicht das Emittenten- Kredit- oder Rücknahmerisiko bei einem der Tokens, die Möglichkeit der Freeze-, Pause- oder Permanent-Delegate-Authority eines Mints, auf im Escrow gehaltene Tokens zuzugreifen (siehe unterstützte Tokens), oder die Abhängigkeit davon, dass die Settlement Authority zum Signieren verfügbar ist.
Überprüfung vor Funding und Abwicklung
CreateDvp ist permissionless: Nur der Payer signiert, die Parteien und die
Settlement Authority nicht. Jeder kann daher einen Record erstellen, der beliebige
Parteien und beliebige Konditionen benennt. Lies vor dem Funding und erneut vor der
Abwicklung das SwapDvp-Konto aus und bestätige:
- Das Konto gehört dem DvP-Programm und ist genau 458 Bytes groß. Ein Konto, das nicht dem Programm gehört, kann mit Bytes gefüllt sein, die sich wie ein echter Record dekodieren lassen.
user_a,user_b,mint_a,mint_bundsettlement_authorityentsprechen dem vereinbarten Trade.amount_a,amount_b,expiry_timestampundearliest_settlement_timestampentsprechen den vereinbarten Konditionen.- Beide Settlement-Ziele sind die vereinbarten Adressen. Ein gefälschter Record kann Erlöse umleiten.
Legs finanzieren listet die geprüften Reader der generierten Clients auf und zeigt die Prüfung im Code.
Betrachte einen Trade als abgewickelt, sobald die Settle-Transaktion
finalized erreicht. Das ist ein Commitment-Level des Netzwerks; ob es auch
die Settlement Finality im rechtlichen Sinne markiert, hängt von den Vereinbarungen
der Parteien und den geltenden Regelwerken ab.
Versionierung
Das SwapDvp-Konto enthält kein Versionsfeld. Sein Layout ist auf 458 Bytes
festgelegt (SwapDvp::LEN), und die geprüften Reader lehnen ein Konto mit anderer
Länge ab. Die IDL trägt ihre eigene Version, 0.1.0 Stand 2. Oktober 2026.
Das Programm ist auf beiden Clustern upgradefähig (siehe Program ID). Ein Trade-Record existiert nur, bis er abgewickelt, storniert oder abgelehnt wird. Prüfe daher im Repository, wie ein Upgrade offene Trades behandelt, und generiere Clients aus der IDL des bereitgestellten Builds neu.
Repository und Audit
Quellcode, IDL, generierte Clients und Tests befinden sich in
solana-foundation/dvp unter der MIT-Lizenz. Das Programm ist ein
no_std-Pinocchio-Programm; die Clients werden mit Codama aus der IDL generiert.
Meldungen zu Schwachstellen laufen über den Security-Advisory-Link des Repositorys,
gemäß dessen SECURITY.md.
Das Programm wurde von Cantina auditiert.
Verwandte Themen
- Delivery vs Payment (DvP) mit Delegation: das Delegationsmuster ohne Programm
- Token ACL: wie ein gesperrter Mint mit den Escrow-Konten interagiert
Is this page helpful?