Einen Trade erstellen

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.git
cd dvp && git checkout df9919ed02c25a93620e7f6820107c9050ce3b92
pnpm install && pnpm generate-clients && pnpm build
# The client is clients/typescript/src; copy or link it into your project as ./dvp
pnpm 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 asset
amountB: 998_500_000n, // 998.50 units of a 6-decimal cash token
expiryTimestamp: nowSeconds + 60n * 60n * 24n, // one day
nonce,
refString: "ORDER-2026-10-02-0001",
userASettlementDestination: null, // defaults to userA
userBSettlementDestination: null, // defaults to userB
earliestSettlementTimestamp: 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 PartyNotSignerCapable fehl, wenn eine Partei nicht zu den Konten gehört, die dem System Program gehören (das schließt ein SPL-Token-Multisig aus), ebenso mit SettlementDestinationIsSwapDvp, SelfDvp, SameMint, ZeroAmount, SettlementAuthorityIsParty oder ExpiryNotInFuture. Die vollständige Liste findest du in der Fehlertabelle.

Nächste Schritte

Is this page helpful?

Inhaltsverzeichnis

Seite bearbeiten
© 2026 Solana Foundation. Alle Rechte vorbehalten.