Tạo một giao dịch

CreateDvp ghi lại các điều khoản của một giao dịch và tạo hai tài khoản escrow mà các bên sẽ nạp tài sản vào. Lệnh này không di chuyển token nào. Bất kỳ ai cũng có thể gửi lệnh, nên bản ghi có thể đến từ một đối tác, từ bên vận hành của cơ quan thanh toán, hoặc từ một bên thứ ba, và mọi người tham gia khác đều xác minh các điều khoản đã lưu trước khi hành động dựa trên chúng (xem nạp các chặng giao dịch).

Các đoạn mã mẫu sử dụng trực tiếp client được sinh tự động, với các địa chỉ giữ chỗ, để có thể thấy rõ các tài khoản và bên ký liên quan. Chúng nhắm đến devnet, nơi chương trình được triển khai tại cùng địa chỉ với mainnet-beta.

Cài đặt

Tính đến ngày 2 tháng 10 năm 2026, chưa có package nào được phát hành cho các client DvP. Cả hai đều được sinh ra từ IDL của chương trình trong repository và được sử dụng trực tiếp từ mã nguồn.

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

Các trang này được viết dựa trên commit df9919e, với @solana/kit 8 trong TypeScript và các crate solana-* 2.x trong Rust.

Các đoạn mã ở mọi trang của từng bước đều được kế thừa: mỗi trang dùng client, bên ký, và địa chỉ đã được định nghĩa ở các trang trước. payerSigner, userASigner, userBSigner và authoritySigner là các TransactionSigner dành cho người trả rent, hai bên giao dịch và cơ quan thanh toán, chẳng hạn được tạo từ createKeyPairSignerFromBytes. Các đoạn mã Rust chỉ dựng lệnh và để việc ký và gửi cho bên gọi.

Dẫn xuất các địa chỉ

Bản ghi giao dịch là một Program Derived Address được dẫn xuất từ cơ quan thanh toán, hai bên giao dịch, hai mint và một nonce. Mỗi escrow là associated token account của bản ghi cho một trong các mint. Cả hai phép dẫn xuất đều không cần truy cập mạng.

Hãy dùng một nonce 64-bit ngẫu nhiên cho mỗi giao dịch. Vì việc tạo giao dịch không cần cấp phép, một nonce có thể đoán trước cho phép bên thứ ba chiếm địa chỉ mà giao dịch định sử dụng. Hãy truyền nonce và các số tiền dưới dạng bigint trong TypeScript; client được sinh tự động từ chối kiểu number.

Hãy thay mỗi giá trị giữ chỗ ..._HERE bằng một địa chỉ thật trước khi chạy. address() kiểm tra base58 và báo lỗi khi gặp giá trị giữ chỗ, còn pubkey! sẽ không biên dịch được.

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

Ghi lại các điều khoản

CreateDvp nhận hai số tiền theo đơn vị cơ bản, một thời điểm hết hạn, nonce, và bốn trường tùy chọn: một mã tham chiếu mờ (opaque) dài tối đa 64 byte, một điểm đến thanh toán cho mỗi phía, và một thời điểm thanh toán sớm nhất (xem Ghi chú). Điểm đến là nơi khoản tiền thu về của bên đó được chuyển đến khi thanh toán, ví dụ ví nạp tiền của một đơn vị lưu ký; mặc định là địa chỉ của chính bên đó và được cố định khi bản ghi đã tồn tại. Các khoản hoàn tiền và thu hồi luôn chuyển về token account của chính bên đó cho mint của chặng giao dịch ấy, không bao giờ về điểm đến, bất kể ai đã nạp escrow.

Người trả phí sẽ chi trả rent cho bản ghi, nonce tombstone và cả hai escrow. Khi đóng, khoản rent đó thuộc về người ký lệnh đóng, không hoàn lại cho người trả phí. Tombstone không bao giờ được đóng, nên mỗi giao dịch để lại một số dư nhỏ vĩnh viễn. Khi lập ngân sách SOL cho người trả phí, hãy dẫn xuất số tiền rent bằng phương thức RPC getMinimumBalanceForRentExemption thay vì gán cứng.

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

Đoạn mã TypeScript cần có getProgramDerivedAddress và getAddressEncoder từ @solana/kit, cùng DVP_SWAP_PROGRAM_PROGRAM_ADDRESS từ client, trong phạm vi sử dụng. Builder Rust giữ nguyên System Program và Associated Token Program ở giá trị mặc định.

Ghi chú

  • Thời điểm hết hạn. Chỉ Settle kiểm tra thời điểm hết hạn. Reclaim, Cancel và Reject vẫn hoạt động sau đó, và khoản nạp đến sau khi hết hạn vẫn có thể được bên của chặng giao dịch ấy thu hồi. Thời điểm hết hạn tối đa là một năm sau khi tạo.
  • Thời gian là thời gian của mạng. Chương trình đọc đồng hồ onchain, có thể lệch so với thời gian thực, đôi khi hàng chục giây, và có thể nhảy tiến. Hãy chừa một khoảng đệm giữa thời điểm hết hạn và thời điểm dự kiến thanh toán, tránh nạp hoặc thanh toán sát hai mốc này, và đừng dựa vào thời điểm hết hạn để áp đặt hạn chót chính xác đến từng giây. README của chương trình gợi ý dự trù độ lệch khoảng một phút.
  • Thanh toán sớm nhất. Tùy chọn. Nếu được đặt, Settle sẽ bị từ chối trước thời điểm đó, và thời điểm này không được muộn hơn thời điểm hết hạn.
  • Kiểm tra khi tạo. Lệnh tạo thất bại với PartyNotSignerCapable nếu một bên không phải là tài khoản do System Program sở hữu (điều này loại trừ multisig của SPL Token), hoặc với SettlementDestinationIsSwapDvp, SelfDvp, SameMint, ZeroAmount, SettlementAuthorityIsParty, hay ExpiryNotInFuture. Danh sách đầy đủ nằm trong bảng lỗi.

Các bước tiếp theo

Is this page helpful?

Mục lục

Chỉnh sửa trang