四条指令可在不结算的情况下退还资金:交易未关闭时使用 Reclaim、Cancel 和 Reject,交易关闭后到账的存款则使用 Recover。它们均不检查到期时间。冻结的托管账户或已变更的 mint 仍可能锁住某一交易腿,详见末尾的说明。
| 指令 | 签名者 | 范围 | 之后的交易状态 |
|---|---|---|---|
ReclaimDvp | user_a 或 user_b | 签名者自己的交易腿 | 仍处于开放状态 |
CancelDvp | 结算权限方 | 两条交易腿 | 已关闭 |
RejectDvp | user_a 或 user_b | 两条交易腿 | 已关闭 |
RecoverDvp | user_a 或 user_b | 签名者的交易腿(关闭后) | 已关闭 |
无论哪种情况,托管账户中的所有余额都会退还给该交易腿的一方:托管账户 A 中的资产退至 user_a 的 mint_a token account,托管账户 B 中的资产退至 user_b 的 mint_b token account,无论是谁出资的。对于要求 memo 的退款账户,需提供 memo program account。
本页承接前面的步骤:创建交易中的客户端和签名者、为交易腿注资中的 userAAtaA 和 userBAtaB,以及结算交易中的 MEMO_PROGRAM_ID 和 authority 均沿用。
取回一条交易腿
Reclaim 会退还签名者的交易腿并保持交易开放,因此该交易腿可以重新注资。当问题出在对手方的交易腿上时使用它;Cancel 和 Reject 则会关闭整个交易。
import { MEMO_PROGRAM_ADDRESS } from "@solana-program/memo";import { getReclaimDvpInstruction } from "./dvp";// The asset-leg party takes its asset back.const reclaimInstruction = getReclaimDvpInstruction({signer: userASigner,swapDvp,mint: mintA,dvpSourceAta: escrowA,signerDestAta: userAAtaA,tokenProgram: tokenProgramA,memoProgram: MEMO_PROGRAM_ADDRESS,});await client.sendTransaction([reclaimInstruction]);
Cancel 或 Reject
Cancel 由结算权限方签名,Reject 由任一交易方签名。两者都会将每个托管账户中的余额退还给对应交易腿的一方,并关闭交易记录和两个托管账户,关闭账户所返还的 rent 归签名者所有。这两条指令所需账户相同,只有签名者不同。
import { getCancelDvpInstruction, getRejectDvpInstruction } from "./dvp";const unwindAccounts = {swapDvp,mintA,mintB,dvpAtaA: escrowA,dvpAtaB: escrowB,userAAtaA,userBAtaB,tokenProgramA,tokenProgramB,memoProgram: MEMO_PROGRAM_ADDRESS,legAExtrasCount: 0,};// Signed by the settlement authority:const cancelInstruction = getCancelDvpInstruction({settlementAuthority: authoritySigner,...unwindAccounts,});// Or signed by either party:const rejectInstruction = getRejectDvpInstruction({signer: userBSigner,...unwindAccounts,});
只要某条交易腿持有余额,其退款账户(userAAtaA、userBAtaB)就必须存在;退款账户未初始化会导致代币转账失败,并回滚该指令。从未注资的交易腿无需转账,即使账户缺失也不受影响。
追回迟到的存款
Settle、Cancel 和 Reject 都会关闭两个托管 token account,因此直接向已关闭的托管账户转账会失败。只有当某些操作先重新创建了托管账户,存款才会在交易关闭后到账,例如:注资交易在转账前包含了 create-idempotent 的 associated token account 指令,且在对手方发送 Reject 时该交易仍在传输中。此时代币会存放在一个对应交易已不存在的托管账户中:记录已消失,Reclaim 找不到它,而 nonce 墓碑标记会阻止在同一地址创建新交易并截获这笔存款。
使用 RecoverDvp 时,交易腿的一方需提供原始的 seed 输入(结算权限方、双方、两个 mint、nonce)。程序会重新派生记录地址,要求其墓碑标记作为交易曾存在的证明,将托管账户中的余额转入签名者自己的 token account,并关闭该账户,把托管账户的 rent 退还给签名者。签名者对应的交易腿由签名方决定:user_a 对应 mint_a,user_b 对应 mint_b。
import { getAddressEncoder, getProgramDerivedAddress } from "@solana/kit";import { DVP_SWAP_PROGRAM_PROGRAM_ADDRESS, getRecoverDvpInstruction } from "./dvp";const [nonceTombstone] = await getProgramDerivedAddress({programAddress: DVP_SWAP_PROGRAM_PROGRAM_ADDRESS,seeds: ["nonce", getAddressEncoder().encode(swapDvp)],});const recoverInstruction = getRecoverDvpInstruction({signer: userASigner,swapDvp,nonceTombstone,mint: mintA,dvpEscrowAta: escrowA,signerDestAta: userAAtaA,tokenProgram: tokenProgramA,memoProgram: MEMO_PROGRAM_ADDRESS,settlementAuthority,userA,userB,mintA,mintB,nonce,});await client.sendTransaction([recoverInstruction]);
能否追回取决于相关方是否保存了创建参数。为交易注资的系统应为每笔订单保存其 seed 输入,直到交易关闭且托管账户余额确认为空。
注意事项
- 被冻结的交易腿。 对于受管控的 mint,被冻结的托管账户会阻止这些指令执行的代币转账,直到冻结权限方解冻。参见概览。
- 创建后 mint 发生变更。 撤销路径会跳过扩展和 mint 权限检查,以免程序自身的检查阻碍追回。token program 自身的规则仍然适用。mint 只有在供应量为零时才能关闭,因此托管账户中仍有余额时它不会发生变化;但如果 mint 在迟到的存款到账前被关闭,并重新创建为 NonTransferable 或带有转账费用,则该存款可能无法转移或需要承担手续费。这就是支持的代币中列出的 mint 权限依赖。
后续步骤
- DvP 程序:概览、支持的代币以及 mint 配置约束
- 使用委托实现券款对付(DvP):通过委托授权完成相同的交换,无需程序
Is this page helpful?