CreateDvp ghi lại các điều khoản của 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ày, nên bản ghi
có thể đến từ một đối tác, từ đơn vị vận hành của cơ quan thanh toán, hoặc từ bên thứ ba,
và mọi bên 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 tài sản cho các vế).
Các đoạn mã mẫu dùng trực tiếp client được tạo sinh, với địa chỉ giữ chỗ, để các tài khoản và bên ký liên quan hiển thị rõ ràng. Các đoạn mã nhắm tới 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ó gói nào được phát hành cho các client DvP. Cả hai đều được tạo sinh từ IDL của chương trình trong kho mã nguồn và được dùng trực tiếp từ mã nguồn.
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
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 bước đều nối tiếp nhau: 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 tương ứng với người trả rent,
hai bên giao dịch và cơ quan thanh toán, ví dụ được tạo từ
createKeyPairSignerFromBytes. Các đoạn mã Rust chỉ dựng các instruction và để
việc ký và gửi cho bên gọi.
Tạo (derive) các địa chỉ
Bản ghi giao dịch là một program-derived address được tạo 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 hai mint. Cả hai phép derive đều không cần truy cập mạng.
Hãy dùng 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 quyền, một
nonce có thể đoán trước sẽ cho phép bên thứ ba chiếm địa chỉ mà giao dịch định
dùng. Hãy truyền nonce và các số lượng dưới dạng bigint trong TypeScript; client được tạo sinh
từ chối 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ố lượng tính theo đơn vị cơ sở, một thời hạn hết hạn (expiry), nonce, và bốn
trường tùy chọn: một mã tham chiếu (reference) dạng mờ dài tối đa 64 byte, một đích nhận thanh toán
cho mỗi bên, và thời điểm thanh toán sớm nhất (xem Ghi chú). Đích nhận thanh toán là
nơi tiền thu được của bên đó được chuyển vào 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 sau khi
bản ghi được tạo. Các khoản hoàn lại và thu hồi luôn chuyển về token account của chính bên đó
cho mint của vế tương ứng, không bao giờ về đích nhận thanh toán, bất kể ai đã nạp vào escrow.
Người trả phí chi trả rent cho bản ghi, tombstone của nonce và cả hai escrow.
Khi đóng, khoản rent đó được chuyển cho người ký instruction đóng, chứ không hoàn lại
cho người trả phí. Tombstone không bao giờ bị đó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 tính các khoản rent
bằng phương thức RPC getMinimumBalanceForRentExemption thay vì viết 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 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);
Đ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. Trình dựng (builder) Rust giữ nguyên system program và associated token program
ở giá trị mặc định.
Ghi chú
- Thời hạn hết hạn (expiry). Chỉ Settle kiểm tra expiry. Reclaim, Cancel và Reject vẫn hoạt động sau thời điểm đó, và khoản nạp đến sau expiry có thể được bên của vế đó thu hồi. Expiry tối đa là một năm sau thời điểm 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 giờ thực, đôi khi hàng chục giây, và có thể nhảy tiến lên. Hãy chừa một khoảng đệm giữa expiry và thời điểm thanh toán dự kiến, tránh nạp hoặc thanh toán gần cả hai mốc, và đừng dựa vào expiry để áp đặt thời hạn chính xác đến từng giây. README của chương trình gợi ý dự phòng khoảng một phút độ lệch.
- Thời điểm 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à nó không được muộn hơn expiry.
- Kiểm tra khi tạo. Create thất bại với lỗi
PartyNotSignerCapablenếu một bên không phải là tài khoản thuộc sở hữu của system program (điều này loại trừ multisig của SPL Token), hoặc vớiSettlementDestinationIsSwapDvp,SelfDvp,SameMint,ZeroAmount,SettlementAuthorityIsParty, hayExpiryNotInFuture. Danh sách đầy đủ có trong bảng lỗi.
Các bước tiếp theo
- Nạp tài sản cho các vế: mỗi bên chuyển vế của mình vào escrow của giao dịch
Is this page helpful?