为交易腿注资

注资只是向该交易腿的托管账户执行一次普通的 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 以内,并把多余部分退还给该交易腿的参与方,因此多出的部分不会丢失,但这会在结算时额外增加一次转账,并且可能在某些转账钩子(transfer hook)下失败(见下方说明)。

请使用 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,会以 LegNotFunded 失败,且不会有任何资金变动;代价只是一次失败的 Settle 和一次重试。请在转账达到 finalized 状态时,再将该交易腿视为已注资。

说明

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

后续步骤

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

Is this page helpful?

Table of Contents

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