Το CreateDvp καταγράφει τους όρους μιας συναλλαγής και δημιουργεί τους δύο λογαριασμούς escrow που
θα χρηματοδοτήσουν τα μέρη. Δεν μεταφέρει tokens. Μπορεί να το στείλει οποιοσδήποτε, οπότε η εγγραφή
μπορεί να προέρχεται από έναν αντισυμβαλλόμενο, τον operator της αρχής διακανονισμού ή έναν τρίτο,
και κάθε άλλος συμμετέχων επαληθεύει τους αποθηκευμένους όρους πριν ενεργήσει βάσει αυτών (δείτε
χρηματοδότηση των σκελών).
Τα αποσπάσματα κώδικα χρησιμοποιούν απευθείας τον παραγόμενο client, με διευθύνσεις υποκατάστασης (placeholder), ώστε να φαίνονται οι λογαριασμοί και οι υπογράφοντες που εμπλέκονται. Στοχεύουν το devnet, όπου το πρόγραμμα έχει αναπτυχθεί στην ίδια διεύθυνση με το mainnet-beta.
Εγκατάσταση
Μέχρι τις 2 Οκτωβρίου 2026 δεν έχει δημοσιευτεί κανένα πακέτο για τους clients του DvP. Και οι δύο παράγονται από το IDL του προγράμματος στο repository και χρησιμοποιούνται από τον πηγαίο κώδικα.
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
Αυτές οι σελίδες έχουν γραφτεί με βάση το commit df9919e, με το @solana/kit 8 σε
TypeScript και τα crates solana-* 2.x σε Rust.
Τα αποσπάσματα κώδικα σε κάθε σελίδα βήματος συνεχίζονται από το ένα στο άλλο: κάθε σελίδα χρησιμοποιεί τον client, τους υπογράφοντες
και τις διευθύνσεις που ορίστηκαν σε προηγούμενες σελίδες. Τα payerSigner, userASigner,
userBSigner και authoritySigner είναι TransactionSigner για τον πληρωτή του rent, τα δύο μέρη και
την αρχή διακανονισμού, για παράδειγμα από το
createKeyPairSignerFromBytes. Τα αποσπάσματα Rust δημιουργούν εντολές (instructions) και αφήνουν
την υπογραφή και την αποστολή στον καλούντα.
Παραγωγή των διευθύνσεων
Η εγγραφή της συναλλαγής είναι μια Program Derived Address που παράγεται από την αρχή διακανονισμού, τα δύο μέρη, τα δύο mints και ένα nonce. Κάθε escrow είναι ο associated token account της εγγραφής για ένα από τα mints. Και οι δύο παραγωγές δεν απαιτούν πρόσβαση στο δίκτυο.
Χρησιμοποιήστε ένα τυχαίο 64-bit nonce για κάθε συναλλαγή. Επειδή η δημιουργία δεν απαιτεί άδεια, ένα
προβλέψιμο nonce επιτρέπει σε έναν τρίτο να καταλάβει τη διεύθυνση που επρόκειτο να χρησιμοποιήσει μια συναλλαγή. Περάστε το nonce και τα ποσά ως bigint στο TypeScript· ο παραγόμενος
client απορρίπτει ένα number.
Αντικαταστήστε κάθε placeholder ..._HERE με πραγματική διεύθυνση πριν από την εκτέλεση.
Η address() επικυρώνει το base58 και πετά σφάλμα σε placeholder, ενώ το pubkey! αποτυγχάνει στη
μεταγλώττιση.
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,});
Καταγραφή των όρων
Το CreateDvp δέχεται τα δύο ποσά σε βασικές μονάδες, μια λήξη, το nonce και τέσσερα
προαιρετικά πεδία: μια αδιαφανή αναφορά έως 64 bytes, έναν προορισμό διακανονισμού
για κάθε πλευρά και έναν νωρίτερο χρόνο διακανονισμού (δείτε Σημειώσεις). Ο προορισμός είναι
το σημείο όπου καταλήγουν τα έσοδα του μέρους κατά τον διακανονισμό, για παράδειγμα το πορτοφόλι κατάθεσης ενός θεματοφύλακα·
επιλέγεται από προεπιλογή η δική διεύθυνση του μέρους και παγιώνεται μόλις δημιουργηθεί
η εγγραφή. Οι επιστροφές και οι ανακτήσεις πηγαίνουν πάντα στον δικό του token account του μέρους
για το mint εκείνου του σκέλους, ποτέ σε προορισμό, ανεξάρτητα από το ποιος χρηματοδότησε το escrow.
Ο πληρωτής χρηματοδοτεί το rent για την εγγραφή, το tombstone του nonce και τους δύο λογαριασμούς escrow.
Κατά το κλείσιμο, το rent αυτό πηγαίνει σε όποιον υπογράφει την εντολή κλεισίματος, όχι πίσω στον
πληρωτή. Το tombstone δεν κλείνει ποτέ, επομένως κάθε συναλλαγή αφήνει ένα μικρό μόνιμο
υπόλοιπο. Όταν προϋπολογίζετε τα SOL του πληρωτή, υπολογίστε τα ποσά του rent με τη
μέθοδο RPC getMinimumBalanceForRentExemption αντί να τα γράφετε σταθερά στον κώδικα.
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);
Το απόσπασμα TypeScript απαιτεί τα getProgramDerivedAddress και getAddressEncoder
από το @solana/kit, και το DVP_SWAP_PROGRAM_PROGRAM_ADDRESS από τον client, να είναι διαθέσιμα στο
scope. Ο builder της Rust αφήνει το system program και το associated token program
στις προεπιλογές τους.
Σημειώσεις
- Λήξη. Μόνο το Settle ελέγχει τη λήξη. Τα Reclaim, Cancel και Reject λειτουργούν και μετά από αυτήν, και μια κατάθεση που φτάνει μετά τη λήξη μπορεί να ανακτηθεί από το μέρος του σκέλους. Η λήξη μπορεί να είναι το πολύ ένα έτος μετά τη δημιουργία.
- Ο χρόνος είναι ο χρόνος του δικτύου. Το πρόγραμμα διαβάζει το ρολόι του onchain, το οποίο μπορεί να διαφέρει από την πραγματική ώρα, κάποιες φορές κατά δεκάδες δευτερόλεπτα, και μπορεί να πηδήξει προς τα εμπρός. Αφήστε ένα περιθώριο ανάμεσα στη λήξη και τον προβλεπόμενο χρόνο διακανονισμού, αποφύγετε τη χρηματοδότηση ή τον διακανονισμό κοντά σε οποιοδήποτε από τα δύο όρια, και μην βασίζεστε στη λήξη για να επιβάλλετε προθεσμία με ακρίβεια δευτερολέπτου. Το README του προγράμματος προτείνει να προβλέπεται απόκλιση περίπου ενός λεπτού.
- Νωρίτερος διακανονισμός. Προαιρετικό. Αν οριστεί, το Settle απορρίπτεται πριν από αυτόν, και δεν πρέπει να είναι μεταγενέστερος της λήξης.
- Έλεγχοι κατά τη δημιουργία. Η δημιουργία αποτυγχάνει με
PartyNotSignerCapableαν ένα μέρος δεν είναι λογαριασμός που ανήκει στο System Program (αυτό αποκλείει ένα multisig του SPL Token),SettlementDestinationIsSwapDvp,SelfDvp,SameMint,ZeroAmount,SettlementAuthorityIsPartyήExpiryNotInFuture. Ο πλήρης κατάλογος βρίσκεται στον πίνακα σφαλμάτων.
Επόμενα βήματα
- Χρηματοδότηση των σκελών: κάθε μέρος μεταφέρει το σκέλος του στο escrow της συναλλαγής
Is this page helpful?