Creare una negoziazione

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.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

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 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);

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 PartyNotSignerCapable se una parte non è un account di proprietà del System Program (questo esclude un multisig SPL Token), SettlementDestinationIsSwapDvp, SelfDvp, SameMint, ZeroAmount, SettlementAuthorityIsParty o ExpiryNotInFuture. 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?

Indice dei contenuti

Modifica pagina
© 2026 Solana Foundation. Tutti i diritti riservati.