Програма «Поставка проти оплати» (DvP)

Програма DvP — це стандартна програма для «поставки проти оплати» (delivery versus payment) у Solana, яку підтримує Solana Foundation і яку аудитувала Cantina. Дві сторони домовляються про угоду, кожна надсилає свою ногу на ескроу-акаунт звичайним переказом токенів, а розрахунковий орган виконує розрахунок за обома ногами в одній транзакції: або переміщуються обидві, або жодна. Розрахунковий орган — це третя адреса, указана в угоді, наприклад майданчик або агент, який її організував. Жодній зі сторін не потрібно писати чи розгортати програму, а гаманець або кастодіан, що може надіслати стандартний переказ токенів (TransferChecked), може профінансувати ногу без жодної інтеграції, специфічної для DvP. До розрахунку кожна зі сторін може забрати свою ногу назад або розірвати угоду.

Перевіряйте запис, перш ніж діяти на його підставі

Створення запису про угоду не потребує дозволів, тому запис не є доказом домовленості. Кожна сторона перевіряє збережені умови перед фінансуванням, а розрахунковий орган — перед розрахунком (див. перевірка).

Як це працює

Кожна угода отримує запис, що належить програмі, та два ескроу-акаунти — по одному на кожну ногу. Сторона активної ноги (user_a) і сторона грошової ноги (user_b) фінансують свою ногу, переказуючи токени на ескроу-акаунт цієї ноги. Розрахунковий орган — єдиний підписант, який може виконати розрахунок. Розрахунок переказує погоджену суму кожної ноги протилежній стороні, повертає будь-який надлишок стороні, якій належить нога, і закриває запис та обидва ескроу. Атомарність забезпечують транзакції Solana: обидва перекази відбуваються в одній транзакції, тож або набувають чинності обидва, або жоден.

Кожне повернення коштів, повернення ноги та повернення надлишку надходить на власний associated token account сторони: акаунт user_a для mint_a, акаунт user_b для mint_b. Кастодіан або казначейська система може профінансувати ногу зі свого акаунта, але все повернене потрапляє на акаунт сторони, а не назад до кастодіана. Кожна угода також залишає постійний акаунт-маркер (див. Стан).

Посібники

Сторінки з кроками проводять одну угоду від створення до розрахунку, із прикладами TypeScript і Rust для кожної інструкції.

Демо в devnet проводить угоду від початку до кінця: створює угоду, фінансує обидва ескроу стандартними переказами токенів і виконує розрахунок за обома ногами в одній транзакції.

Вибір між програмою та делегуванням

Поставка проти оплати (DvP) із делегуванням забезпечує той самий обмін без програми: кожна сторона делегує свою позицію розрахунковому агенту, який переміщує обидві ноги в одній транзакції.

  • Програма не потребує делегування, тому обмеження в одного делегата на token account не діє, а розрахунковий орган не може перенаправити виплати. Кожна угода залежить від оновлюваної програми та її authority для оновлення.
  • Патерн делегування не залежить від програми та працює з mint-ами, що мають Scaled UI Amount. Програма відхиляє такі mint-и, що виключає будь-який mint, створений із шаблону tokenized-security від Mosaic (див. обмеження конфігурації mint).

Ролі

Програма симетрична. В її інтерфейсі двоє сторін: user_a, який постачає mint_a, і user_b, який постачає mint_b. У README та згенерованих клієнтах mint_a називають активною ногою, а mint_b — грошовою ногою, і ці сторінки дотримуються цієї конвенції, але ніщо в програмі не вимагає, щоб грошова нога була грошовим інструментом. Два активи або два стейблкоїни розраховуються так само.

РольХтоПідписуєПримітки
Сторона активної ногиuser_a, постачає mint_aВласний переказ для фінансування; Reclaim, Reject, RecoverГаманець на основі keypair або смарт-гаманець на основі PDA, наприклад мультипідписне сховище. Мультипідписний акаунт SPL Token або program account не може бути стороною: Create завершується помилкою PartyNotSignerCapable
Сторона грошової ногиuser_b, постачає mint_bВласний переказ для фінансування; Reclaim, Reject, RecoverТа сама вимога
Розрахунковий органТретя адреса, указана під час створенняSettle, CancelЄдиний підписант, який може виконати розрахунок. Він не може перенаправити виплати, які фіксуються під час створення. Отримує rent закритих акаунтів, коли виконує Settle або Cancel
Платник rentТой, хто підписує CreateDvpCreateСплачує rent (депозит у SOL, що підтримує акаунт відкритим) за запис, nonce tombstone і обидва ескроу. Не фіксується як отримувач rent: rent закритих акаунтів отримує той, хто закриває угоду

ID програми

dvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq
КластерПрограмаAuthority для оновленняОстанній розгорнутий slot
mainnet-betadvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZqDXtFpbPjcn2hxPnw79x1Pfoj35vXh5AsWBkS37YnXMVv452,058,374
devnetdvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq5EX1zFeJWj4rGYZv432WnYw8CbaSUeTdxPx4r573UgYU479,376,863

Програму можна оновлювати в обох кластерах. Наведені вище значення — це те, що solana program show повернула 2 жовтня 2026 року; щоб отримати актуальні значення, виконайте ту саму команду. Збірка в devnet старіша за коміт, під який написано сторінки з кроками (див. Встановлення), але набір інструкцій і помилок у них однаковий.

Життєвий цикл

Угода залишається відкритою від CreateDvp, доки її не закриє одна з інструкцій Settle, Cancel або Reject. Фінансування не має власної інструкції: нога вважається профінансованою, коли її ескроу-акаунт містить принаймні погоджену суму. Reclaim діє на рівні окремої ноги й залишає угоду відкритою, тож проблема з однією ногою не змушує іншу сторону скасовувати всю угоду.

Стан

Кожна угода має один акаунт SwapDvp розміром 458 байтів, що належить програмі. Його адреса виводиться з розрахункового органу, двох сторін, двох mint-ів і nonce (seeds ["dvp", settlement_authority, user_a, user_b, mint_a, mint_b, nonce]). Суми й часові значення зберігаються всередині акаунта та не впливають на адресу, тому їх потрібно зчитувати, а не виводити. Невеликий акаунт-маркер, nonce tombstone за адресою ["nonce", swap_dvp], створюється разом з угодою та ніколи не закривається, тож кожну комбінацію сторін, mint-ів, органу й nonce можна використати лише для однієї угоди.

ПолеТипЗначення
bumpu8bump PDA
user_a, user_bPubkeyДві сторони
mint_a, mint_bPubkeyMint-и активної та грошової ноги
settlement_authorityPubkeyЄдиний підписант, який може виконати Settle або Cancel
token_program_a, token_program_bPubkeytoken program, якій належить кожен mint; в одній угоді можна поєднувати ноги SPL Token і Token-2022
amount_a, amount_bu64Погоджена сума кожної ноги в найменшій одиниці mint-а (базові одиниці)
expiry_timestampi64Після цього мережевого часу (годинник ончейну) Settle відхиляється. Reclaim, Cancel і Reject усе ще працюють
nonceu64Розрізняє угоди, що мають однакові інші seeds. Одноразовий
ref_string[u8; 64]Непрозоре посилання клієнта, доповнене нулями; програма його ніколи не читає
user_a_settlement_destinationPubkeyОтримує грошову ногу під час Settle; за замовчуванням user_a
user_b_settlement_destinationPubkeyОтримує активну ногу під час Settle; за замовчуванням user_b
mint_a_authority, mint_b_authorityPubkeyAuthority mint-ів, зафіксовані під час створення; Settle завершується помилкою, якщо будь-яка з них змінилася
earliest_settlement_timestampOption<i64>Якщо задано, Settle відхиляється до цього мережевого часу

Перелік полів узято з IDL програми для коміту, указаного в розділі Створення угоди.

Інструкції

ІнструкціяПідписантРезультат
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_bТой самий результат, що й у Cancel, але підписує сторона угоди
RecoverDvpuser_a або user_bПісля закриття угоди: виводить кошти зі створеного повторно ескроу на власний token account підписанта та закриває його. Охоплює депозит, що надійшов після Settle, Cancel або Reject

Кожне повернення коштів надходить на власний associated token account сторони для mint-а відповідної ноги, незалежно від того, хто профінансував ескроу.

Акаунти й аргументи кожної інструкції описано на сторінці відповідного кроку.

Помилки

КодПомилкаКоли
0SignerNotPartyReclaim, Reject або Recover підписано адресою, яка не є user_a чи user_b
1DvpExpiredSettle після expiry_timestamp
2SettlementAuthorityMismatchSettle або Cancel підписано адресою, яка не є розрахунковим органом
3SettlementTooEarlySettle до earliest_settlement_timestamp
4LegNotFundedSettle, поки одне з ескроу містить менше за погоджену суму
5ExpiryNotInFutureCreate із закінченням строку, що збігається з поточним мережевим часом або передує йому
6EarliestAfterExpiryCreate із найранішим часом розрахунку, що настає після закінчення строку
7SelfDvpCreate, де user_a дорівнює user_b
8SameMintCreate, де mint_a дорівнює mint_b
9ZeroAmountCreate з нульовою сумою на будь-якій із ніг
10BlockedMintExtensionCreate або Settle з mint-ом, що має TransferFee, InterestBearing, ScaledUiAmount або NonTransferable
11SettlementAuthorityIsPartyCreate, де розрахунковий орган збігається з однією зі сторін
12SettlementAuthorityExecutableCreate із виконуваним розрахунковим органом
13NonceAlreadyUsedCreate із повторним використанням (seeds, nonce), для якого вже існує tombstone
14ExpiryTooFarInFutureCreate із закінченням строку більш ніж на рік уперед
15RefStringTooLongCreate з посиланням довшим за 64 байти
16DvpStillOpenRecover, поки угода ще відкрита; використовуйте Reclaim
17DvpNeverCreatedRecover із seed-ами, для яких немає tombstone
18EscrowPreloadedWithLamportsCreate, коли ненативний escrow містить lamports понад мінімум для звільнення від rent
19SettlementDestinationIsSwapDvpCreate з адресою призначення розрахунку, що збігається із записом угоди
20SwapDvpPreloadedWithLamportsCreate, коли адреса запису містить lamports понад свій резерв rent
21RecipientAtaMismatchtoken account одержувача або повернення коштів більше не належить очікуваному гаманцю та mint
22PartyNotSignerCapableCreate зі стороною, яка не є невиконуваним акаунтом, що належить системній програмі
23MintAuthorityChangedSettle, коли mint authority складової відрізняється від значення, зафіксованого під час створення

Події та індексація

Програма не генерує ончейн-подій. Індексатор або система звірки читає стан акаунта SwapDvp і баланси токенів двох escrow та використовує ref_string, щоб зіставити угоду з її офчейн-ордером. Оскільки запис закривається під час розрахунку, система, якій згодом потрібні умови, зберігає їх під час зчитування або відновлює з транзакції, що створила угоду.

Підтримувані токени

Угода може поєднувати складову SPL Token зі складовою Token-2022; кожна складова має власну Token Program. Складові з Wrapped SOL підтримуються.

Розширення Token-2022 на mint складовоїПоведінка програми
TransferFee, InterestBearing, ScaledUiAmount, NonTransferableВідхиляється в Create і Settle з BlockedMintExtension
PermanentDelegate, Pausable, DefaultAccountState, freeze authority, MintCloseAuthorityПриймається; кожен authority може діяти щодо токенів в escrow
ConfidentialTransferПриймається; суми на цій складовій публічні
TransferHookПідтримується, до 32 додаткових акаунтів на складову
MemoTransfer на акаунті призначення або поверненняПриймається; програма додає потрібний memo перед переказом

Комісія за переказ залишила б отримувальну сторону з сумою, меншою за узгоджену. Interest Bearing і Scaled UI Amount не змінюють сирі суми, але їхня ставка або множник можуть змінитися між створенням і розрахунком угоди, що змінює те, як відображаються узгоджені суми. NonTransferable заблокував би обидві складові.

Допущені authority можуть переміщувати, заморожувати або зупиняти токени в escrow протягом усього життєвого циклу угоди (див. нижче). Escrow не можна налаштувати на отримання конфіденційних переказів, тому суми в escrow і розраховані суми на конфіденційній складовій є публічними.

Для mint із hook клієнт визначає додаткові акаунти hook і додає їх до Settle, Cancel, Reject, Reclaim та Recover. Ліміт у 32 на складову враховує програму hook і її акаунт валідації. Якщо hook authority збільшить список акаунтів hook понад цей ліміт, кожен переказ на цій складовій завершуватиметься помилкою, зокрема Reclaim, Cancel, Reject і Recover, доки authority не скасує зміну. Ліміт акаунтів у транзакції може спрацювати раніше за ліміт на складову, коли hook є на обох складових.

Коли акаунт призначення або повернення вимагає memo, програма викликає програму Memo перед переказом, тому кожна інструкція, що виводить токени з escrow, приймає акаунт memo_program.

Обмеження конфігурації mint

Scaled UI Amount. Шаблон tokenized-security у Mosaic, інструментарії випуску від Solana Foundation, завжди додає Scaled UI Amount, тому програма не приймає mint, створений із цього шаблону, як складову. Mint для цієї програми можна створити без цього розширення (див. Token Extensions); DvP із делегуванням виконує розрахунок через делегований authority і не застосовує цю перевірку.

Mint, заморожені за замовчуванням. Escrow кожної угоди — це associated token account, що належить Program Derived Address, новому для кожної угоди. На mint із Default Account State, встановленим у frozen, escrow створюється замороженим, і переказ для його фінансування завершується помилкою, доки його не розморозять. У рамках Token ACL це означає, що емітент або його transfer agent допускає escrow угоди перед фінансуванням: або додаючи адресу запису угоди (власника escrow) до allowlist, щоб будь-хто міг розморозити escrow, якщо конфігурація Token ACL для mint вмикає permissionless thaw, або через розморожування безпосередньо freeze authority. Заморожений escrow також блокує Settle, Reclaim, Cancel, Reject і Recover на цій складовій, тож freeze authority, а на mint із hook — hook authority, є довіреною стороною протягом життя угоди. Шлях із замороженим escrow описано тут, але він не використовується у фрагментах для devnet.

Обмеження

  • Немає зіставлення ордерів, визначення цін чи книги ордерів. Сторони узгоджують умови поза реєстром (ledger), і одна з них їх записує.
  • Немає неттінгу, лише двосторонні угоди: один запис — одна угода між двома сторонами.
  • Немає часткового виконання. Settle переміщує рівно узгоджену суму кожної складової та повертає будь-який надлишок стороні цієї складової.
  • Немає позаблокчейнової складової. Обидві складові — це token account на Solana; кешова складова через наявні платіжні системи розраховується окремо та звіряється.
  • Немає перевірки допустимості, allowlist чи KYC. Допустимість власників забезпечують власні засоби контролю mint.
  • Немає автоматичного розрахунку. Settlement authority має підписати.
  • Немає подій і немає комісії протоколу, окрім комісій за транзакції та rent.

Програма усуває принципальний ризик між двома сторонами: жодна складова не переміщується, якщо не переміщуються обидві. Вона не усуває кредитний ризик емітента чи ризик погашення за будь-яким із токенів, здатність freeze, pause чи permanent delegate authority mint діяти щодо токенів в escrow (див. підтримувані токени), а також залежність від того, що settlement authority буде доступний для підпису.

Перевірка перед фінансуванням і розрахунком

CreateDvp не потребує дозволів: підписує лише платник, а сторони та settlement authority — ні. Тож будь-хто може створити запис із будь-якими сторонами й умовами. Перед фінансуванням і ще раз перед розрахунком прочитайте акаунт 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. Це рівень підтвердження мережі (commitment level); чи позначає він також остаточність розрахунку для юридичних цілей, залежить від домовленостей сторін і застосовних режимів регулювання.

Версіонування

Акаунт SwapDvp не має поля версії. Його макет фіксований — 458 байтів (SwapDvp::LEN), а перевірювальні рідери відхиляють акаунт будь-якої іншої довжини. IDL має власну версію: 0.1.0 станом на 2 жовтня 2026 року.

Програма є оновлюваною в обох кластерах (див. ID програми). Запис угоди існує лише доти, доки її не розраховано, скасовано чи відхилено, тож перевірте в репозиторії, як оновлення впливає на відкриті угоди, і перегенеруйте клієнти з IDL розгорнутої збірки.

Репозиторій та аудит

Вихідний код, IDL, згенеровані клієнти й тести містяться в solana-foundation/dvp за ліцензією MIT. Програма є no_std Pinocchio-програмою; клієнти генеруються з IDL за допомогою Codama. Повідомлення про вразливості надсилаються через посилання на security advisory репозиторію згідно з його SECURITY.md.

Програму проаудитувала Cantina.

Пов’язане

Is this page helpful?