CreateDvp registra i termini di una negoziazione e crea i due account di escrow che le
parti finanzieranno. Non sposta alcun token. Può essere inviata da chiunque, quindi il record può
provenire da una controparte, dall'operatore dell'autorità di regolamento o da una terza parte,
e tutti gli altri partecipanti verificano i termini memorizzati prima di agire (vedi
finanziare i leg).
Gli snippet usano direttamente il client generato, con indirizzi segnaposto, in modo che gli account e i firmatari coinvolti siano visibili. Hanno come destinazione devnet, dove il programma è distribuito allo stesso indirizzo di mainnet-beta.
Installazione
Al 2 ottobre 2026 non è pubblicato alcun pacchetto per i client DvP. Entrambi sono generati dall'IDL del programma nel repository e utilizzati dal codice sorgente.
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
Queste pagine sono scritte in riferimento al commit df9919e, con @solana/kit 8 in
TypeScript e i crate solana-* 2.x in Rust.
Gli snippet di ogni pagina dei passaggi sono collegati tra loro: ogni pagina usa il client, i firmatari
e gli indirizzi definiti nelle pagine precedenti. payerSigner, userASigner,
userBSigner e authoritySigner sono TransactionSigner per il pagatore della
rent, le due parti e l'autorità di regolamento, ad esempio ottenuti da
createKeyPairSignerFromBytes. Gli snippet Rust costruiscono le istruzioni e lasciano
la firma e l'invio al chiamante.
Ricavare gli indirizzi
Il record della negoziazione è un Program Derived Address calcolato a partire dall'autorità di regolamento, dalle due parti, dai due mint e da un nonce. Ogni escrow è l'associated token account del record per uno dei mint. Entrambe le derivazioni non richiedono accesso alla rete.
Usa un nonce casuale a 64 bit per ogni negoziazione. Poiché la creazione non richiede autorizzazioni, un
nonce prevedibile consente a una terza parte di occupare l'indirizzo che una negoziazione avrebbe
usato. Passa il nonce e gli importi come bigint in TypeScript; il client
generato rifiuta un number.
Sostituisci ogni segnaposto ..._HERE con un indirizzo reale prima dell'esecuzione.
address() convalida il base58 e genera un errore su un segnaposto, mentre pubkey! non
viene compilato.
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,});
Registrare i termini
CreateDvp accetta i due importi in unità di base, una scadenza, il nonce e quattro
campi opzionali: un riferimento opaco fino a 64 byte, una destinazione di regolamento
per ciascuna parte e un orario di regolamento minimo (vedi Note). La destinazione è
dove finiscono i proventi della parte al momento del regolamento, ad esempio il wallet di deposito di un
custode; per impostazione predefinita è l'indirizzo della parte stessa e viene fissata una volta che
il record esiste. I rimborsi e i recuperi vanno sempre al token account della parte stessa
per il mint di quel leg, mai a una destinazione, chiunque abbia finanziato l'escrow.
Il pagatore finanzia la rent per il record, il tombstone del nonce ed entrambi gli escrow.
Alla chiusura, tale rent va a chi firma l'istruzione di chiusura, non torna al
pagatore. Il tombstone non viene mai chiuso, quindi ogni negoziazione lascia un piccolo saldo
permanente. Quando pianifichi il budget di SOL del pagatore, ricava gli importi della rent con il
metodo RPC getMinimumBalanceForRentExemption invece di inserirli nel codice in modo fisso.
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);
Lo snippet TypeScript richiede che getProgramDerivedAddress e getAddressEncoder
di @solana/kit, e DVP_SWAP_PROGRAM_PROGRAM_ADDRESS del client, siano
nello scope. Il builder Rust lascia il System Program e l'Associated Token Program
ai loro valori predefiniti.
Note
- Scadenza. Solo Settle controlla la scadenza. Reclaim, Cancel e Reject funzionano anche dopo di essa, e un deposito che arriva dopo la scadenza è recuperabile dalla parte del leg. La scadenza può essere al massimo un anno dopo la creazione.
- Il tempo è il tempo di rete. Il programma legge l'orologio onchain, che può differire dall'orario reale, a volte di decine di secondi, e può saltare in avanti. Lascia un margine tra la scadenza e l'orario di regolamento previsto, evita di finanziare o regolare vicino a entrambi i limiti e non fare affidamento sulla scadenza per imporre un termine al secondo. Il README del programma suggerisce di prevedere circa un minuto di scarto.
- Regolamento minimo. Opzionale. Se impostato, Settle viene rifiutata prima di tale momento e non deve essere successivo alla scadenza.
- Controlli in fase di creazione. La creazione fallisce con
PartyNotSignerCapablese una parte non è un account di proprietà del System Program (questo esclude un multisig SPL Token),SettlementDestinationIsSwapDvp,SelfDvp,SameMint,ZeroAmount,SettlementAuthorityIsPartyoExpiryNotInFuture. L'elenco completo è nella tabella degli errori.
Passaggi successivi
- Finanziare i leg: ciascuna parte trasferisce il proprio leg nell'escrow della negoziazione
Is this page helpful?