入金はレッグのエスクローへのプレーンな TransferChecked で行うため、トークン転送を送信できるウォレットやカストディシステムであれば、どれでもレッグに入金できます。入金前の取引レコードの読み取りにのみ、DvPクライアントが必要です。プログラムがエスクロー残高を確認するのは決済時だけで、転送が反映された時点では何も検証されません。そのため、各当事者は転送の前に取引レコードを読み取ります。
このページは取引の作成の続きです。クライアントと署名者はそのまま引き継ぎます。
条件を検証する
レコードの作成は誰でも可能(パーミッションレス)で、エスクローアドレスはレコードとミントのみから導出され、条件からは導出されません。そのため、合意していない条件の取引に対してエスクローへの転送が行われてしまう可能性があり、その中には、相手方の受取金を第三のアドレスにリダイレクトする決済先も含まれます。入金する前に SwapDvp アカウントを読み取り、合意した取引と照合してください。
TypeScript の verifySwapDvp と Rust の verify::decode_swap_dvp_account は、アカウントが DvP プログラムの所有であること、サイズがちょうど 458 バイトであること、自身のフィールドから導出されるアドレスに存在することを確認します(fetchSwapDvpChecked が実行するのは最初の2つのチェックのみです)。プレーンな 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 ではなく、TransferChecked を使用してください。Pausable など、mint account に依存する拡張機能を持つ Token-2022 ミントは、それを指定しない転送を拒否します。また、トランスファーフックを持つミントでは、そのトークンの他の転送と同様に、フックのアカウントを解決して追加する必要があります。
アセットレッグの当事者が 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 になった時点で、そのレッグを入金済みとみなしてください。
注記
- ラップド SOL のレッグ。 プログラムはネイティブエスクローの残高を同期してから読み取るため、システム転送でラップド SOL のエスクローに送られた lamports もカウントされます。他のミントのエスクローには、決して SOL を送らないでください。プログラムは返却できず、アカウントをクローズした人に渡ってしまいます。
- ゲート付きミント。 Default Account State が frozen のミントでは、エスクローが凍結状態で作成され、発行者が凍結を解除するか許可するまで転送は失敗します。Token ACL との連携については、概要を参照してください。
- トランスファーフック。 フック付きのエスクローには、合意した金額ちょうどを入金してください。超過分により決済時に返金が必要になると、返金ではレッグのフックアカウントが異なる送信先と金額で再利用されるため、一部のフックはこれを拒否します。復旧するには、Reclaim を実行し、正確な金額で再入金して、もう一度決済します。
- 期限後の入金。
expiry_timestampを過ぎても、転送は止められません。転送自体は反映されますが、決済はもうできず、レッグの当事者は、取引がオープンの間は Reclaim、クローズ後は Recover で回収します。取引の巻き戻しを参照してください。 - どちらが先に入金するか。 プログラムは2つのレッグの順序を制御しません。順序は当事者間で決めることであり、ミント権限者(フリーズ、一時停止、またはトランスファーフック)がエスクローをブロックしていない限り、どちらの側も決済前ならいつでも自分のレッグを Reclaim できます。
次のステップ
Is this page helpful?