CreateDvp hält die Konditionen eines Trades fest und erstellt die beiden Escrow-Konten, die die Parteien finanzieren 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. Alle anderen Beteiligten prüfen die gespeicherten Konditionen, bevor sie darauf reagieren (siehe Legs finanzieren).
Die Snippets verwenden den generierten Client direkt mit Platzhalteradressen, sodass die beteiligten Konten und Signer sichtbar sind. Sie zielen auf Devnet, wo das Program unter derselben Adresse wie im Mainnet-Beta deployt ist.
Installation
Stand 2. Oktober 2026 ist für die DvP-Clients kein Paket veröffentlicht. Beide werden aus der IDL des Programs im Repository generiert und direkt 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 basieren auf dem Commit df9919e, mit @solana/kit 8 in TypeScript und den solana-*-2.x-Crates in Rust.
Die Snippets auf jeder Schrittseite 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 rent-Zahler, die beiden Parteien und die Settlement Authority, zum Beispiel aus createKeyPairSignerFromBytes. Die Rust-Snippets erstellen 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 permissionless ist, kann ein Dritter bei einer vorhersehbaren Nonce die Adresse 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 Konditionen festhalten
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ühestmöglichen 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; standardmäßig ist es 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 finanziert hat.
Der Payer finanziert die rent für den Datensatz, den Nonce-Tombstone und beide Escrows. Beim Schließen geht diese rent an denjenigen, der die Anweisung zum Schließen signiert, nicht zurück an den Payer. Der Tombstone wird nie geschlossen, sodass jeder Trade ein kleines dauerhaftes Guthaben hinterlässt. Wenn du das SOL des Payers einplanst, leite die rent-Beträge mit der RPC-Methode getMinimumBalanceForRentExemption ab, anstatt 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 Scope. Der Rust-Builder belässt das System Program und das Associated Token Program bei ihren Standardwerten.
Hinweise
- Ablauf. Nur Settle prüft das Ablaufdatum. Reclaim, Cancel und Reject funktionieren auch danach, und eine Einzahlung, die nach dem 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 Echtzeit abweichen kann, zeitweise um mehrere Dutzend Sekunden, und die vorspringen kann. Lass zwischen dem Ablaufdatum und dem geplanten Settlement-Zeitpunkt einen Puffer, vermeide es, nahe an einer der beiden Grenzen zu finanzieren oder zu settlen, und verlasse dich nicht darauf, dass das Ablaufdatum eine Frist sekundengenau durchsetzt. Das README des Programs empfiehlt, etwa eine Minute Abweichung einzuplanen.
- Frühestes Settlement. Optional. Wenn gesetzt, wird Settle davor abgelehnt, und der Wert darf nicht nach dem Ablaufdatum liegen.
- Prüfungen bei der Erstellung. Create schlägt mit
PartyNotSignerCapablefehl, wenn eine Partei kein Konto im Besitz des System Program ist (das schließt ein SPL-Token-Multisig aus), außerdem mitSettlementDestinationIsSwapDvp,SelfDvp,SameMint,ZeroAmount,SettlementAuthorityIsPartyoderExpiryNotInFuture. Die vollständige Liste steht in der Fehlertabelle.
Nächste Schritte
- Legs finanzieren: Jede Partei überträgt ihr Leg in das Escrow des Trades
Is this page helpful?