Программа DvP — стандартная программа для поставки против платежа в Solana.
Её поддерживает Solana Foundation, аудит провела Cantina. Две стороны
договариваются о сделке, каждая отправляет свою ногу в эскроу-аккаунт обычным
переводом токенов, а расчётный орган проводит расчёт по обеим ногам в одной
транзакции: либо исполняются обе, либо ни одна. Расчётный орган — это третий
адрес, указанный в сделке, например площадка или агент, организовавший её.
Ни одной из сторон не нужно писать или развёртывать программу, а кошелёк или
кастодиан, способный отправить стандартный перевод токенов (TransferChecked),
может пополнить ногу без какой-либо интеграции с DvP. До расчёта любая из сторон
может забрать свою ногу обратно или расторгнуть сделку.
Прочитайте запись, прежде чем действовать
Создание записи о сделке не требует разрешений, поэтому запись не является доказательством соглашения. Каждая сторона читает сохранённые условия перед пополнением, а расчётный орган — перед расчётом (см. проверку).
Как это работает
Каждая сделка получает запись, принадлежащую программе, и два эскроу-аккаунта —
по одному на ногу. Сторона ноги с активом (user_a) и сторона денежной ноги
(user_b) пополняют свои ноги, переводя токены на эскроу-аккаунт соответствующей
ноги. Расчёт может провести только расчётный орган. При расчёте согласованная
сумма каждой ноги переводится противоположной стороне, излишек возвращается
стороне, которой принадлежит нога, а запись и оба эскроу закрываются. Атомарность
обеспечивают транзакции Solana: оба перевода выполняются в одной
транзакции, поэтому либо вступают в силу оба, либо ни один.
Каждый возврат, изъятие и возврат излишка направляются на собственный associated
token account стороны: аккаунт user_a для mint_a, аккаунт user_b для
mint_b. Кастодиан или казначейская система может пополнить ногу со своего
аккаунта, но всё возвращаемое попадает на аккаунт стороны, а не обратно к
кастодиану. Кроме того, каждая сделка оставляет постоянный маркерный аккаунт
(см. Состояние).
Руководства
Пошаговые страницы проводят одну сделку от создания до расчёта, с кодом на TypeScript и Rust для каждой инструкции.
Создание сделки
Установите клиенты и запишите условия сделки с помощью CreateDvp
Пополнение ног
Проверьте сохранённые условия, затем пополните каждую ногу обычным переводом
Расчёт по сделке
Проведите расчёт по обеим ногам в одной транзакции от имени расчётного органа
Расторжение сделки
Заберите ногу, отмените или отклоните сделку и верните запоздавшие депозиты
Демо в devnet проводит сделку целиком: создаёт её, пополняет оба эскроу стандартными переводами токенов и проводит расчёт по обеим ногам в одной транзакции.
Выбор между программой и делегированием
Delivery vs Payment (DvP) с использованием делегирования проводит тот же обмен без программы: каждая сторона делегирует свою позицию расчётному агенту, который перемещает обе ноги в одной транзакции.
- Программа не требует делегирования, поэтому ограничение в один делегат на token account не действует, а расчётный орган не может перенаправить средства. Каждая сделка зависит от обновляемой программы и её органа обновления.
- Шаблон с делегированием не зависит от программы и работает с минтами, у
которых есть Scaled UI Amount. Программа отклоняет такие минты, что исключает
любой минт, созданный по шаблону
tokenized-securityот Mosaic (см. ограничения конфигурации минта).
Роли
Программа симметрична. В её интерфейсе две стороны — 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 закрытых аккаунтов |
| Плательщик rent | Тот, кто подписывает CreateDvp | Create | Вносит rent (депозит в SOL, который поддерживает аккаунт открытым) за запись, tombstone nonce и оба эскроу. В качестве получателя rent не записывается: rent закрытых аккаунтов получает тот, кто закрывает сделку |
ID программы
dvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq
| Кластер | Программа | Орган обновления | Последний slot развёртывания |
|---|---|---|---|
| mainnet-beta | dvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq | DXtFpbPjcn2hxPnw79x1Pfoj35vXh5AsWBkS37YnXMVv | 452 058 374 |
| devnet | dvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq | 5EX1zFeJWj4rGYZv432WnYw8CbaSUeTdxPx4r573UgYU | 479 376 863 |
Программа обновляема в обоих кластерах. Приведённые выше значения — это то, что
solana program show выдала 2 октября 2026 г.; для актуальных значений
выполните ту же команду. Сборка в devnet старше коммита, под который написаны
пошаговые страницы (см. Установка), но набор инструкций и ошибок
тот же.
Жизненный цикл
Сделка открыта от CreateDvp до тех пор, пока её не закроет одна из инструкций
Settle, Cancel или Reject. У пополнения нет собственной инструкции: нога
считается пополненной, когда её эскроу-аккаунт хранит не меньше согласованной
суммы. Reclaim действует для одной ноги и оставляет сделку открытой, поэтому
проблема с одной ногой не вынуждает другую сторону отменять всю сделку.
Состояние
У каждой сделки один аккаунт SwapDvp размером 458 байт, принадлежащий
программе. Его адрес выводится из расчётного органа, двух сторон, двух минтов и
nonce (seeds
["dvp", settlement_authority, user_a, user_b, mint_a, mint_b, nonce]). Суммы и
время хранятся внутри аккаунта и не влияют на адрес, поэтому их нужно считывать,
а не выводить. Небольшой маркерный аккаунт — tombstone nonce по адресу
["nonce", swap_dvp] — создаётся вместе со сделкой и никогда не закрывается,
поэтому каждую комбинацию сторон, минтов, органа и nonce можно использовать
только для одной сделки.
| Поле | Тип | Значение |
|---|---|---|
bump | u8 | Bump PDA |
user_a, user_b | Pubkey | Две стороны |
mint_a, mint_b | Pubkey | Минты ноги с активом и денежной ноги |
settlement_authority | Pubkey | Единственный подписант, который может провести расчёт или отмену |
token_program_a, token_program_b | Pubkey | Token program, которому принадлежит каждый минт; в сделке могут сочетаться ноги SPL Token и Token-2022 |
amount_a, amount_b | u64 | Согласованная сумма каждой ноги в наименьшей единице минта (базовых единицах) |
expiry_timestamp | i64 | После этого сетевого времени (ончейн-часов) Settle отклоняется. Reclaim, Cancel и Reject продолжают работать |
nonce | u64 | Различает сделки с одинаковыми остальными seeds. Одноразовый |
ref_string | [u8; 64] | Непрозрачная клиентская ссылка, дополненная нулями; программа её никогда не читает |
user_a_settlement_destination | Pubkey | Получает денежную ногу при Settle; по умолчанию user_a |
user_b_settlement_destination | Pubkey | Получает ногу с активом при Settle; по умолчанию user_b |
mint_a_authority, mint_b_authority | Pubkey | Mint authority, зафиксированные при создании; Settle завершается ошибкой, если любое из них изменилось |
earliest_settlement_timestamp | Option<i64> | Если задано, Settle отклоняется до этого сетевого времени |
Список полей взят из IDL программы в коммите, указанном в разделе Создание сделки.
Инструкции
| Инструкция | Подписант | Эффект |
|---|---|---|
CreateDvp | Любой плательщик | Создаёт запись SwapDvp, её tombstone nonce и оба эскроу-аккаунта. Токены не перемещает |
ReclaimDvp | user_a или user_b | Возвращает собственную ногу подписанта из эскроу. Сделка остаётся открытой, и ногу можно пополнить снова |
SettleDvp | Расчётный орган | Переводит amount_a на адрес назначения user_b и amount_b на адрес назначения user_a, возвращает излишек стороне ноги, закрывает запись и оба эскроу |
CancelDvp | Расчётный орган | Возвращает всё, что хранится в эскроу A, user_a, а в эскроу B — user_b, закрывает запись и оба эскроу |
RejectDvp | user_a или user_b | Тот же эффект, что у Cancel, но подписывает сторона |
RecoverDvp | user_a или user_b | После закрытия сделки: переводит всё из пересозданного эскроу на собственный token account подписанта и закрывает его. Покрывает депозит, поступивший после Settle, Cancel или Reject |
Каждый возврат направляется на собственный associated token account стороны для минта этой ноги, независимо от того, кто пополнил эскроу.
Аккаунты и аргументы каждой инструкции описаны на странице соответствующего шага.
Ошибки
| Код | Ошибка | Когда |
|---|---|---|
| 0 | SignerNotParty | Reclaim, Reject или Recover подписаны адресом, который не является user_a или user_b |
| 1 | DvpExpired | Settle после expiry_timestamp |
| 2 | SettlementAuthorityMismatch | Settle или Cancel подписаны адресом, отличным от расчётного органа |
| 3 | SettlementTooEarly | Settle до earliest_settlement_timestamp |
| 4 | LegNotFunded | Settle, пока в каком-либо эскроу меньше согласованной суммы |
| 5 | ExpiryNotInFuture | Create со сроком действия, равным текущему сетевому времени или более ранним |
| 6 | EarliestAfterExpiry | Create с самым ранним временем расчёта после истечения срока |
| 7 | SelfDvp | Create, где user_a равен user_b |
| 8 | SameMint | Create, где mint_a равен mint_b |
| 9 | ZeroAmount | Create с нулевой суммой в любой из ног |
| 10 | BlockedMintExtension | Create или Settle с минтом, у которого есть расширение TransferFee, InterestBearing, ScaledUiAmount или NonTransferable |
| 11 | SettlementAuthorityIsParty | Create, где расчётный орган совпадает с одной из сторон |
| 12 | SettlementAuthorityExecutable | Create с исполняемым расчётным органом |
| 13 | NonceAlreadyUsed | Create с повторным использованием (seeds, nonce), для которого уже есть tombstone |
| 14 | ExpiryTooFarInFuture | Create со сроком действия более чем на год вперёд |
| 15 | RefStringTooLong | Create со ссылкой длиннее 64 байт |
| 16 | DvpStillOpen | Recover, пока сделка ещё открыта; используйте Reclaim |
| 17 | DvpNeverCreated | Recover с seed-значениями, для которых нет tombstone |
| 18 | EscrowPreloadedWithLamports | Create, когда эскроу не в нативном токене удерживает lamports сверх минимума для освобождения от rent |
| 19 | SettlementDestinationIsSwapDvp | Create с адресом назначения расчёта, совпадающим с записью сделки |
| 20 | SwapDvpPreloadedWithLamports | Create, когда адрес записи удерживает lamports сверх резерва rent |
| 21 | RecipientAtaMismatch | Token account получателя или возврата больше не принадлежит ожидаемому кошельку и mint |
| 22 | PartyNotSignerCapable | Create со стороной, которая не является неисполняемым аккаунтом, принадлежащим System Program |
| 23 | MintAuthorityChanged | Settle, когда mint authority ноги отличается от значения, зафиксированного при создании |
События и индексирование
Программа не генерирует ончейн-событий. Индексатор или система сверки считывает состояние аккаунта SwapDvp и балансы токенов двух эскроу и использует 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 может распоряжаться токенами в эскроу |
| ConfidentialTransfer | Допускается; суммы на этой ноге публичны |
| TransferHook | Поддерживается, до 32 дополнительных аккаунтов на ногу |
| MemoTransfer на аккаунте получения или возврата | Допускается; программа отправляет требуемый memo перед переводом |
Комиссия за перевод оставила бы принимающую сторону без согласованной суммы. Interest Bearing и Scaled UI Amount не меняют исходные (raw) суммы, но их ставка или множитель могут измениться между созданием и расчётом сделки, из-за чего изменится отображение согласованных сумм. NonTransferable заблокировал бы обе ноги.
Допущенные authority могут перемещать, замораживать или останавливать токены в эскроу на протяжении всей жизни сделки (см. ниже). Эскроу нельзя настроить на приём конфиденциальных переводов, поэтому суммы в эскроу и при расчёте на конфиденциальной ноге публичны.
Для mint с хуком клиент определяет дополнительные аккаунты хука и добавляет их к Settle, Cancel, Reject, Reclaim и Recover. Лимит в 32 на ногу включает программу хука и её аккаунт валидации. Если hook authority расширит список аккаунтов хука сверх этого лимита, каждый перевод на этой ноге, включая Reclaim, Cancel, Reject и Recover, будет завершаться ошибкой, пока authority не отменит изменение. Лимит аккаунтов в транзакции может сработать раньше лимита на ногу, если хуки есть на обеих ногах.
Если аккаунт получения или возврата требует memo, программа вызывает программу Memo перед переводом — поэтому каждая инструкция, выводящая токены из эскроу, принимает аккаунт memo_program.
Ограничения конфигурации mint
Scaled UI Amount. Шаблон tokenized-security в Mosaic, наборе инструментов Solana Foundation для эмиссии, всегда добавляет Scaled UI Amount, поэтому программа не принимает mint, созданный из этого шаблона, в качестве ноги. Mint для этой программы можно создать без этого расширения (см. Token Extensions); DvP с использованием делегирования проводит расчёт через делегированные полномочия и эту проверку не применяет.
Mint с заморозкой по умолчанию. Эскроу каждой сделки — это associated token account, принадлежащий program-derived address, новому для этой сделки. На mint с Default Account State, установленным в frozen, эскроу создаётся замороженным, и пополняющий перевод в него завершается ошибкой, пока он не будет разморожен. В случае Token ACL это означает, что эмитент или его трансфер-агент допускает эскроу сделки до пополнения: либо добавляя адрес записи сделки (владельца эскроу) в allowlist, так что любой может разморозить эскроу, если конфигурация Token ACL у mint включает permissionless thaw, либо поручая freeze authority разморозить его напрямую. Замороженное эскроу также блокирует Settle, Reclaim, Cancel, Reject и Recover на этой ноге, поэтому freeze authority, а для mint с хуком — hook authority, является доверенной стороной на всё время жизни сделки. Путь с замороженным эскроу описан здесь и не используется в devnet-сниппетах.
Ограничения
- Нет сопоставления, определения цены и книги ордеров. Стороны согласуют условия вне реестра, и одна из них их записывает.
- Нет неттинга, только двусторонние сделки: одна запись — одна сделка между двумя сторонами.
- Нет частичного исполнения. Settle переводит ровно согласованную сумму по каждой ноге и возвращает излишек стороне этой ноги.
- Нет офчейн-ноги. Обе ноги — это token accounts в Solana; денежная нога на существующих платёжных рельсах рассчитывается отдельно и сверяется.
- Нет проверки допуска, allowlist или KYC. Допуск держателей обеспечивают собственные механизмы контроля mint.
- Нет автоматического расчёта. Settlement authority должна подписать.
- Нет событий и нет комиссии протокола, помимо комиссий за транзакции и rent.
Программа устраняет принципальный риск между двумя сторонами: ни одна нога не перемещается, если не перемещаются обе. Она не устраняет кредитный риск эмитента или риск погашения по любому из токенов, возможность freeze, pause или permanent delegate authority mint воздействовать на токены в эскроу (см. поддерживаемые токены), а также зависимость от доступности settlement authority для подписи.
Проверка перед пополнением и расчётом
CreateDvp не требует разрешений: подписывает только плательщик, а стороны и settlement authority — нет. Поэтому любой может создать запись с любыми сторонами и условиями. Перед пополнением и ещё раз перед расчётом прочитайте аккаунт SwapDvp и убедитесь, что:
- Аккаунт принадлежит программе DvP и имеет ровно 458 байт. Аккаунт, не принадлежащий программе, можно заполнить байтами, которые декодируются как настоящая запись.
user_a,user_b,mint_a,mint_bиsettlement_authorityсоответствуют согласованной сделке.amount_a,amount_b,expiry_timestampиearliest_settlement_timestampсоответствуют согласованным условиям.- Оба адреса назначения расчёта — согласованные адреса. Поддельная запись может перенаправить средства.
В разделе Пополнение ног перечислены проверяющие ридеры сгенерированных клиентов и показана проверка в коде.
Считайте сделку рассчитанной, когда транзакция Settle достигает finalized. Это уровень подтверждения сети; будет ли он также означать окончательность расчёта в юридическом смысле, зависит от соглашений сторон и применимых режимов регулирования.
Версионирование
У аккаунта 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.
Связанные материалы
- Поставка против платежа (DvP) с использованием делегирования: паттерн делегирования без программы
- Token ACL: как mint с ограниченным доступом взаимодействует с аккаунтами эскроу
Is this page helpful?