为交易腿注资

注资就是向交易腿的托管账户发起一次普通的 TransferChecked 转账,因此任何能够发送代币转账的钱包或托管系统都可以为交易腿注资。只有注资前读取交易记录这一步需要用到 DvP 客户端。程序只在结算时检查托管账户余额;转账到账时不会有任何检查,因此各方都应在转账前先读取交易记录。

本页承接创建交易;客户端和签名者沿用上文。

验证条款

创建记录无需许可,并且托管账户地址仅由记录和 mint 派生,与条款无关。因此,转入托管账户的资金可能对应你从未同意的条款,包括会把对手方所得款项重定向到第三方地址的结算目的地。注资前,请读取 SwapDvp 账户,并对照约定的交易进行核对。

TypeScript 中的 verifySwapDvp 和 Rust 中的 verify::decode_swap_dvp_account 会确认该账户归 DvP 程序所有、大小恰好为 458 字节,并且位于由其自身字段派生出的地址(fetchSwapDvpChecked 仅执行前两项检查)。普通的 fetchSwapDvp 和 decodeSwapDvp 读取器不执行上述任何检查。对各方、金额、时间和目的地的检查,由调用方对照约定的交易自行完成。

import { unwrapOption } from "@solana/kit";
import { verifySwapDvp } from "./dvp";
const trade = await verifySwapDvp(client.rpc, swapDvp);
const t = trade.data;
const expectedAmountA = 1_000_000_000n;
const expectedAmountB = 998_500_000n;
const expectedEarliestSettlement: bigint | null = null; // as agreed
const expectedExpiry = 1_798_761_600n; // as agreed, unix seconds
if (
t.userA !== userA ||
t.userB !== userB ||
t.mintA !== mintA ||
t.mintB !== mintB ||
t.settlementAuthority !== settlementAuthority ||
t.tokenProgramA !== tokenProgramA ||
t.tokenProgramB !== tokenProgramB ||
t.amountA !== expectedAmountA ||
t.amountB !== expectedAmountB ||
unwrapOption(t.earliestSettlementTimestamp) !== expectedEarliestSettlement ||
t.expiryTimestamp !== expectedExpiry ||
t.userASettlementDestination !== userA ||
t.userBSettlementDestination !== userB
) {
throw new Error("Stored terms do not match the agreed trade; do not fund");
}
if (t.expiryTimestamp <= BigInt(Math.floor(Date.now() / 1000)) + 60n) {
throw new Error("Trade expires too soon to fund safely");
}

如果记录由你的对手方或结算授权方创建,请将每个字段与你们在链下约定的条款逐一比对。如果记录由你自己创建,这次读取可确认其已按所发送的内容成功落地。

向托管账户转账

请按约定金额足额为交易腿注资。程序会将结算金额限制为 amount_x,并把多出的部分退还给该交易腿的一方,因此多转的资金不会丢失,但这会在结算时增加一次转账,并且可能在某些转账钩子下失败(见下方说明)。

请使用 TransferChecked,而不要使用未经检查的 Transfer。带有依赖 mint account 的扩展(例如 Pausable)的 Token-2022 mint 会拒绝未提供该账户的转账;带有转账钩子的 mint,则需要像该代币的其他转账一样,解析并附加钩子所需的账户。

资产腿的一方以 userASigner 签名,为托管账户 A 注资:

import {
findAssociatedTokenPda,
getTransferCheckedInstruction,
} from "@solana-program/token-2022";
const [userAAtaA] = await findAssociatedTokenPda({
mint: mintA,
owner: userA,
tokenProgram: tokenProgramA,
});
const [userBAtaB] = await findAssociatedTokenPda({
mint: mintB,
owner: userB,
tokenProgram: tokenProgramB,
});
const fundAssetLeg = getTransferCheckedInstruction({
source: userAAtaA,
mint: mintA,
destination: escrowA,
authority: userASigner,
amount: t.amountA,
decimals: 6,
}, { programAddress: tokenProgramA });
await client.sendTransaction([fundAssetLeg]);

资金腿的一方以同样的方式为托管账户 B 注资,使用 userBAtaB、mintB、userBSigner、t.amountB 和 { programAddress: tokenProgramB }。

在 confirmed 级别看到的注资转账仍有可能被取代。程序在结算时读取链上的托管账户余额,因此如果 Settle 所针对的注资转账最终未能落地,Settle 将以 LegNotFunded 失败,且不会有任何资金移动;代价只是一次失败的 Settle 和一次重试。请在转账达到 finalized 时,才将该交易腿视为已注资。

说明

  • Wrapped SOL 交易腿。 程序会在同步原生托管账户后读取其余额,因此通过系统转账发送到 wrapped-SOL 托管账户的 lamports 会被计入。切勿向其他任何 mint 的托管账户发送 SOL:程序无法将其退回,这些 SOL 将归关闭该账户的人所有。
  • 受限 mint。 对于默认账户状态(Default Account State)为冻结的 mint,托管账户创建时即处于冻结状态,在发行方解冻或准入之前,转账都会失败。概览介绍了与 Token ACL 的交互。
  • 转账钩子。 对带有钩子的托管账户,请按约定金额足额注资。如果多转的部分导致结算时需要退款,退款会复用该交易腿的钩子账户,但目的地和金额不同,部分钩子会拒绝这样的转账。恢复方式是:先 Reclaim,再按准确金额重新注资,然后再次结算。
  • 到期后注资。 在 expiry_timestamp 之后,没有任何机制会阻止转账。转账会到账,但结算已无法进行;该交易腿的一方可在交易仍处于开放状态时通过 Reclaim 取回,或在交易关闭后通过 Recover 取回。请参阅撤销交易。
  • 谁先注资。 程序不会规定两条交易腿的先后顺序,顺序由各方自行商定;在结算之前,任何一方都可以随时取回自己的交易腿,除非 mint 权限方(冻结、暂停或转账钩子)阻止了该托管账户。

后续步骤

  • 结算交易:结算授权方在一笔交易中交换两条交易腿
  • 撤销交易:在交易未能结算时,取回已注资的交易腿

Is this page helpful?

Table of Contents

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