Δημιουργία συναλλαγής

Το CreateDvp καταγράφει τους όρους μιας συναλλαγής και δημιουργεί τους δύο λογαριασμούς escrow που θα χρηματοδοτήσουν τα μέρη. Δεν μεταφέρει κανένα token. Μπορεί να το στείλει οποιοσδήποτε, επομένως η εγγραφή μπορεί να προέρχεται από έναν αντισυμβαλλόμενο, από τον operator της αρχής διακανονισμού ή από τρίτο μέρος, και κάθε άλλος συμμετέχων επαληθεύει τους αποθηκευμένους όρους πριν ενεργήσει με βάση αυτούς (δείτε χρηματοδότηση των σκελών).

Τα αποσπάσματα κώδικα χρησιμοποιούν απευθείας τον παραγόμενο client, με προσωρινές διευθύνσεις, ώστε να φαίνονται οι λογαριασμοί και οι υπογράφοντες που εμπλέκονται. Στοχεύουν το devnet, όπου το πρόγραμμα έχει αναπτυχθεί στην ίδια διεύθυνση με το mainnet-beta.

Εγκατάσταση

Από τις 2 Οκτωβρίου 2026 δεν έχει δημοσιευτεί κανένα πακέτο για τους clients του DvP. Και οι δύο παράγονται από το IDL του προγράμματος στο αποθετήριο και χρησιμοποιούνται από τον πηγαίο κώδικα.

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

Αυτές οι σελίδες έχουν γραφτεί με βάση το 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 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);

Το απόσπασμα 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. Ο πλήρης κατάλογος βρίσκεται στον πίνακα σφαλμάτων.

Επόμενα βήματα

Is this page helpful?

Πίνακας Περιεχομένων

Επεξεργασία Σελίδας
© 2026 Ίδρυμα Solana. Με επιφύλαξη παντός δικαιώματος.