CreateDvpは取引条件を記録し、当事者が資金を預け入れる2つのエスクローアカウントを作成します。トークンの移動は行いません。誰でも送信できるため、レコードは取引相手、決済権限者のオペレーター、または第三者から作成される可能性があり、他のすべての参加者は、記録された条件に基づいて行動する前にその内容を検証します(各レッグへの入金を参照)。
スニペットでは、関与するアカウントと署名者が分かるよう、プレースホルダーのアドレスを使い、生成されたクライアントを直接使用しています。対象はdevnetで、プログラムはmainnet-betaと同じアドレスにデプロイされています。
インストール
2026年10月2日時点で、DvPクライアントのパッケージは公開されていません。どちらもリポジトリ内のプログラムのIDLから生成され、ソースから使用します。
git clone https://github.com/solana-foundation/dvp.gitcd dvp && git checkout df9919ed02c25a93620e7f6820107c9050ce3b92pnpm install && pnpm generate-clients && pnpm build# The client is clients/typescript/src; copy or link it into your project as ./dvppnpm add @solana/kit @solana/kit-plugin-rpc @solana/kit-plugin-signer @solana-program/token-2022 @solana-program/memo
これらのページは、コミットdf9919e、TypeScriptでは@solana/kit 8、Rustではsolana-* 2.xクレートを前提に書かれています。
各ステップのページのスニペットは引き継がれます。各ページでは、前のページで定義したクライアント、署名者、アドレスを使用します。payerSigner、userASigner、userBSigner、authoritySignerは、それぞれrentの支払者、2人の当事者、決済権限者のTransactionSignerです(例: createKeyPairSignerFromBytesで作成)。Rustのスニペットはinstructionsを構築するだけで、署名と送信は呼び出し側に任せます。
アドレスを導出する
取引レコードは、決済権限者、2人の当事者、2つのmint、およびnonceから導出されるProgram Derived Addressです。各エスクローは、いずれかのmintに対するレコードのassociated token accountです。どちらの導出もネットワークアクセスは不要です。
取引ごとにランダムな64ビットのnonceを使用してください。作成には許可が不要なため、nonceが予測可能だと、第三者が取引で使われるはずだったアドレスを先に占有できてしまいます。TypeScriptでは、nonceと金額をbigintで渡してください。生成されたクライアントはnumberを拒否します。
実行前に、各..._HEREプレースホルダーを実際のアドレスに置き換えてください。address()はbase58を検証してプレースホルダーでは例外をスローし、pubkey!はコンパイルに失敗します。
import { address, createClient } from "@solana/kit";import { solanaRpc } from "@solana/kit-plugin-rpc";import { signer } from "@solana/kit-plugin-signer";import { TOKEN_2022_PROGRAM_ADDRESS } from "@solana-program/token-2022";import { findSwapDvpEscrowAta, findSwapDvpPda } from "./dvp";const client = createClient().use(signer(payerSigner)).use(solanaRpc({rpcUrl: "https://api.devnet.solana.com",rpcSubscriptionsUrl: "wss://api.devnet.solana.com",}),);const settlementAuthority = address("SETTLEMENT_AUTHORITY_ADDRESS_HERE");const userA = address("ASSET_LEG_PARTY_ADDRESS_HERE");const userB = address("CASH_LEG_PARTY_ADDRESS_HERE");const mintA = address("ASSET_MINT_ADDRESS_HERE");const mintB = address("CASH_MINT_ADDRESS_HERE");const tokenProgramA = TOKEN_2022_PROGRAM_ADDRESS;const tokenProgramB = TOKEN_2022_PROGRAM_ADDRESS;// A fresh random nonce per trade, as a bigint.const nonce = new DataView(crypto.getRandomValues(new Uint8Array(8)).buffer,).getBigUint64(0);const [swapDvp] = await findSwapDvpPda({settlementAuthority,userA,userB,mintA,mintB,nonce,});const [escrowA] = await findSwapDvpEscrowAta({swapDvp,mint: mintA,tokenProgram: tokenProgramA,});const [escrowB] = await findSwapDvpEscrowAta({swapDvp,mint: mintB,tokenProgram: tokenProgramB,});
条件を記録する
CreateDvpは、基本単位での2つの金額、有効期限、nonce、そして4つの任意フィールドを受け取ります。任意フィールドは、最大64バイトの不透明な参照、各当事者の決済先、最早決済時刻です(注記を参照)。決済先は、決済時にその当事者の受取分が入る場所(例: カストディアンの入金用ウォレット)で、デフォルトは当事者自身のアドレスであり、レコードの作成後は変更できません。返金と回収は、エスクローに誰が資金を入れたかにかかわらず、決済先ではなく常に、そのレッグのmintに対する当事者自身のtoken accountに送られます。
支払者は、レコード、nonceトゥームストーン、および2つのエスクローのrentを負担します。クローズ時には、そのrentは支払者に戻らず、クローズinstructionに署名した人に渡ります。トゥームストーンはクローズされないため、取引ごとにわずかな残高が恒久的に残ります。支払者のSOLの予算を見積もる際は、rentの額をハードコードせず、getMinimumBalanceForRentExemption RPCメソッドで導出してください。
import { getCreateDvpInstruction } from "./dvp";const nowSeconds = BigInt(Math.floor(Date.now() / 1000));const createInstruction = getCreateDvpInstruction({payer: payerSigner,swapDvp,nonceTombstone: (await getProgramDerivedAddress({programAddress: DVP_SWAP_PROGRAM_PROGRAM_ADDRESS,seeds: ["nonce", getAddressEncoder().encode(swapDvp)],}))[0],settlementAuthority,userA,userB,mintA,mintB,dvpAtaA: escrowA,dvpAtaB: escrowB,tokenProgramA,tokenProgramB,amountA: 1_000_000_000n, // 1,000 units of a 6-decimal assetamountB: 998_500_000n, // 998.50 units of a 6-decimal cash tokenexpiryTimestamp: nowSeconds + 60n * 60n * 24n, // one daynonce,refString: "ORDER-2026-10-02-0001",userASettlementDestination: null, // defaults to userAuserBSettlementDestination: null, // defaults to userBearliestSettlementTimestamp: null,});const { context } = await client.sendTransaction([createInstruction]);console.log("Trade record:", swapDvp, "signature:", context.signature);
TypeScriptのスニペットでは、@solana/kitのgetProgramDerivedAddressとgetAddressEncoder、およびクライアントのDVP_SWAP_PROGRAM_PROGRAM_ADDRESSがスコープ内にある必要があります。Rustのビルダーでは、system programとassociated token programはデフォルトのままにします。
注記
- 有効期限。 有効期限を確認するのはSettleだけです。Reclaim、Cancel、Rejectは期限後も実行でき、期限後に入金された預け入れ分は、そのレッグの当事者が回収できます。有効期限は、作成から最大1年後まで設定できます。
- 時刻はネットワーク時刻です。 プログラムはオンチェーンのクロックを読み取ります。これは実際の時刻と、場合によっては数十秒ずれることがあり、前方にジャンプすることもあります。有効期限と想定する決済時刻の間に余裕を持たせ、どちらの境界付近でも入金や決済を避け、秒単位の期限を強制する目的で有効期限に頼らないでください。プログラムのREADMEでは、約1分のずれを見込むことを推奨しています。
- 最早決済時刻。 任意です。設定した場合、その時刻より前のSettleは拒否されます。また、有効期限より後に設定することはできません。
- 作成時のチェック。 当事者がsystem programが所有するアカウントでない場合(これによりSPL Tokenのマルチシグは除外されます)、Createは
PartyNotSignerCapableで失敗します。そのほか、SettlementDestinationIsSwapDvp、SelfDvp、SameMint、ZeroAmount、SettlementAuthorityIsParty、ExpiryNotInFutureでも失敗します。完全な一覧はエラー表にあります。
次のステップ
- 各レッグへの入金: 各当事者が自分のレッグを取引のエスクローに転送します
Is this page helpful?