CreateDvp 用于记录交易条款,并创建双方将要注资的两个托管账户。它不会转移任何代币。任何人都可以发送该指令,因此交易记录可能来自交易对手方、结算授权方的运营方或第三方,其他所有参与者在据此行动之前都会验证已存储的条款(参见 为交易腿注资)。
这些代码片段直接使用生成的客户端,并采用占位地址,以便清楚展示所涉及的账户和签名者。它们面向 devnet,程序在 devnet 上的部署地址与 mainnet-beta 相同。
安装
截至 2026 年 10 月 2 日,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
这些页面基于提交 df9919e 编写,TypeScript 使用 @solana/kit 8,Rust 使用 solana-* 2.x crate。
每个步骤页面中的代码片段都是前后衔接的:每个页面都会使用前面页面中定义的客户端、签名者和地址。payerSigner、userASigner、userBSigner 和 authoritySigner 是 TransactionSigner,分别对应 rent 支付方、交易双方和结算授权方,例如可通过 createKeyPairSignerFromBytes 获得。Rust 代码片段只构建指令,签名和发送由调用方负责。
派生地址
交易记录是基于结算授权方、交易双方、两个 mint 和一个 nonce 得出的 Program Derived Address。每个托管账户是该记录针对其中一个 mint 的 associated token account。这两种派生都不需要访问网络。
每笔交易都请使用随机的 64 位 nonce。由于创建无需许可,可预测的 nonce 会让第三方抢先占用交易原本要使用的地址。在 TypeScript 中,nonce 和金额请以 bigint 传入;生成的客户端会拒绝 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 字节的不透明引用、双方各自的结算目标地址,以及最早结算时间(参见“注意事项”)。结算目标地址是该方在结算时收到款项的位置,例如托管机构的充值钱包;默认为该方自己的地址,且记录创建后即固定不变。无论由谁为托管账户注资,退款和取回始终进入该方对应交易腿 mint 的自有 token account,而不会进入结算目标地址。
付款方为交易记录、nonce 墓碑账户和两个托管账户支付 rent。关闭时,这笔 rent 会退还给签署关闭指令的人,而不是付款方。墓碑账户永远不会被关闭,因此每笔交易都会留下少量永久余额。在预算付款方的 SOL 时,请使用 getMinimumBalanceForRentExemption RPC 方法推导 rent 金额,而不要硬编码。
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 代码片段需要在作用域内具备来自 @solana/kit 的 getProgramDerivedAddress 和 getAddressEncoder,以及来自客户端的 DVP_SWAP_PROGRAM_PROGRAM_ADDRESS。Rust builder 会将 System Program 和 Associated Token Program 保持为默认值。
注意事项
- 到期时间。 只有 Settle 会检查到期时间。到期后 Reclaim、Cancel 和 Reject 仍可使用,到期后才到账的存款可由对应交易腿的一方取回。到期时间最晚为创建后一年。
- 时间指网络时间。 程序读取的是链上时钟,它可能与实际时间不一致,有时相差数十秒,并且可能向前跳变。请在到期时间与预期结算时间之间留出余量,避免在任一边界附近注资或结算,也不要依赖到期时间来精确到秒地强制执行截止时间。程序 README 建议预留约一分钟的时钟漂移。
- 最早结算时间。 可选。若设置,则在此时间之前 Settle 会被拒绝,且该时间不得晚于到期时间。
- 创建时检查。 如果某一方不是由 System Program 所有的账户(这会排除 SPL Token 多签),Create 会失败并返回
PartyNotSignerCapable;其他失败情形还包括SettlementDestinationIsSwapDvp、SelfDvp、SameMint、ZeroAmount、SettlementAuthorityIsParty或ExpiryNotInFuture。完整列表见错误表。
后续步骤
- 为交易腿注资:各方将自己的交易腿转入该交易的托管账户
Is this page helpful?