Delivery vs Payment(DvP)プログラム

DvPプログラムは、Solana上のDelivery versus Payment(資産と代金の同時決済)の標準プログラムで、Solana Foundationが保守し、Cantinaが監査しています。2者の当事者が取引に合意し、それぞれが通常のトークン転送で自分のレッグをエスクローアカウントに送ると、決済権限者が両方のレッグを1回のトランザクションで決済します。そのため、両方が移動するか、どちらも移動しないかのいずれかになります。決済権限者は、取引の手配を行った取引所やエージェントなど、取引で指定された第三のアドレスです。どちらの当事者もプログラムを作成・デプロイする必要はなく、標準のトークン転送(TransferChecked)を送信できるウォレットやカストディアンであれば、DvP固有の連携なしでレッグに入金できます。決済までは、いずれの当事者も自分のレッグを引き戻したり、取引を取り消したりできます。

操作する前にレコードを確認してください

取引レコードの作成にはパーミッションが不要なため、レコードの存在は合意の証明にはなりません。各当事者は入金前に保存された条件を確認し、決済権限者は決済前に確認してください(検証を参照)。

仕組み

取引ごとに、プログラム所有のレコードと、レッグごとに1つずつ、計2つのエスクローアカウントが作成されます。アセットレッグの当事者(user_a)とキャッシュレッグの当事者(user_b)は、それぞれ自分のレッグ用のエスクローアカウントにトークンを転送して入金します。決済できる署名者は決済権限者のみです。決済では、各レッグの合意された数量が相手側に転送され、余剰分はそのレッグを所有する当事者に返却され、レコードと両方のエスクローがクローズされます。アトミック性はSolanaのトランザクションによって実現されます。両方の転送が1つのトランザクションに含まれるため、両方が反映されるか、どちらも反映されないかのいずれかになります。

返金、引き戻し、余剰分の返却はすべて、当事者自身のassociated token accountに送られます。mint_aについてはuser_aのアカウント、mint_bについてはuser_bのアカウントです。カストディアンやトレジャリーシステムは自身のアカウントからレッグに入金できますが、返却されるものは当事者のアカウントに入り、カストディアンには戻りません。また、各取引では恒久的なマーカーアカウントが残ります(Stateを参照)。

ガイド

ステップごとのページでは、1件の取引を作成から決済まで順に説明し、各instructionについてTypeScriptとRustの例を示します。

devnetデモでは、取引をエンドツーエンドで実行します。取引を作成し、標準のトークン転送で両方のエスクローに入金し、両方のレッグを1回のトランザクションで決済します。

プログラムとデリゲーションの選択

デリゲーションを使ったDelivery vs Payment(DvP)では、プログラムを使わずに同じ交換を決済します。各当事者が自分のポジションを決済エージェントにデリゲートし、エージェントが両方のレッグを1回のトランザクションで移動します。

  • プログラムはデリゲーションを必要としないため、token accountごとにデリゲートは1つまでという制限が適用されず、決済権限者が受取先を変更することもできません。ただし、各取引はアップグレード可能なプログラムとそのアップグレード権限に依存します。
  • デリゲーションパターンはプログラムへの依存がなく、Scaled UI Amountを持つミントでも動作します。プログラムはそのようなミントを拒否するため、Mosaicのtokenized-securityテンプレートから作成されたミントはすべて対象外となります(ミント設定の制約を参照)。

ロール

このプログラムは対称的な設計です。インターフェースでは、2者の当事者は、mint_aを引き渡すuser_aと、mint_bを引き渡すuser_bです。READMEと生成されたクライアントではmint_aをアセットレッグ、mint_bをキャッシュレッグと呼んでおり、これらのページもその慣例に従っていますが、キャッシュレッグが現金性の資産である必要はプログラム上ありません。2つのアセットや2つのステーブルコインでも、同じように決済されます。

ロール対象者署名備考
アセットレッグの当事者user_a、mint_aを引き渡す自身の入金転送、Reclaim、Reject、Recoverkeypairウォレット、またはマルチシグボールトなどのPDAベースのスマートウォレット。SPL Tokenのマルチシグアカウントやprogram accountは当事者になれません。CreateはPartyNotSignerCapableで失敗します
キャッシュレッグの当事者user_b、mint_bを引き渡す自身の入金転送、Reclaim、Reject、Recover同じ要件
決済権限者作成時に指定される第三のアドレスSettle、Cancel決済できる唯一の署名者です。受取先は作成時に固定されているため、変更することはできません。決済またはキャンセル時に、クローズされたアカウントのrentを受け取ります
rentの支払者CreateDvpに署名した者Createレコード、nonce tombstone、両方のエスクローのrent(アカウントを維持するためのSOLのデポジット)を負担します。rentの受取人としては記録されません。クローズされたアカウントのrentは、取引をクローズした者に渡ります

プログラムID

dvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq
クラスタープログラムアップグレード権限最終デプロイslot
mainnet-betadvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZqDXtFpbPjcn2hxPnw79x1Pfoj35vXh5AsWBkS37YnXMVv452,058,374
devnetdvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq5EX1zFeJWj4rGYZv432WnYw8CbaSUeTdxPx4r573UgYU479,376,863

このプログラムは両方のクラスターでアップグレード可能です。上記の数値は、2026年10月2日時点でsolana program showが報告した内容です。最新の値は同じコマンドを実行して確認してください。devnet版は、ステップページが基にしているコミット(インストールを参照)より古いものですが、instructionとエラーのセットは同じです。

ライフサイクル

取引はCreateDvpから、Settle、Cancel、Rejectのいずれかでクローズされるまでオープン状態です。入金には専用のinstructionはなく、エスクローアカウントが合意された数量以上を保持した時点でレッグが入金済みとなります。Reclaimはレッグ単位で行われ、取引はオープンのままとなるため、一方のレッグに問題があっても、もう一方の当事者が取引全体をキャンセルする必要はありません。

State

各取引には、プログラムが所有する458バイトのSwapDvpアカウントが1つあります。そのアドレスは、決済権限者、2者の当事者、2つのミント、およびnonceから導出されます(シード:["dvp", settlement_authority, user_a, user_b, mint_a, mint_b, nonce])。数量や時刻はアカウント内に保存され、アドレスには影響しないため、推測ではなく読み取って確認する必要があります。["nonce", swap_dvp]にある小さなマーカーアカウントであるnonce tombstoneは、取引とともに作成され、クローズされることはありません。そのため、当事者、ミント、権限者、nonceの各組み合わせは、1件の取引にのみ使用できます。

フィールド型意味
bumpu8PDAのbump
user_a、user_bPubkey2者の当事者
mint_a、mint_bPubkeyアセットレッグとキャッシュレッグのミント
settlement_authorityPubkey決済またはキャンセルできる唯一の署名者
token_program_a、token_program_bPubkey各ミントを所有するToken Program。1つの取引でSPL TokenとToken-2022のレッグを混在させることができます
amount_a、amount_bu64各レッグの合意された数量。ミントの最小単位(ベース単位)で表します
expiry_timestampi64このネットワーク時刻(オンチェーンクロック)を過ぎると、Settleは拒否されます。Reclaim、Cancel、Rejectは引き続き利用できます
nonceu64他のシードが同じ取引を区別します。1回限り有効です
ref_string[u8; 64]クライアント用の不透明な参照値で、ゼロパディングされます。プログラムはこれを読み取りません
user_a_settlement_destinationPubkeySettle時にキャッシュレッグを受け取ります。デフォルトはuser_aです
user_b_settlement_destinationPubkeySettle時にアセットレッグを受け取ります。デフォルトはuser_bです
mint_a_authority、mint_b_authorityPubkey作成時に記録されたミント権限。いずれかが変更されていると、Settleは失敗します
earliest_settlement_timestampOption<i64>設定されている場合、このネットワーク時刻より前のSettleは拒否されます

フィールド一覧は、取引を作成するで示されているコミット時点のプログラムのIDLから取得したものです。

instructions

instruction署名者効果
CreateDvp任意の支払者SwapDvpレコード、そのnonce tombstone、および両方のエスクローアカウントを作成します。トークンは移動しません
ReclaimDvpuser_aまたはuser_b署名者自身のレッグをエスクローから返却します。取引はオープンのままで、そのレッグには再度入金できます
SettleDvp決済権限者amount_aをuser_bの受取先に、amount_bをuser_aの受取先に転送し、余剰分をそのレッグの当事者に返却し、レコードと両方のエスクローをクローズします
CancelDvp決済権限者エスクローAが保持する分をuser_aに、エスクローBが保持する分をuser_bに返金し、レコードと両方のエスクローをクローズします
RejectDvpuser_aまたはuser_bCancelと同じ効果ですが、当事者が署名します
RecoverDvpuser_aまたはuser_b取引のクローズ後に、再作成されたエスクローの残高を署名者自身のtoken accountに移してクローズします。Settle、Cancel、Rejectの後に着金した入金分が対象です

返金はすべて、誰がエスクローに入金したかにかかわらず、そのレッグのミントに対する当事者自身のassociated token accountに送られます。

各instructionのアカウントと引数は、そのステップのページに記載されています。

エラー

コードエラー発生条件
0SignerNotPartyReclaim、Reject、Recoverが、user_aでもuser_bでもないアドレスによって署名された場合
1DvpExpiredexpiry_timestampの後にSettleした場合
2SettlementAuthorityMismatchSettleまたはCancelが、決済権限者以外のアドレスによって署名された場合
3SettlementTooEarlyearliest_settlement_timestampより前にSettleした場合
4LegNotFundedエスクローの保有量が合意された数量に満たない状態でSettleした場合
5ExpiryNotInFuture現在のネットワーク時刻以前の有効期限でCreateした場合
6EarliestAfterExpiry有効期限より後の最早決済時刻でCreateした場合
7SelfDvpuser_aとuser_bが同一のままCreateした場合
8SameMintmint_aとmint_bが同一のままCreateした場合
9ZeroAmountいずれかのレッグの数量がゼロのままCreateした場合
10BlockedMintExtensionTransferFee、InterestBearing、ScaledUiAmount、またはNonTransferableを持つミントでCreateまたはSettleした場合
11SettlementAuthorityIsParty決済権限者が当事者のいずれかと同一のままCreateした場合
12SettlementAuthorityExecutable実行可能アカウントを決済権限者に指定してCreateした場合
13NonceAlreadyUsedすでにtombstoneが存在する(seeds, nonce)を再利用してCreateした場合
14ExpiryTooFarInFuture1年を超える先の有効期限でCreateした場合
15RefStringTooLong64バイトを超える参照値でCreateした場合
16DvpStillOpen取引がまだオープンの状態でRecoverした場合。代わりにReclaimを使用してください
17DvpNeverCreatedtombstone のないシードで Recover を実行
18EscrowPreloadedWithLamports非ネイティブのエスクローが rent-exempt の最小額を超える lamports を保持している状態で Create を実行
19SettlementDestinationIsSwapDvp決済先をトレードレコードと同一にして Create を実行
20SwapDvpPreloadedWithLamportsレコードアドレスが rent reserve を超える lamports を保持している状態で Create を実行
21RecipientAtaMismatch受取人または返金先の token account が、想定されたウォレットとミントに属さなくなった場合
22PartyNotSignerCapableシステム所有の実行不可アカウントではない当事者を指定して Create を実行
23MintAuthorityChangedレッグのミント権限が作成時に記録された値と異なる状態で Settle を実行

イベントとインデックス作成

このプログラムはオンチェーンイベントを発行しません。インデクサーや照合システムは、SwapDvp アカウントの状態と2つのエスクローのトークン残高を読み取り、ref_string を使ってトレードとオフチェーンの注文を紐付けます。レコードは決済時にクローズされるため、後から取引条件が必要なシステムは、読み取った時点で条件を保存するか、トレードを作成したトランザクションから再構築する必要があります。

サポートされるトークン

トレードでは、SPL Token のレッグと Token-2022 のレッグを組み合わせることができます。各レッグはそれぞれ独自の token program を持ちます。Wrapped SOL のレッグもサポートされています。

レッグのミントにおける Token-2022 拡張機能プログラムの動作
TransferFee, InterestBearing, ScaledUiAmount, NonTransferableCreate および Settle 時に BlockedMintExtension で拒否されます
PermanentDelegate, Pausable, DefaultAccountState, freeze authority, MintCloseAuthority受け入れられます。各権限者はエスクローされたトークンに対して操作できます
ConfidentialTransfer受け入れられます。そのレッグの金額は公開されます
TransferHookサポートされます。1レッグあたり最大32個の追加アカウントまで
受取先または返金先アカウントでの MemoTransfer受け入れられます。プログラムは転送前に必要なメモを発行します

転送手数料があると、受取側は合意した金額に満たなくなります。Interest Bearing と Scaled UI Amount は元の金額(raw amount)を変更しませんが、トレードの作成から決済までの間にレートや乗数が変わる可能性があり、その結果、合意した金額の表示が変わります。NonTransferable は両方のレッグを動かせなくしてしまいます。

受け入れられる権限者は、トレードの存続期間中(下記参照)、エスクローされたトークンを移動、凍結、または停止できます。エスクローは confidential transfer を受け取るように設定できないため、confidential レッグでのエスクロー金額と決済金額は公開されます。

フック付きミントの場合、クライアントはフックの追加アカウントを解決し、Settle、Cancel、Reject、Reclaim、Recover に付加します。1レッグあたり32個という上限には、フックプログラムとその検証アカウントも含まれます。フック権限者がフックのアカウントリストをこの上限を超えて拡大すると、そのレッグでのすべての転送(Reclaim、Cancel、Reject、Recover を含む)が、権限者が元に戻すまで失敗します。両方のレッグにフックがある場合、1レッグあたりの上限より先にトランザクションのアカウント数の上限に達することがあります。

受取先または返金先アカウントがメモを必須とする場合、プログラムは転送前に Memo プログラムを呼び出します。そのため、エスクローからトークンを移動するすべての instructions は memo_program アカウントを受け取ります。

ミント設定の制約

Scaled UI Amount。 Solana Foundation の発行ツールキットであるMosaicの tokenized-security テンプレートは常に Scaled UI Amount を追加するため、このプログラムはそのテンプレートで作成されたミントをレッグとして受け入れません。このプログラム向けのミントは、この拡張機能なしで作成できます(Token Extensionsを参照)。委任を使った DvPは委任された権限を通じて決済されるため、このチェックは適用されません。

デフォルトで凍結されるミント。 各トレードのエスクローは、そのトレードごとに新規作成される program-derived address が所有する associated token account です。Default Account State が frozen に設定されたミントでは、エスクローは凍結された状態で作成され、解凍されるまでエスクローへの入金転送は失敗します。Token ACLの下では、発行者またはその transfer agent が、入金前にトレードのエスクローを承認する必要があります。方法は、トレードレコードのアドレス(エスクローの所有者)を許可リストに追加するか(ミントの Token ACL 設定で permissionless thaw が有効な場合は誰でもエスクローを解凍できます)、freeze authority に直接解凍してもらうかのいずれかです。凍結されたエスクローは、そのレッグでの Settle、Reclaim、Cancel、Reject、Recover もブロックするため、freeze authority(フック付きミントの場合はフック権限者も)は、トレードの存続期間中、信頼すべき当事者となります。凍結エスクローの経路はここで説明していますが、devnet のスニペットでは実行していません。

制限事項

  • マッチング、価格発見、オーダーブックはありません。当事者は台帳外で条件に合意し、いずれか一方がそれを記録します。
  • ネッティングはなく、二者間のみです。1つのレコードは2当事者間の1つのトレードです。
  • 部分約定はありません。Settle は各レッグの合意された金額ちょうどを移動し、余剰分はそのレッグの当事者に返却します。
  • 台帳外のレッグはありません。両方のレッグは Solana 上の token account です。既存の決済基盤上の現金レッグは別途決済され、照合されます。
  • 資格確認、許可リスト、KYC はありません。保有者の適格性は、ミント自体の制御によって強制されます。
  • 自動決済はありません。決済権限者が署名する必要があります。
  • イベントはなく、トランザクション手数料と rent 以外のプロトコル手数料もありません。

このプログラムは2当事者間の元本リスクを取り除きます。つまり、両方が動かない限り、どちらのレッグも動きません。一方で、いずれかのトークンの発行体の信用リスクや償還リスク、ミントの freeze、pause、permanent delegate 権限がエスクローされたトークンに対して行使される可能性(サポートされるトークンを参照)、そして決済権限者が署名できる状態であることへの依存は取り除きません。

入金および決済前の検証

CreateDvp は permissionless です。署名するのは支払者のみで、当事者と決済権限者は署名しません。そのため、誰でも任意の当事者と条件を指定したレコードを作成できます。入金前、および決済前に再度、SwapDvp アカウントを読み取り、次の点を確認してください。

  1. アカウントが DvP プログラムによって所有されており、サイズがちょうど458バイトであること。プログラムが所有していないアカウントには、本物のレコードのようにデコードされるバイト列を入れることができます。
  2. user_a、user_b、mint_a、mint_b、settlement_authority が合意したトレードと一致していること。
  3. amount_a、amount_b、expiry_timestamp、earliest_settlement_timestamp が合意した条件と一致していること。
  4. 両方の決済先が合意したアドレスであること。偽造されたレコードは収益を別の宛先へリダイレクトする可能性があります。

レッグへの入金では、生成されたクライアントのチェック付きリーダーを一覧にし、コードでのチェック方法を示します。

Settle トランザクションがfinalizedに達した時点で、トレードが決済されたものとみなしてください。これはネットワークのコミットメントレベルであり、法的な意味での決済完了(settlement finality)にもなるかどうかは、当事者間の合意および適用される法制度によって異なります。

バージョン管理

SwapDvp アカウントにはバージョンフィールドがありません。そのレイアウトは458バイト(SwapDvp::LEN)に固定されており、チェック付きリーダーは他の長さのアカウントを拒否します。IDL には独自のバージョンがあり、2026年10月2日時点では 0.1.0 です。

このプログラムは両方のクラスターでアップグレード可能です(Program IDを参照)。トレードレコードは、決済、キャンセル、または拒否されるまでのみ存在するため、アップグレードが未決済のトレードをどのように扱うかはリポジトリで確認し、クライアントはデプロイ済みビルドの IDL から再生成してください。

リポジトリと監査

ソース、IDL、生成されたクライアント、テストは、MIT ライセンスのもとsolana-foundation/dvpにあります。このプログラムは no_std の Pinocchio プログラムで、クライアントは Codama を使って IDL から生成されています。脆弱性の報告は、SECURITY.md に従い、リポジトリのセキュリティアドバイザリのリンクから行ってください。

このプログラムはCantinaによる監査を受けています。

関連情報

Is this page helpful?

© 2026 Solana Foundation. 無断転載を禁じます。