交付对付款(DvP)程序

DvP 程序是 Solana 上用于交付对付款的标准程序,由 Solana 基金会维护,并经 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 墓碑和两个托管账户支付 rent(用于保持账户开启的 SOL 押金)。不会被记录为 rent 接收方:被关闭账户的 rent 归关闭该交易的人所有

程序 ID

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

该程序在两个集群上均可升级。上述数据是 solana program show 于 2026 年 10 月 2 日返回的结果;请运行同一命令以获取当前值。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 墓碑,它永远不会被关闭,因此各方、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 墓碑和两个托管账户。不转移任何代币
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
4LegNotFundedSettle 时某个托管账户的余额低于其约定数额
5ExpiryNotInFutureCreate 时到期时间不晚于当前网络时间
6EarliestAfterExpiryCreate 时最早结算时间晚于到期时间
7SelfDvpCreate 时 user_a 等于 user_b
8SameMintCreate 时 mint_a 等于 mint_b
9ZeroAmountCreate 时任意一条腿的数额为零
10BlockedMintExtensionCreate 或 Settle 时 mint 带有 TransferFee、InterestBearing、ScaledUiAmount 或 NonTransferable 扩展
11SettlementAuthorityIsPartyCreate 时结算授权方等于某一参与方
12SettlementAuthorityExecutableCreate 时结算授权方为可执行账户
13NonceAlreadyUsedCreate 重复使用了已有墓碑的 (seeds, nonce)
14ExpiryTooFarInFutureCreate 时到期时间超过当前时间一年以上
15RefStringTooLongCreate 时引用长度超过 64 字节
16DvpStillOpen交易仍处于开启状态时执行 Recover;请改用 Reclaim
17DvpNeverCreated使用不存在墓碑标记的 seeds 进行 Recover
18EscrowPreloadedWithLamportsCreate 时,非原生托管账户持有的 lamports 超过其 rent 豁免最低额度
19SettlementDestinationIsSwapDvpCreate 时,结算目标账户与交易记录相同
20SwapDvpPreloadedWithLamportsCreate 时,记录地址持有的 lamports 超过其 rent 储备
21RecipientAtaMismatch收款方或退款 token account 不再属于预期的钱包和 mint
22PartyNotSignerCapableCreate 时,某参与方不是系统程序拥有的、不可执行的账户
23MintAuthorityChangedSettle 时,某条交易腿的 mint authority 与创建时记录的值不同

事件与索引

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

支持的代币

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

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

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

被接受的这些 authority 可以在交易存续期间转移、冻结或暂停托管的代币(见下文)。托管账户无法配置为接收机密转账,因此机密交易腿上托管和结算的金额是公开的。

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

当目标账户或退款账户要求 memo 时,程序会在转账前调用 Memo 程序,因此每条将代币转出托管的指令都需要一个 memo_program 账户。

Mint 配置限制

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

默认冻结的 mint。 每笔交易的托管账户都是由 Program Derived Address 拥有的 associated token account,该地址对每笔交易都是全新的。在 Default Account State 设为冻结的 mint 上,托管账户创建时即处于冻结状态,向其转入资金的转账会失败,直到它被解冻。在 Token ACL 下,这意味着发行方或其过户代理需要在注资前准入该交易的托管账户:要么将交易记录的地址(即托管账户的所有者)加入允许列表,这样在 mint 的 Token ACL 配置启用无许可解冻的情况下任何人都可以解冻该托管账户;要么由 freeze authority 直接解冻。被冻结的托管账户还会阻止该交易腿上的 Settle、Reclaim、Cancel、Reject 和 Recover,因此在交易存续期间,freeze authority(对于带 hook 的 mint 还包括 hook authority)是受信任的一方。此处描述了冻结托管账户的路径,devnet 代码片段中未演示该路径。

限制

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

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

注资和结算前的验证

CreateDvp 是无需许可的:只有付款方签名,参与方和结算 authority 不需要签名。因此任何人都可以创建指定任意参与方和任意条款的记录。在注资之前以及结算之前,请读取 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 交易达到 finalized 时,才可视为交易已结算。这是网络的承诺级别(commitment level);它是否同时构成法律意义上的结算终局性,取决于各方的协议以及适用的法律制度。

版本管理

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 基金会版权所有