撤销交易

有四条指令可以在不结算的情况下退还资金:交易开放期间使用 Reclaim、Cancel 和 Reject,交易关闭后才到账的存款则使用 Recover。它们都不会检查到期时间。冻结的托管账户或被更改的 mint 仍可能锁住某条交易腿,详见文末的说明。

指令签名者范围之后的交易状态
ReclaimDvpuser_a 或 user_b签名者自己的交易腿仍处于开放状态
CancelDvp结算授权方两条交易腿已关闭
RejectDvpuser_a 或 user_b两条交易腿已关闭
RecoverDvpuser_a 或 user_b关闭后签名者的交易腿此前已关闭

在每种情况下,托管账户中持有的全部资产都会退还给该交易腿所属的一方:托管账户 A 退还至 user_a 的 mint_a token account,托管账户 B 退还至 user_b 的 mint_b token account,无论最初由谁出资。对于需要备注的退款账户,需提供 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 由任意一方签名。两者都会把每个托管账户中持有的资产退还给该交易腿所属的一方,并关闭交易记录和两个托管账户,关闭账户所退还的 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,因此直接向已关闭的托管账户转账会失败。只有在有操作先重新创建托管账户的情况下,存款才会在关闭后到账,例如:某笔注资交易在转账之前包含了一条幂等创建 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 权限依赖。

后续步骤

Is this page helpful?

Table of Contents

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