取引を決済する

SettleDvpは、取引レコードに指定された決済権限者が署名します。1つのトランザクションの中で、アセットレッグのamount_aをキャッシュレッグ側の当事者の宛先へ、キャッシュレッグのamount_bをアセットレッグ側の当事者の宛先へ移動します。いずれかのエスクローに余剰分があれば、そのレッグの当事者へ返却し、レコードと両方のエスクローをクローズします。これらの送金のいずれかが完了できない場合、instructionは失敗し、何も移動しません。

このページは、取引の作成とレッグへの資金拠出の続きです。クライアント、署名者、trade、および各当事者のtoken accountは引き継がれます。

署名前に再検証する

決済権限者は両レッグを移動するトランザクションに署名するため、署名の前に各当事者と同じ読み取りを行います。対象は、オーナー、サイズ、当事者、ミント、金額、有効期限、および両方の宛先です(レッグへの資金拠出を参照)。プログラム自身も決済時に、各ミントが作成時に記録されたtoken programに引き続き属していること、禁止されている拡張機能を持たないこと、取引作成時と同じミント権限を持つことを再確認します。そのため、禁止された拡張機能付き、別のtoken program、または別のミント権限で再作成されたミントは、決済できません。

受取側アカウントを作成する

決済では、プログラムが作成しない4つのtoken accountに書き込みを行います。

アカウント受取内容オーナー
userADestinationAtaBキャッシュレッグ(amount_b)user_a_settlement_destination
userBDestinationAtaAアセットレッグ(amount_a)user_b_settlement_destination
userAAtaAアセットレッグの余剰分user_a
userBAtaBキャッシュレッグの余剰分user_b

余剰分用のアカウントが使われるのは、エスクローに合意額を超える残高がある場合のみです。ただし、そのミントを保有していれば誰でも、ごく少量のベース単位をエスクローに送るだけで余剰分を発生させることができます。返金先アカウントが存在しない場合、決済全体が失敗します。そのため、4つすべてを同じトランザクション内で冪等に作成してください。

import {
findAssociatedTokenPda,
getCreateAssociatedTokenIdempotentInstruction,
} from "@solana-program/token-2022";
const destinationA = t.userASettlementDestination; // defaults to userA
const destinationB = t.userBSettlementDestination; // defaults to userB
const [userADestinationAtaB] = await findAssociatedTokenPda({
mint: mintB,
owner: destinationA,
tokenProgram: tokenProgramB,
});
const [userBDestinationAtaA] = await findAssociatedTokenPda({
mint: mintA,
owner: destinationB,
tokenProgram: tokenProgramA,
});
// userAAtaA and userBAtaB are from fund the legs.
const createRecipientAccounts = [
{ ata: userADestinationAtaB, owner: destinationA, mint: mintB, tokenProgram: tokenProgramB },
{ ata: userBDestinationAtaA, owner: destinationB, mint: mintA, tokenProgram: tokenProgramA },
{ ata: userAAtaA, owner: userA, mint: mintA, tokenProgram: tokenProgramA },
{ ata: userBAtaB, owner: userB, mint: mintB, tokenProgram: tokenProgramB },
].map(({ ata, owner, mint, tokenProgram }) =>
getCreateAssociatedTokenIdempotentInstruction({
payer: authoritySigner,
ata,
owner,
mint,
tokenProgram,
}),
);

決済

legAExtrasCountは、固定リストの後ろに追加されたアカウントのうち、いくつがアセットレッグのトランスファーフックに属するかをプログラムに伝えます。残りはキャッシュレッグのものです。トランスファーフックを持たないミントの場合は、0を渡し、何も追加しません。memo program accountはinstructionで必須であり、メモが必要な宛先に対してのみ使用されます。

import { MEMO_PROGRAM_ADDRESS } from "@solana-program/memo";
import { getSettleDvpInstruction } from "./dvp";
const settleInstruction = getSettleDvpInstruction({
settlementAuthority: authoritySigner,
swapDvp,
mintA,
mintB,
dvpAtaA: escrowA,
dvpAtaB: escrowB,
userADestinationAtaB,
userBDestinationAtaA,
userAAtaA,
userBAtaB,
tokenProgramA,
tokenProgramB,
memoProgram: MEMO_PROGRAM_ADDRESS,
legAExtrasCount: 0,
});
const { context: settleContext } = await client.sendTransaction([
...createRecipientAccounts,
settleInstruction,
]);
const settleSignature = settleContext.signature;
console.log("Submitted:", settleSignature);
// sendTransaction returns at the client's commitment (confirmed by default).
// Wait for finalized before recording the trade as settled.
let finalized = false;
for (let attempt = 0; attempt < 30 && !finalized; attempt++) {
const {
value: [status],
} = await client.rpc.getSignatureStatuses([settleSignature]).send();
if (status?.err) {
throw new Error("Settle transaction failed");
}
finalized = status?.confirmationStatus === "finalized";
if (!finalized) {
await new Promise((resolve) => setTimeout(resolve, 2_000));
}
}
if (!finalized) {
// A closed record means it settled; an open one is safe to settle again.
throw new Error("Settle not finalized after 60s; read the trade record");
}
console.log("Settled:", settleSignature);

トランスファーフック

生成されたIDLには固定アカウントのみが記載されるため、生成されたビルダーはフックアカウントを認識しません。各フックミントの追加アカウントメタは、そのトークンを直接送金する場合と同じ方法で解決し、instructionの末尾に追加してください。順序は、アセットレッグのアカウントが先、キャッシュレッグのアカウントが後で、legAExtrasCountには最初のグループの数を設定します。TypeScriptでは、解決したメタをinstructionのaccounts配列にスプレッドします。Rustでは、ビルダーのadd_remaining_accountsを使用します。上限は、フックプログラムと検証アカウントを含め、1レッグあたり32アカウントです。両レッグにフックがある場合は、トランザクションのアカウント数の上限が先に制約になることがあります。プログラムは転送するすべてのアカウントからsignerフラグを取り除くため、フックがこの経路を通じて決済権限者の署名を取得することはできません。

結果を記録する

決済トランザクションがfinalizedに到達した時点で、取引は決済済みとして扱ってください。決済後はレコードと両方のエスクローが存在しなくなるため、後で条件が必要なシステムは、決済前の読み取り結果、または作成時のトランザクションから条件を保存しておきます。クローズされたアカウントのrentは、決済権限者に渡っています。

決済エラー

LegNotFundedは、エスクローの残高が合意額に満たないことを意味します。DvpExpiredとSettlementTooEarlyは、時間枠を逃したことを意味します。MintAuthorityChangedとBlockedMintExtensionは、作成後にミントが変更されたことを意味します。RecipientAtaMismatchは、宛先または返金先のtoken accountが存在しないか、想定されたオーナーとミントに属していないことを意味し、多くの場合、上記のアカウント作成ステップを省略したことが原因です。いずれの場合も何も移動しておらず、当事者はミントの権限による制約を受けつつ、取引を巻き戻すことができます(取引の巻き戻しを参照)。

次のステップ

  • 取引の巻き戻し: 取引が決済されない場合に、資金拠出済みのレッグを回収する

Is this page helpful?

目次

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