CreateDvp записывает условия сделки и создаёт два эскроу-аккаунта, которые стороны будут пополнять. Токены он не перемещает. Отправить его может любой, поэтому запись может поступить от контрагента, оператора расчётного органа или третьего лица, а каждый другой участник проверяет сохранённые условия, прежде чем действовать на их основе (см. внесение активов в эскроу).
В примерах сгенерированный клиент используется напрямую, с адресами-заполнителями, чтобы были видны задействованные аккаунты и подписанты. Они рассчитаны на devnet, где программа развёрнута по тому же адресу, что и в mainnet-beta.
Установка
По состоянию на 2 октября 2026 года для клиентов 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 с @solana/kit 8 в TypeScript и крейтами solana-* 2.x в Rust.
Фрагменты кода на каждой странице шагов продолжают друг друга: каждая страница использует клиент, подписантов и адреса, определённые на предыдущих страницах. payerSigner, userASigner, userBSigner и authoritySigner — это TransactionSigner для плательщика rent, двух сторон и расчётного органа соответственно, например полученные из createKeyPairSignerFromBytes. Фрагменты на Rust собирают инструкции, а подписание и отправку оставляют вызывающей стороне.
Вычисление адресов
Запись о сделке — это program-derived address, вычисляемый из адреса расчётного органа, двух сторон, двух минтов и нонса. Каждый эскроу — это associated token account записи для одного из минтов. Оба вычисления не требуют доступа к сети.
Используйте для каждой сделки случайный 64-битный нонс. Поскольку создание не требует разрешений, предсказуемый нонс позволяет третьей стороне занять адрес, который собиралась использовать сделка. В TypeScript передавайте нонс и суммы как 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 принимает две суммы в базовых единицах, срок действия, нонс и четыре необязательных поля: непрозрачную ссылку размером до 64 байт, адрес назначения расчёта для каждой стороны и самое раннее время расчёта (см. «Примечания»). Адрес назначения — это место, куда при расчёте поступают средства соответствующей стороны, например депозитный кошелёк кастодиана; по умолчанию это собственный адрес стороны, и после создания записи он не меняется. Возвраты и изъятия средств всегда направляются на собственный token account стороны для минта соответствующей части сделки, а не на адрес назначения, независимо от того, кто пополнил эскроу.
Плательщик вносит rent за запись, tombstone-запись нонса и оба эскроу. При закрытии этот rent получает тот, кто подписывает закрывающую инструкцию, а не плательщик. Tombstone никогда не закрывается, поэтому после каждой сделки остаётся небольшой постоянный баланс. При планировании SOL плательщика определяйте суммы rent методом RPC getMinimumBalanceForRentExemption, а не задавайте их жёстко в коде.
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 в области видимости должны быть getProgramDerivedAddress и getAddressEncoder из @solana/kit, а также DVP_SWAP_PROGRAM_PROGRAM_ADDRESS из клиента. Билдер на Rust оставляет system program и associated token program со значениями по умолчанию.
Примечания
- Срок действия. Срок действия проверяет только Settle. Reclaim, Cancel и Reject работают и после его истечения, а депозит, поступивший после истечения срока, может вернуть сторона соответствующей части сделки. Срок действия не может превышать один год с момента создания.
- Время — это время сети. Программа читает ончейн-часы, которые могут расходиться с реальным временем, иногда на десятки секунд, и могут скачком уходить вперёд. Оставляйте запас между сроком действия и планируемым временем расчёта, избегайте пополнения и расчёта вблизи любой из границ и не полагайтесь на срок действия как на дедлайн с точностью до секунды. README программы советует закладывать около минуты расхождения.
- Самое раннее время расчёта. Необязательный параметр. Если он задан, Settle до этого момента отклоняется, и он не должен быть позже срока действия.
- Проверки при создании. Create завершается ошибкой
PartyNotSignerCapable, если сторона не является аккаунтом, принадлежащим system program (это исключает мультиподпись SPL Token), а также ошибкамиSettlementDestinationIsSwapDvp,SelfDvp,SameMint,ZeroAmount,SettlementAuthorityIsPartyилиExpiryNotInFuture. Полный список приведён в таблице ошибок.
Дальнейшие шаги
- Внесение активов в эскроу: каждая сторона переводит свою часть сделки в эскроу-аккаунт сделки
Is this page helpful?