创建交易

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 代码片段只负责构建指令,签名和发送由调用方完成。

派生地址

交易记录是一个 Program Derived Address,由结算授权方、交易双方、两种 mint 以及一个 nonce 派生而来。每个托管账户是交易记录针对其中一种 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 构建器会将 System Program 和 Associated Token Program 保持为默认值。

注意事项

  • 到期时间。 只有 Settle 会检查到期时间。Reclaim、Cancel 和 Reject 在到期后仍可使用,到期后才到账的存款也可由对应交割腿的一方收回。到期时间最晚为创建后一年。
  • 时间指的是网络时间。 程序读取的是链上时钟,它可能与实际时钟时间存在偏差,有时相差数十秒,还可能向前跳变。请在到期时间与预期结算时间之间留出余量,避免在任一时间边界附近注资或结算,也不要依赖到期时间来精确到秒地强制执行截止期限。程序的 README 建议预留约一分钟的时钟漂移。
  • 最早结算时间。 可选。若已设置,则在该时间之前 Settle 会被拒绝,且该时间不得晚于到期时间。
  • 创建时检查。 如果出现以下情况,Create 会失败:某一方不是由 System Program 拥有的账户(这会排除 SPL Token 多签账户),错误为 PartyNotSignerCapable;或出现 SettlementDestinationIsSwapDvp、SelfDvp、SameMint、ZeroAmount、SettlementAuthorityIsParty 或 ExpiryNotInFuture。完整列表见错误表。

后续步骤

Is this page helpful?

Table of Contents

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