创建交易

CreateDvp 用于记录交易条款,并创建双方将要注资的两个托管账户。它不会转移任何代币。任何人都可以发送该指令,因此交易记录可能来自交易对手方、结算授权方的运营方或第三方,其他所有参与者在据此行动之前都会验证已存储的条款(参见 为交易腿注资)。

这些代码片段直接使用生成的客户端,并采用占位地址,以便清楚展示所涉及的账户和签名者。它们面向 devnet,程序在 devnet 上的部署地址与 mainnet-beta 相同。

安装

截至 2026 年 10 月 2 日,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

这些页面基于提交 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 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 代码片段需要在作用域内具备来自 @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?

Table of Contents

Edit Page
©️ 2026 Solana 基金会版权所有