注资只是向该交易腿的托管账户执行一次普通的 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 agreedconst expectedExpiry = 1_798_761_600n; // as agreed, unix secondsif (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?