各レッグに入金する

入金は、レッグのエスクローへの通常の TransferChecked です。そのため、トークン転送を送信できるウォレットやカストディシステムであれば、どれでもレッグに入金できます。DvPクライアントが必要なのは、入金前に取引レコードを読み取る場面だけです。プログラムがエスクロー残高を確認するのは決済時のみで、転送が着金した時点では何も確認されません。そのため、各当事者は転送前に取引レコードを読み取ってください。

このページは取引の作成の続きです。クライアントと署名者はそのまま引き継ぎます。

条件を検証する

レコードの作成には許可が不要で、エスクローのアドレスはレコードとmintのみから導出され、条件からは導出されません。そのため、合意していない条件に対して転送が着金してしまう可能性があり、その中には、相手方の受取分を第三のアドレスにリダイレクトする決済先も含まれます。入金する前に 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 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 に制限し、余剰分はレッグの当事者に返却されるため、余剰分が失われることはありません。ただし、決済時に2回目の転送が発生し、一部のtransfer hookでは失敗する可能性があります(下記の注意事項を参照)。

未チェックの Transfer ではなく TransferChecked を使用してください。Pausableなど、mint accountに依存する拡張機能を持つToken-2022のmintは、それを指定しない転送を拒否します。また、transfer hookを持つ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]);

キャッシュレッグの当事者は、userBAtaB、mintB、userBSigner、t.amountB、{ programAddress: tokenProgramB } を使って、エスクローBに対して同様に実行します。

confirmedの時点で確認された入金転送も、後から無効になる可能性があります。プログラムは決済時にオンチェーンでエスクロー残高を読み取るため、着金しなかった入金転送に対して送信したSettleは LegNotFunded で失敗し、何も移動しません。コストは、失敗したSettleと再試行のみです。転送が finalized になった時点で、そのレッグを入金済みとみなしてください。

注意事項

  • ラップドSOLのレッグ。 プログラムはネイティブエスクローの残高を同期した後で読み取るため、システム転送でラップドSOLのエスクローに送られたlamportも計上されます。他のmintのエスクローには、決してSOLを送らないでください。プログラムはそれを返却できず、アカウントをクローズした人の手に渡ります。
  • ゲート付きmint。 Default Account Stateがfrozenのmintでは、エスクローがfrozen状態で作成され、発行者が解除または許可するまで転送は失敗します。Token ACLとの連携については、概要で説明しています。
  • Transfer hook。 フックを持つエスクローには、合意した金額ちょうどで入金してください。余剰分があり決済時に返金が必要になった場合、返金ではレッグのフックアカウントが異なる宛先と金額で再利用されるため、拒否されるフックがあります。復旧手順は、Reclaimを実行し、正確な金額で再入金して、再度決済することです。
  • 期限後の入金。 expiry_timestamp を過ぎても、転送を止める仕組みはありません。転送は着金しますが、決済はもうできなくなり、レッグの当事者は、取引がオープンの間はReclaim、クローズ後はRecoverで取り戻します。取引の巻き戻しを参照してください。
  • 誰が先に入金するか。 プログラムは2つのレッグの順序を制御しません。順序は当事者間で決めることであり、mint権限者(freeze、pause、またはtransfer hook)がエスクローをブロックしていない限り、どちらの側も決済前ならいつでも自分のレッグを取り戻せます。

次のステップ

  • 取引を決済する:決済権限者が1回のトランザクションで両方のレッグを交換します
  • 取引を巻き戻す:取引が決済されない場合に、入金済みのレッグを取り戻します

Is this page helpful?

目次

ページを編集
© 2026 Solana Foundation. 無断転載を禁じます。