注资就是向交易腿的托管账户发起一次普通的 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,并把多出的部分退还给该交易腿的一方,因此多转的资金不会丢失,但这会在结算时增加一次转账,并且可能在某些转账钩子下失败(见下方说明)。
请使用 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?