Το CreateDvp καταγράφει τους όρους μιας συναλλαγής και δημιουργεί τους δύο
λογαριασμούς escrow που θα χρηματοδοτήσουν τα μέρη. Δεν μεταφέρει κανένα token.
Μπορεί να το στείλει οποιοσδήποτε, επομένως η εγγραφή μπορεί να προέρχεται από
έναν αντισυμβαλλόμενο, από τον operator της αρχής διακανονισμού ή από τρίτο
μέρος, και κάθε άλλος συμμετέχων επαληθεύει τους αποθηκευμένους όρους πριν
ενεργήσει με βάση αυτούς (δείτε χρηματοδότηση των σκελών).
Τα αποσπάσματα κώδικα χρησιμοποιούν απευθείας τον παραγόμενο client, με προσωρινές διευθύνσεις, ώστε να φαίνονται οι λογαριασμοί και οι υπογράφοντες που εμπλέκονται. Στοχεύουν το devnet, όπου το πρόγραμμα έχει αναπτυχθεί στην ίδια διεύθυνση με το mainnet-beta.
Εγκατάσταση
Από τις 2 Οκτωβρίου 2026 δεν έχει δημοσιευτεί κανένα πακέτο για τους clients του DvP. Και οι δύο παράγονται από το IDL του προγράμματος στο αποθετήριο και χρησιμοποιούνται από τον πηγαίο κώδικα.
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 δημιουργούν εντολές και
αφήνουν την υπογραφή και την αποστολή στον καλούντα.
Εξαγωγή των διευθύνσεων
Η εγγραφή της συναλλαγής είναι μια program-derived address που προκύπτει από την αρχή διακανονισμού, τα δύο μέρη, τα δύο mints και ένα nonce. Κάθε escrow είναι ο associated token account της εγγραφής για ένα από τα mints. Και οι δύο εξαγωγές δεν απαιτούν πρόσβαση στο δίκτυο.
Χρησιμοποιήστε ένα τυχαίο 64-bit nonce για κάθε συναλλαγή. Επειδή η δημιουργία
δεν απαιτεί άδεια, ένα προβλέψιμο nonce επιτρέπει σε τρίτο μέρος να καταλάβει τη
διεύθυνση που επρόκειτο να χρησιμοποιήσει μια συναλλαγή. Περάστε το nonce και τα
ποσά ως bigint στη TypeScript· ο παραγόμενος client απορρίπτει ένα number.
Αντικαταστήστε κάθε προσωρινή τιμή ..._HERE με μια πραγματική διεύθυνση πριν
εκτελέσετε τον κώδικα. Το address() επικυρώνει το base58 και πετάει σφάλμα σε
προσωρινή τιμή, ενώ το 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, έναν προορισμό
διακανονισμού για κάθε πλευρά και μια χρονική στιγμή πρώιμου διακανονισμού
(δείτε Σημειώσεις). Ο προορισμός είναι το σημείο όπου καταλήγουν τα έσοδα του
μέρους κατά τον διακανονισμό, για παράδειγμα το πορτοφόλι κατάθεσης ενός
επιμελητή (custodian)· από προεπιλογή είναι η δική του διεύθυνση του μέρους και
δεν αλλάζει μετά τη δημιουργία της εγγραφής. Οι επιστροφές χρημάτων και οι
ανακτήσεις πηγαίνουν πάντα στον δικό του token account του μέρους για το mint του
συγκεκριμένου σκέλους, ποτέ σε προορισμό, ανεξάρτητα από το ποιος χρηματοδότησε
το escrow.
Ο πληρωτής χρηματοδοτεί το rent για την εγγραφή, την ταφόπλακα (tombstone) του
nonce και τα δύο escrow. Κατά το κλείσιμο, αυτό το rent πηγαίνει σε όποιον
υπογράφει την εντολή κλεισίματος, όχι πίσω στον πληρωτή. Η ταφόπλακα δεν
κλείνει ποτέ, επομένως κάθε συναλλαγή αφήνει ένα μικρό μόνιμο υπόλοιπο. Όταν
προϋπολογίζετε τα 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?