CreateDvp erfasst die Bedingungen eines Trades und erstellt die beiden Escrow-Konten,
die die Parteien befüllen werden. Es bewegt keine Token. Jeder kann es senden, daher kann der Datensatz
von einer Gegenpartei, vom Operator der Settlement Authority oder von einem Dritten stammen,
und jeder andere Teilnehmer prüft die gespeicherten Bedingungen, bevor er darauf aufbaut (siehe
Legs finanzieren).
Die Snippets verwenden direkt den generierten Client mit Platzhalteradressen, damit die beteiligten Konten und Signer sichtbar sind. Sie zielen auf Devnet, wo das Program unter derselben Adresse wie im Mainnet-Beta bereitgestellt ist.
Installation
Für die DvP-Clients ist Stand 2. Oktober 2026 kein Paket veröffentlicht. Beide werden aus der IDL des Programs im Repository generiert und aus dem Quellcode verwendet.
git clone https://github.com/solana-foundation/dvp.gitcd dvp && git checkout df9919ed02c25a93620e7f6820107c9050ce3b92pnpm install && pnpm generate-clients && pnpm build# The client is clients/typescript/src; copy or link it into your project as ./dvppnpm add @solana/kit @solana/kit-plugin-rpc @solana/kit-plugin-signer @solana-program/token-2022 @solana-program/memo
Diese Seiten beziehen sich auf den Commit df9919e, mit @solana/kit 8 in
TypeScript und den solana-*-2.x-Crates in Rust.
Die Snippets auf jeder Schritt-Seite bauen aufeinander auf: Jede Seite verwendet den Client, die Signer
und die Adressen, die auf früheren Seiten definiert wurden. payerSigner, userASigner,
userBSigner und authoritySigner sind TransactionSigner für den Zahler der rent,
die beiden Parteien und die Settlement Authority, zum Beispiel aus
createKeyPairSignerFromBytes. Die Rust-Snippets bauen Anweisungen und überlassen
das Signieren und Senden dem Aufrufer.
Die Adressen ableiten
Der Trade-Datensatz ist eine Program Derived Address aus der Settlement Authority, den beiden Parteien, den beiden Mints und einer Nonce. Jedes Escrow ist der associated token account des Datensatzes für einen der Mints. Beide Ableitungen benötigen keinen Netzwerkzugriff.
Verwende für jeden Trade eine zufällige 64-Bit-Nonce. Da die Erstellung erlaubnisfrei ist, kann ein
vorhersehbarer Nonce-Wert einem Dritten ermöglichen, die Adresse zu belegen, die ein Trade
verwenden sollte. Übergib die Nonce und die Beträge in TypeScript als bigint; der generierte
Client lehnt eine number ab.
Ersetze jeden ..._HERE-Platzhalter vor der Ausführung durch eine echte Adresse.
address() validiert Base58 und wirft bei einem Platzhalter einen Fehler, und pubkey! lässt sich nicht
kompilieren.
import { address, createClient } from "@solana/kit";import { solanaRpc } from "@solana/kit-plugin-rpc";import { signer } from "@solana/kit-plugin-signer";import { TOKEN_2022_PROGRAM_ADDRESS } from "@solana-program/token-2022";import { findSwapDvpEscrowAta, findSwapDvpPda } from "./dvp";const client = createClient().use(signer(payerSigner)).use(solanaRpc({rpcUrl: "https://api.devnet.solana.com",rpcSubscriptionsUrl: "wss://api.devnet.solana.com",}),);const settlementAuthority = address("SETTLEMENT_AUTHORITY_ADDRESS_HERE");const userA = address("ASSET_LEG_PARTY_ADDRESS_HERE");const userB = address("CASH_LEG_PARTY_ADDRESS_HERE");const mintA = address("ASSET_MINT_ADDRESS_HERE");const mintB = address("CASH_MINT_ADDRESS_HERE");const tokenProgramA = TOKEN_2022_PROGRAM_ADDRESS;const tokenProgramB = TOKEN_2022_PROGRAM_ADDRESS;// A fresh random nonce per trade, as a bigint.const nonce = new DataView(crypto.getRandomValues(new Uint8Array(8)).buffer,).getBigUint64(0);const [swapDvp] = await findSwapDvpPda({settlementAuthority,userA,userB,mintA,mintB,nonce,});const [escrowA] = await findSwapDvpEscrowAta({swapDvp,mint: mintA,tokenProgram: tokenProgramA,});const [escrowB] = await findSwapDvpEscrowAta({swapDvp,mint: mintB,tokenProgram: tokenProgramB,});
Die Bedingungen erfassen
CreateDvp nimmt die beiden Beträge in Basiseinheiten, ein Ablaufdatum, die Nonce und vier
optionale Felder entgegen: eine opake Referenz von bis zu 64 Bytes, ein Settlement-Ziel
für jede Seite und einen frühesten Settlement-Zeitpunkt (siehe Hinweise). Das Ziel ist
der Ort, an dem die Erlöse der jeweiligen Partei beim Settlement landen, zum Beispiel die
Einzahlungs-Wallet eines Verwahrers; es ist standardmäßig die eigene Adresse der Partei und steht fest, sobald
der Datensatz existiert. Rückerstattungen und Rückforderungen gehen immer an den token account der Partei
für den Mint des jeweiligen Legs, nie an ein Ziel, unabhängig davon, wer das Escrow befüllt hat.
Der Zahler finanziert die rent für den Datensatz, den Nonce-Tombstone und beide Escrow-Konten.
Beim Schließen geht diese rent an den, der die Anweisung zum Schließen signiert, nicht zurück an
den Zahler. Der Tombstone wird nie geschlossen, daher hinterlässt jeder Trade ein kleines dauerhaftes
Guthaben. Wenn du das SOL des Zahlers einplanst, ermittle die rent-Beträge mit der
RPC-Methode getMinimumBalanceForRentExemption, statt sie fest zu codieren.
import { getCreateDvpInstruction } from "./dvp";const nowSeconds = BigInt(Math.floor(Date.now() / 1000));const createInstruction = getCreateDvpInstruction({payer: payerSigner,swapDvp,nonceTombstone: (await getProgramDerivedAddress({programAddress: DVP_SWAP_PROGRAM_PROGRAM_ADDRESS,seeds: ["nonce", getAddressEncoder().encode(swapDvp)],}))[0],settlementAuthority,userA,userB,mintA,mintB,dvpAtaA: escrowA,dvpAtaB: escrowB,tokenProgramA,tokenProgramB,amountA: 1_000_000_000n, // 1,000 units of a 6-decimal assetamountB: 998_500_000n, // 998.50 units of a 6-decimal cash tokenexpiryTimestamp: nowSeconds + 60n * 60n * 24n, // one daynonce,refString: "ORDER-2026-10-02-0001",userASettlementDestination: null, // defaults to userAuserBSettlementDestination: null, // defaults to userBearliestSettlementTimestamp: null,});const { context } = await client.sendTransaction([createInstruction]);console.log("Trade record:", swapDvp, "signature:", context.signature);
Das TypeScript-Snippet benötigt getProgramDerivedAddress und getAddressEncoder
aus @solana/kit sowie DVP_SWAP_PROGRAM_PROGRAM_ADDRESS aus dem Client im
Gültigkeitsbereich. Der Rust-Builder belässt das System Program und das Associated Token Program
bei ihren Standardwerten.
Hinweise
- Ablaufdatum. Nur Settle prüft das Ablaufdatum. Reclaim, Cancel und Reject funktionieren auch danach, und eine Einzahlung, die nach Ablauf eingeht, kann von der Partei des Legs zurückgeholt werden. Das Ablaufdatum darf höchstens ein Jahr nach der Erstellung liegen.
- Zeit ist Netzwerkzeit. Das Program liest die Onchain-Uhr, die von der Wanduhrzeit abweichen kann, mitunter um mehrere zehn Sekunden, und die vorspringen kann. Lass einen Puffer zwischen dem Ablaufdatum und dem geplanten Settlement-Zeitpunkt, vermeide es, nahe an einer der beiden Grenzen zu finanzieren oder zu settlen, und verlasse dich nicht darauf, dass das Ablaufdatum eine sekundengenaue Frist durchsetzt. Die README des Programs empfiehlt, etwa eine Minute Abweichung einzuplanen.
- Frühestes Settlement. Optional. Wenn gesetzt, wird Settle davor abgelehnt, und der Zeitpunkt darf nicht nach dem Ablaufdatum liegen.
- Prüfungen bei der Erstellung. Create schlägt mit
PartyNotSignerCapablefehl, wenn eine Partei nicht zu den Konten gehört, die dem System Program gehören (das schließt ein SPL-Token-Multisig aus), ebenso mitSettlementDestinationIsSwapDvp,SelfDvp,SameMint,ZeroAmount,SettlementAuthorityIsPartyoderExpiryNotInFuture. Die vollständige Liste findest du in der Fehlertabelle.
Nächste Schritte
- Legs finanzieren: Jede Partei überträgt ihr Leg in das Escrow des Trades
Is this page helpful?