券款对付(DvP)程序

DvP 程序是 Solana 上券款对付的标准程序,由 Solana Foundation 维护,并经 Cantina 审计。双方就一笔交易达成一致后,各自通过普通的代币转账把自己的一侧资产发送到托管账户,再由结算授权方在一笔交易中完成两侧的结算,因此要么两侧同时转移,要么都不转移。结算授权方是交易中指定的第三方地址,例如促成该交易的交易场所或代理方。双方都无需编写或部署程序,任何能够发送标准代币转账(TransferChecked)的钱包或托管方,无需任何 DvP 专属集成即可为一条腿注资。在结算之前,任何一方都可以取回自己的那条腿,或撤销交易。

操作前请先读取记录

创建交易记录无需许可,因此记录本身并不能证明双方已达成一致。各方在注资前须读取已存储的条款,结算授权方在结算前也须如此(参见 验证)。

工作原理

每笔交易都会获得一条程序所有的记录和两个托管账户,每条腿各一个。资产腿参与方(user_a)和资金腿参与方(user_b)分别将代币转入对应腿的托管账户,以完成注资。结算授权方是唯一能够执行结算的签名者。结算时,每条腿约定的数量会转给对方,多余部分退还给该腿的所有者,同时关闭记录和两个托管账户。原子性来自 Solana 交易:两笔转账处于同一笔交易中,因此要么同时生效,要么都不生效。

所有退款、取回和余额返还都会进入参与方自己的 associated token account:mint_a 对应 user_a 的账户,mint_b 对应 user_b 的账户。托管方或资金管理系统可以从自己的账户为一条腿注资,但任何返还的资产都会进入参与方的账户,而不会回到托管方。每笔交易还会留下一个永久性标记账户(参见 状态)。

指南

这些分步页面将带你完成一笔交易从创建到结算的全过程,每条指令都提供 TypeScript 和 Rust 示例。

devnet 演示会完整运行一笔交易:创建交易、通过标准代币转账为两个托管账户注资,并在一笔交易中完成两条腿的结算。

在程序与委托之间选择

使用委托的券款对付(DvP)无需程序即可完成同样的交换:双方各自将自己的头寸委托给结算代理,由其在一笔交易中转移两条腿。

  • 程序方式无需委托,因此每个 token account 只能有一个委托方的限制不适用,并且结算授权方无法改变收益的去向。每笔交易都依赖一个可升级程序及其升级权限。
  • 委托模式不依赖任何程序,并且适用于带有 Scaled UI Amount 的 mint。该程序会拒绝此类 mint,这意味着任何基于 Mosaic 的 tokenized-security 模板创建的 mint 都无法使用(参见 mint 配置限制)。

角色

该程序是对称的。在其接口中,两个参与方分别是交付 mint_a 的 user_a 和交付 mint_b 的 user_b。README 和生成的客户端将 mint_a 称为资产腿,将 mint_b 称为资金腿,本文档沿用这一约定,但程序并不要求资金腿必须是现金类工具。两种资产或两种稳定币,同样可以按此方式结算。

角色对象签名内容说明
资产腿参与方user_a,交付 mint_a自己的注资转账;Reclaim、Reject、Recoverkeypair 钱包,或基于 PDA 的智能钱包(如多签金库)。SPL Token 多签账户或 program account 不能作为参与方:Create 会以 PartyNotSignerCapable 失败
资金腿参与方user_b,交付 mint_b自己的注资转账;Reclaim、Reject、Recover要求相同
结算授权方创建时指定的第三方地址Settle、Cancel唯一能够执行结算的签名者。它无法改变收益的去向,收益在创建时即已确定。结算或取消时,已关闭账户的 rent 归其所有
Rent 支付方签署 CreateDvp 的任何人Create为记录、nonce tombstone 以及两个托管账户支付 rent(使账户保持开启的 SOL 押金)。不会被记录为 rent 接收方:已关闭账户的 rent 归关闭交易的一方

程序 ID

dvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq
集群程序升级权限最近部署的 slot
mainnet-betadvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZqDXtFpbPjcn2hxPnw79x1Pfoj35vXh5AsWBkS37YnXMVv452,058,374
devnetdvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq5EX1zFeJWj4rGYZv432WnYw8CbaSUeTdxPx4r573UgYU479,376,863

该程序在两个集群上均可升级。上表数据为 2026 年 10 月 2 日 solana program show 的输出;如需当前值,请自行运行同一命令。devnet 构建版本早于各步骤页面所依据的提交(参见安装),但指令集和错误集相同。

生命周期

交易从 CreateDvp 开始处于开放状态,直到 Settle、Cancel 或 Reject 之一将其关闭。注资没有专门的指令:当某条腿的托管账户持有的数量不少于约定数量时,即视为该腿已注资。Reclaim 针对单条腿,执行后交易仍保持开放,因此某一条腿出现问题时,不会迫使另一方取消整笔交易。

状态

每笔交易对应一个由程序所有、大小为 458 字节的 SwapDvp 账户。其地址由结算授权方、两个参与方、两个 mint 和一个 nonce 派生(seeds 为 ["dvp", settlement_authority, user_a, user_b, mint_a, mint_b, nonce])。数量和时间存储在账户内部,不影响地址,因此必须读取而不能推断。一个小型标记账户,即位于 ["nonce", swap_dvp] 的 nonce tombstone,会随交易一同创建且永不关闭,因此参与方、mint、授权方和 nonce 的每种组合只能用于一笔交易。

字段类型含义
bumpu8PDA bump
user_a、user_bPubkey两个参与方
mint_a、mint_bPubkey资产腿和资金腿的 mint
settlement_authorityPubkey唯一能够执行结算或取消的签名者
token_program_a、token_program_bPubkey拥有各 mint 的 Token Program;一笔交易可以混用 SPL Token 和 Token-2022 的腿
amount_a、amount_bu64每条腿约定的数量,以 mint 的最小单位(基础单位)计
expiry_timestampi64超过该网络时间(链上时钟)后,Settle 会被拒绝。Reclaim、Cancel 和 Reject 仍然可用
nonceu64用于区分其余 seeds 相同的交易。仅可使用一次
ref_string[u8; 64]不透明的客户端引用,以零填充;程序从不读取它
user_a_settlement_destinationPubkey在 Settle 时接收资金腿;默认为 user_a
user_b_settlement_destinationPubkey在 Settle 时接收资产腿;默认为 user_b
mint_a_authority、mint_b_authorityPubkey创建时记录的 mint 权限;若其中任何一个发生变化,Settle 会失败
earliest_settlement_timestampOption<i64>如果已设置,则在该网络时间之前 Settle 会被拒绝

字段列表取自程序 IDL,对应创建交易中所指明的提交。

指令

指令签名者作用
CreateDvp任意支付方创建 SwapDvp 记录、其 nonce tombstone 以及两个托管账户。不转移任何代币
ReclaimDvpuser_a 或 user_b从托管账户中退回签名者自己的那条腿。交易保持开放,该腿可以重新注资
SettleDvp结算授权方将 amount_a 转给 user_b 的目标地址,将 amount_b 转给 user_a 的目标地址,把多余部分退还给该腿的参与方,并关闭记录和两个托管账户
CancelDvp结算授权方将托管账户 A 中的全部余额退还给 user_a,托管账户 B 中的全部余额退还给 user_b,并关闭记录和两个托管账户
RejectDvpuser_a 或 user_b效果与 Cancel 相同,但由参与方签名
RecoverDvpuser_a 或 user_b交易关闭后:将重新创建的托管账户中的资金转入签名者自己的 token account 并将其关闭。适用于在 Settle、Cancel 或 Reject 之后才到账的存款

无论由谁为托管账户注资,所有退款都会进入参与方自己持有的、对应该腿 mint 的 associated token account。

每条指令的账户和参数,请参见对应步骤的页面。

错误

代码错误触发时机
0SignerNotPartyReclaim、Reject 或 Recover 由非 user_a 或 user_b 的地址签名
1DvpExpired在 expiry_timestamp 之后执行 Settle
2SettlementAuthorityMismatchSettle 或 Cancel 由结算授权方以外的地址签名
3SettlementTooEarly在 earliest_settlement_timestamp 之前执行 Settle
4LegNotFunded在某个托管账户余额低于其约定数量时执行 Settle
5ExpiryNotInFuture创建时设置的到期时间不晚于当前网络时间
6EarliestAfterExpiry创建时设置的最早结算时间晚于到期时间
7SelfDvp创建时 user_a 与 user_b 相同
8SameMint创建时 mint_a 与 mint_b 相同
9ZeroAmount创建时任一条腿的数量为零
10BlockedMintExtension使用带有 TransferFee、InterestBearing、ScaledUiAmount 或 NonTransferable 的 mint 执行 Create 或 Settle
11SettlementAuthorityIsParty创建时结算授权方与某一参与方相同
12SettlementAuthorityExecutable创建时结算授权方是可执行账户
13NonceAlreadyUsed创建时重复使用已有 tombstone 的 (seeds, nonce)
14ExpiryTooFarInFuture创建时设置的到期时间超过一年之后
15RefStringTooLong创建时引用长度超过 64 字节
16DvpStillOpen在交易仍处于开放状态时执行 Recover;请改用 Reclaim
17DvpNeverCreated使用没有墓碑(tombstone)的 seeds 执行 Recover
18EscrowPreloadedWithLamports当非原生托管账户持有的 lamports 超过其 rent 豁免最低额时,执行 Create
19SettlementDestinationIsSwapDvp在结算目标地址与交易记录相同的情况下执行 Create
20SwapDvpPreloadedWithLamports当记录地址持有的 lamports 超过其 rent 储备时,执行 Create
21RecipientAtaMismatch收款方或退款方的 token account 不再属于预期的钱包和 mint
22PartyNotSignerCapable所指定的参与方不是由系统程序拥有的非可执行账户时,执行 Create
23MintAuthorityChanged当某一交易腿的 mint authority 与创建时记录的值不一致时,执行 Settle

事件与索引

该程序不发出任何链上事件。索引器或对账系统会读取 SwapDvp 账户状态以及两个托管账户的 token 余额,并使用 ref_string 将交易与其链下订单关联起来。由于记录在结算时会被关闭,事后需要查看交易条款的系统,应在读取时将其存储下来,或者从创建该交易的 transaction 中重建。

支持的 token

一笔交易可以将 SPL Token 交易腿与 Token-2022 交易腿配对;每条交易腿各自携带其 token program。支持封装的 SOL 交易腿。

交易腿 mint 上的 Token-2022 扩展程序行为
TransferFee、InterestBearing、ScaledUiAmount、NonTransferable在 Create 和 Settle 时被拒绝,并返回 BlockedMintExtension
PermanentDelegate、Pausable、DefaultAccountState、freeze authority、MintCloseAuthority允许;各权限方均可对托管的 token 进行操作
ConfidentialTransfer允许;该交易腿上的金额是公开的
TransferHook支持,每条交易腿最多 32 个额外账户
目标账户或退款账户上的 MemoTransfer允许;程序会在转账前发出所需的 memo

转账手续费会使接收方收到的金额低于约定金额。Interest Bearing 和 Scaled UI Amount 不会改变原始金额,但其利率或乘数可能在创建与结算交易之间发生变化,从而改变约定金额的显示方式。NonTransferable 会使两条交易腿都被锁死。

在交易存续期间(见下文),被允许的权限方可以转移、冻结或暂停托管的 token。托管账户无法配置为接收保密转账,因此保密交易腿上的托管金额和结算金额都是公开的。

对于带 hook 的 mint,客户端会解析该 hook 的额外账户,并将其附加到 Settle、Cancel、Reject、Reclaim 和 Recover 中。每条交易腿 32 个的上限包含 hook 程序及其验证账户。如果 hook 权限方将 hook 的账户列表扩充到超过该上限,则该交易腿上的所有转账(包括 Reclaim、Cancel、Reject 和 Recover)都会失败,直到该权限方撤销此更改。当两条交易腿都带有 hook 时,transaction 的账户数量限制可能会先于每条交易腿的上限生效。

当目标账户或退款账户要求 memo 时,程序会在转账前调用 Memo 程序,这就是为什么每个将 token 转出托管账户的指令都需要一个 memo_program 账户。

Mint 配置约束

Scaled UI Amount。 Mosaic(Solana Foundation 的发行工具包)中的 tokenized-security 模板始终会添加 Scaled UI Amount,因此程序不接受由该模板创建的 mint 作为交易腿。供本程序使用的 mint 可以在不带该扩展的情况下创建(参见 Token Extensions);使用委托的 DvP 通过委托授权进行结算,不适用此项检查。

默认冻结的 mint。 每笔交易的托管账户都是一个 associated token account,其所有者是为该交易新生成的 program-derived address。在 Default Account State 设为冻结的 mint 上,托管账户创建后即处于冻结状态,在其解冻之前,向其注资的转账会失败。在 Token ACL 下,这意味着发行方或其过户代理必须在注资前准入该交易的托管账户:可以将交易记录的地址(即托管账户的所有者)加入允许列表,这样在 mint 的 Token ACL 配置启用了无许可解冻的情况下,任何人都可以解冻该托管账户;也可以由 freeze authority 直接将其解冻。冻结的托管账户还会阻止该交易腿上的 Settle、Reclaim、Cancel、Reject 和 Recover,因此在交易存续期间,freeze authority(对于带 hook 的 mint,还包括 hook 权限方)是一个受信任的一方。冻结托管账户的路径在此处仅作说明,devnet 代码片段中并未演示。

限制

  • 没有撮合、价格发现或订单簿。参与方在账本之外商定条款,并由其中一方将其记录下来。
  • 没有净额结算,且仅支持双边:一条记录就是两方之间的一笔交易。
  • 不支持部分成交。Settle 会精确转移每条交易腿的约定金额,并将任何多余部分退还给该交易腿的参与方。
  • 没有账本之外的交易腿。两条交易腿都是 Solana 上的 token account;基于现有渠道的现金交易腿单独结算并进行对账。
  • 没有资格检查、允许列表或 KYC。持有人资格由 mint 自身的控制机制来强制执行。
  • 没有自动结算。结算授权方必须签名。
  • 没有事件,除交易手续费和 rent 外也没有协议费用。

该程序消除了双方之间的本金风险:除非两条交易腿同时转移,否则任何一条都不会转移。但它并不能消除任一 token 的发行方信用风险或赎回风险、mint 的冻结、暂停或永久委托权限对托管 token 进行操作的可能性(参见 支持的 token),也不能消除对结算授权方能够随时签名的依赖。

注资和结算前的验证

CreateDvp 是无许可的:只有付款方需要签名,参与方和结算授权方无需签名。因此任何人都可以创建指定任意参与方和任意条款的记录。在注资之前以及结算之前,请读取 SwapDvp 账户并确认:

  1. 该账户由 DvP 程序拥有,且大小恰好为 458 字节。不属于该程序的账户可能被填入能够解码成真实记录的字节。
  2. user_a、user_b、mint_a、mint_b 和 settlement_authority 与约定的交易一致。
  3. amount_a、amount_b、expiry_timestamp 和 earliest_settlement_timestamp 与约定的条款一致。
  4. 两个结算目标地址均为约定的地址。伪造的记录可能会重定向收益。

为各交易腿注资列出了生成的客户端中带检查的读取器,并通过代码展示了该检查。

当 Settle transaction 达到 finalized 时,即可将交易视为已结算。这是网络层面的确认级别;它是否同时构成法律意义上的结算终局性,取决于各方的协议以及适用的法律制度。

版本控制

SwapDvp 账户不包含版本字段。其布局固定为 458 字节(SwapDvp::LEN),带检查的读取器会拒绝任何其他长度的账户。IDL 自带版本号,截至 2026 年 10 月 2 日为 0.1.0。

该程序在两个集群上均可升级(参见 Program ID)。交易记录仅在结算、取消或拒绝之前存在,因此请查看代码仓库,了解升级如何处理未结交易,并根据已部署版本的 IDL 重新生成客户端。

代码仓库与审计

源代码、IDL、生成的客户端和测试均位于 solana-foundation/dvp,采用 MIT 许可证。该程序是一个 no_std 的 Pinocchio 程序;客户端由 Codama 根据 IDL 生成。漏洞报告请按照 SECURITY.md 的说明,通过代码仓库的安全公告链接提交。

该程序已由 Cantina 审计。

相关内容

Is this page helpful?

Table of Contents

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