CreateDvp enregistre les conditions d'une transaction et crée les deux comptes de séquestre que les
parties alimenteront. Il ne déplace aucun token. N'importe qui peut l'envoyer ; l'enregistrement peut donc
provenir d'une contrepartie, de l'opérateur de l'autorité de règlement ou d'un tiers,
et chaque autre participant vérifie les conditions stockées avant d'agir en conséquence (voir
financer les jambes).
Les extraits utilisent directement le client généré, avec des adresses fictives, afin que les comptes et les signataires concernés soient visibles. Ils ciblent devnet, où le programme est déployé à la même adresse que sur mainnet-beta.
Installation
Aucun package n'est publié pour les clients DvP au 2 octobre 2026. Les deux sont générés à partir de l'IDL du programme dans le dépôt et utilisés depuis les sources.
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
Ces pages sont rédigées pour le commit df9919e, avec @solana/kit 8 en
TypeScript et les crates solana-* 2.x en Rust.
Les extraits de chaque page d'étape se complètent : chaque page utilise le client, les signataires
et les adresses définis dans les pages précédentes. payerSigner, userASigner,
userBSigner et authoritySigner sont des TransactionSigner pour le payeur du
rent, les deux parties et l'autorité de règlement, par exemple issus de
createKeyPairSignerFromBytes. Les extraits Rust construisent des instructions et laissent
la signature et l'envoi à l'appelant.
Dériver les adresses
L'enregistrement de la transaction est une program-derived address calculée à partir de l'autorité de règlement, des deux parties, des deux mints et d'un nonce. Chaque séquestre est l'associated token account de l'enregistrement pour l'un des mints. Aucune des deux dérivations ne nécessite d'accès réseau.
Utilisez un nonce aléatoire de 64 bits pour chaque transaction. La création étant sans permission, un
nonce prévisible permet à un tiers d'occuper l'adresse qu'une transaction devait
utiliser. Passez le nonce et les montants en bigint en TypeScript ; le client
généré rejette un number.
Remplacez chaque placeholder ..._HERE par une véritable adresse avant l'exécution.
address() valide le base58 et lève une erreur sur un placeholder, et pubkey! ne
compile pas.
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,});
Enregistrer les conditions
CreateDvp prend les deux montants en unités de base, une expiration, le nonce et quatre
champs facultatifs : une référence opaque d'au plus 64 octets, une destination de règlement pour
chaque partie et une heure de règlement au plus tôt (voir Remarques). La destination est
l'endroit où atterrit le produit de cette partie au règlement, par exemple le portefeuille de
dépôt d'un conservateur ; elle correspond par défaut à l'adresse de la partie et est figée une fois que
l'enregistrement existe. Les remboursements et récupérations vont toujours vers le token account de la partie
pour le mint de cette jambe, jamais vers une destination, quel que soit celui qui a alimenté le séquestre.
Le payeur finance le rent de l'enregistrement, du tombstone du nonce et des deux séquestres.
À la fermeture, ce rent revient à celui qui signe l'instruction de fermeture, et non au
payeur. Le tombstone n'est jamais fermé : chaque transaction laisse donc un petit solde
permanent. Pour budgétiser les SOL du payeur, calculez les montants de rent avec la méthode RPC
getMinimumBalanceForRentExemption plutôt que de les coder en dur.
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);
L'extrait TypeScript nécessite que getProgramDerivedAddress et getAddressEncoder
de @solana/kit, ainsi que DVP_SWAP_PROGRAM_PROGRAM_ADDRESS du client, soient dans
la portée. Le builder Rust laisse le system program et l'associated token program
à leurs valeurs par défaut.
Remarques
- Expiration. Seul Settle vérifie l'expiration. Reclaim, Cancel et Reject fonctionnent après celle-ci, et un dépôt arrivé après l'expiration peut être récupéré par la partie de la jambe concernée. L'expiration peut être au plus un an après la création.
- Le temps est celui du réseau. Le programme lit l'horloge onchain, qui peut différer de l'heure réelle, parfois de plusieurs dizaines de secondes, et peut faire un bond en avant. Laissez une marge entre l'expiration et l'heure de règlement prévue, évitez d'alimenter ou de régler à proximité de l'une ou l'autre limite, et ne comptez pas sur l'expiration pour imposer une échéance à la seconde près. Le README du programme suggère de prévoir environ une minute de dérive.
- Règlement au plus tôt. Facultatif. S'il est défini, Settle est rejeté avant cette heure, et celle-ci ne doit pas être postérieure à l'expiration.
- Vérifications à la création. Create échoue avec
PartyNotSignerCapablesi une partie n'est pas un compte appartenant au System Program (ce qui exclut un multisig SPL Token),SettlementDestinationIsSwapDvp,SelfDvp,SameMint,ZeroAmount,SettlementAuthorityIsPartyouExpiryNotInFuture. La liste complète figure dans le tableau des erreurs.
Étapes suivantes
- Financer les jambes : chaque partie transfère sa jambe dans le séquestre de la transaction
Is this page helpful?