Program dostawy za płatność (DvP)

Program DvP to standardowy program do rozliczeń typu dostawa za płatność (delivery versus payment) w Solanie, utrzymywany przez Solana Foundation i zaudytowany przez Cantina. Dwie strony uzgadniają wymianę, każda wysyła swoją nogę na konto escrow zwykłym transferem tokenów, a podmiot rozliczający rozlicza obie nogi w jednej transakcji, więc albo przechodzą obie, albo żadna. Podmiot rozliczający to trzeci adres wskazany w wymianie, na przykład platforma lub agent, który ją zorganizował. Żadna ze stron nie pisze ani nie wdraża programu, a portfel lub powiernik, który potrafi wysłać standardowy transfer tokenów (TransferChecked), może zasilić nogę bez żadnej integracji specyficznej dla DvP. Do czasu rozliczenia każda ze stron może odebrać własną nogę lub cofnąć wymianę.

Zapoznaj się z rekordem, zanim podejmiesz działanie

Tworzenie rekordu wymiany nie wymaga uprawnień, więc rekord nie jest dowodem uzgodnienia. Każda ze stron czyta zapisane warunki, zanim zasili nogę, a podmiot rozliczający – zanim dokona rozliczenia (zobacz weryfikacja).

Jak to działa

Każda wymiana otrzymuje rekord należący do programu oraz dwa konta escrow, po jednym na nogę. Strona nogi aktywa (user_a) i strona nogi gotówkowej (user_b) zasilają swoje nogi, przesyłając tokeny na konto escrow danej nogi. Podmiot rozliczający jest jedynym podpisującym, który może dokonać rozliczenia. Rozliczenie przesyła uzgodnioną kwotę każdej nogi na drugą stronę, zwraca ewentualną nadwyżkę stronie, do której należy dana noga, i zamyka rekord oraz oba konta escrow. Atomowość wynika z transakcji Solany: oba transfery są w jednej transakcji, więc albo oba zostaną wykonane, albo żaden.

Każdy zwrot, odebranie i zwrot nadwyżki trafia na własne associated token account strony: konto user_a dla mint_a, konto user_b dla mint_b. Powiernik lub system skarbowy może zasilić nogę z własnego konta, ale wszystko, co zostanie zwrócone, trafia na konto strony, a nie z powrotem do powiernika. Każda wymiana zostawia też trwałe konto znacznika (zobacz Stan).

Przewodniki

Strony z krokami prowadzą jedną wymianę od utworzenia do rozliczenia, z kodem w TypeScript i Rust dla każdej instrukcji.

Demo na devnecie przeprowadza wymianę od początku do końca: tworzy wymianę, zasila oba konta escrow standardowymi transferami tokenów i rozlicza obie nogi w jednej transakcji.

Wybór między programem a delegowaniem

Dostawa za płatność (DvP) z użyciem delegowania rozlicza tę samą wymianę bez programu: każda ze stron deleguje swoją pozycję do agenta rozliczającego, który przenosi obie nogi w jednej transakcji.

  • Program nie wymaga delegowania, więc limit jednego delegata na token account nie ma zastosowania, a podmiot rozliczający nie może przekierować wpływów. Każda wymiana zależy od aktualizowalnego programu i jego upgrade authority.
  • Wzorzec delegowania nie ma zależności od programu i działa z mintami, które mają Scaled UI Amount. Program odrzuca takie minty, co wyklucza każdy mint utworzony z szablonu tokenized-security z Mosaic (zobacz ograniczenia konfiguracji mintu).

Role

Program jest symetryczny. W jego interfejsie obie strony to user_a, który dostarcza mint_a, oraz user_b, który dostarcza mint_b. README i wygenerowani klienci nazywają mint_a nogą aktywa, a mint_b nogą gotówkową, i te strony stosują tę konwencję, ale nic w programie nie wymaga, aby noga gotówkowa była instrumentem gotówkowym. Dwa aktywa lub dwa stablecoiny rozlicza się w ten sam sposób.

RolaKtoPodpisujeUwagi
Strona nogi aktywauser_a, dostarcza mint_aWłasny transfer zasilający; Reclaim, Reject, RecoverPortfel keypair lub inteligentny portfel oparty na PDA, taki jak skarbiec multisig. Konto multisig SPL Token ani program account nie może być stroną: Create kończy się błędem PartyNotSignerCapable
Strona nogi gotówkowejuser_b, dostarcza mint_bWłasny transfer zasilający; Reclaim, Reject, RecoverTo samo wymaganie
Podmiot rozliczającyTrzeci adres wskazany przy tworzeniuSettle, CancelJedyny podpisujący, który może dokonać rozliczenia. Nie może przekierować wpływów, które są ustalone przy tworzeniu. Otrzymuje rent z zamkniętych kont, gdy dokonuje rozliczenia lub anulowania
Płatnik rentTen, kto podpisuje CreateDvpCreateFinansuje rent (depozyt w SOL, który utrzymuje konto otwarte) dla rekordu, nonce tombstone i obu kont escrow. Nie jest zapisywany jako odbiorca rent: rent z zamkniętych kont trafia do tego, kto zamyka wymianę

ID programu

dvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq
KlasterProgramUpgrade authorityOstatni wdrożony slot
mainnet-betadvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZqDXtFpbPjcn2hxPnw79x1Pfoj35vXh5AsWBkS37YnXMVv452,058,374
devnetdvp34bdbcEm4f4FCUjGV4mDAkDshaQR4LkK8fdcsyZq5EX1zFeJWj4rGYZv432WnYw8CbaSUeTdxPx4r573UgYU479,376,863

Program jest aktualizowalny na obu klastrach. Powyższe wartości to to, co zwróciło solana program show dnia 2 października 2026; uruchom to samo polecenie, aby uzyskać aktualne wartości. Wersja na devnecie jest starsza niż commit, względem którego napisano strony z krokami (zobacz Instalacja), z tym samym zestawem instrukcji i błędów.

Cykl życia

Wymiana jest otwarta od CreateDvp do momentu, gdy zamknie ją Settle, Cancel lub Reject. Zasilanie nie ma własnej instrukcji: noga jest zasilona, gdy jej konto escrow zawiera co najmniej uzgodnioną kwotę. Reclaim działa dla pojedynczej nogi i pozostawia wymianę otwartą, więc problem z jedną nogą nie zmusza drugiej strony do anulowania całej wymiany.

Stan

Każda wymiana ma jedno konto SwapDvp o rozmiarze 458 bajtów, należące do programu. Jego adres jest wyprowadzany z podmiotu rozliczającego, obu stron, obu mintów i nonce (seedy ["dvp", settlement_authority, user_a, user_b, mint_a, mint_b, nonce]). Kwoty i czasy są zapisane wewnątrz konta i nie wpływają na adres, dlatego trzeba je odczytywać, a nie wnioskować. Małe konto znacznika, nonce tombstone pod adresem ["nonce", swap_dvp], jest tworzone wraz z wymianą i nigdy nie jest zamykane, więc każda kombinacja stron, mintów, podmiotu rozliczającego i nonce może zostać użyta tylko do jednej wymiany.

PoleTypZnaczenie
bumpu8Bump PDA
user_a, user_bPubkeyObie strony
mint_a, mint_bPubkeyMinty nogi aktywa i nogi gotówkowej
settlement_authorityPubkeyJedyny podpisujący, który może dokonać rozliczenia lub anulowania
token_program_a, token_program_bPubkeyToken Program będący właścicielem każdego mintu; wymiana może łączyć nogi SPL Token i Token-2022
amount_a, amount_bu64Uzgodniona kwota każdej nogi, w najmniejszej jednostce mintu (jednostki bazowe)
expiry_timestampi64Po tym czasie sieci (zegar on-chain) Settle jest odrzucane. Reclaim, Cancel i Reject nadal działają
nonceu64Rozróżnia wymiany, które mają pozostałe seedy wspólne. Jednorazowy
ref_string[u8; 64]Nieprzezroczysty identyfikator klienta, dopełniony zerami; program nigdy go nie odczytuje
user_a_settlement_destinationPubkeyOdbiera nogę gotówkową przy Settle; domyślnie user_a
user_b_settlement_destinationPubkeyOdbiera nogę aktywa przy Settle; domyślnie user_b
mint_a_authority, mint_b_authorityPubkeyMint authority zapisane przy tworzeniu; Settle kończy się błędem, jeśli któreś się zmieniło
earliest_settlement_timestampOption<i64>Jeśli ustawione, Settle jest odrzucane przed tym czasem sieci

Lista pól pochodzi z IDL programu w commicie wskazanym w Utwórz wymianę.

Instrukcje

InstrukcjaPodpisującyEfekt
CreateDvpDowolny płatnikTworzy rekord SwapDvp, jego nonce tombstone i oba konta escrow. Nie przenosi tokenów
ReclaimDvpuser_a lub user_bZwraca z escrow nogę należącą do podpisującego. Wymiana pozostaje otwarta, a noga może zostać zasilona ponownie
SettleDvpPodmiot rozliczającyPrzesyła amount_a do miejsca docelowego user_b i amount_b do miejsca docelowego user_a, zwraca ewentualną nadwyżkę stronie nogi, zamyka rekord i oba konta escrow
CancelDvpPodmiot rozliczającyZwraca wszystko, co zawiera escrow A, do user_a, a escrow B do user_b, zamyka rekord i oba konta escrow
RejectDvpuser_a lub user_bTen sam efekt co Cancel, podpisany przez stronę
RecoverDvpuser_a lub user_bPo zamknięciu wymiany: opróżnia odtworzone escrow na własne token account podpisującego i je zamyka. Obejmuje depozyt, który dotarł po Settle, Cancel lub Reject

Każdy zwrot trafia na własne associated token account strony dla mintu danej nogi, niezależnie od tego, kto zasilił escrow.

Konta i argumenty każdej instrukcji znajdują się na stronie danego kroku.

Błędy

KodBłądKiedy
0SignerNotPartyReclaim, Reject lub Recover podpisane przez adres, który nie jest user_a ani user_b
1DvpExpiredSettle po expiry_timestamp
2SettlementAuthorityMismatchSettle lub Cancel podpisane przez adres inny niż podmiot rozliczający
3SettlementTooEarlySettle przed earliest_settlement_timestamp
4LegNotFundedSettle, gdy escrow zawiera mniej niż uzgodnioną kwotę
5ExpiryNotInFutureCreate z terminem wygaśnięcia równym lub wcześniejszym niż bieżący czas sieci
6EarliestAfterExpiryCreate z najwcześniejszym czasem rozliczenia po terminie wygaśnięcia
7SelfDvpCreate, gdy user_a jest równy user_b
8SameMintCreate, gdy mint_a jest równy mint_b
9ZeroAmountCreate z zerową kwotą na którejkolwiek nodze
10BlockedMintExtensionCreate lub Settle z mintem z rozszerzeniem TransferFee, InterestBearing, ScaledUiAmount lub NonTransferable
11SettlementAuthorityIsPartyCreate, gdy podmiot rozliczający jest równy jednej ze stron
12SettlementAuthorityExecutableCreate z wykonywalnym podmiotem rozliczającym
13NonceAlreadyUsedCreate ponownie używające (seeds, nonce), które ma już tombstone
14ExpiryTooFarInFutureCreate z terminem wygaśnięcia odległym o ponad rok
15RefStringTooLongCreate z referencją dłuższą niż 64 bajty
16DvpStillOpenRecover, gdy wymiana jest nadal otwarta; użyj Reclaim
17DvpNeverCreatedRecover z seedami, dla których nie istnieje tombstone
18EscrowPreloadedWithLamportsCreate, gdy escrow inny niż natywny przechowuje lamports powyżej minimum zwalniającego z rent
19SettlementDestinationIsSwapDvpCreate z celem rozliczenia równym rekordowi wymiany
20SwapDvpPreloadedWithLamportsCreate, gdy adres rekordu przechowuje lamports powyżej swojej rezerwy rent
21RecipientAtaMismatchToken account odbiorcy lub zwrotu nie należy już do oczekiwanego portfela i mint
22PartyNotSignerCapableCreate ze stroną, która nie jest kontem należącym do systemu i niewykonywalnym
23MintAuthorityChangedSettle, gdy mint authority nogi różni się od wartości zapisanej przy tworzeniu

Zdarzenia i indeksowanie

Program nie emituje żadnych zdarzeń onchain. Indeksator lub system uzgadniania odczytuje stan konta SwapDvp oraz salda tokenów obu escrow i używa ref_string, aby powiązać wymianę z jej zleceniem off-chain. Ponieważ rekord jest zamykany przy rozliczeniu, system, który potrzebuje warunków po fakcie, zapisuje je w momencie odczytu lub odtwarza z transakcji, która utworzyła wymianę.

Obsługiwane tokeny

Wymiana może łączyć nogę SPL Token z nogą Token-2022; każda noga ma własny token program. Obsługiwane są nogi Wrapped SOL.

Rozszerzenie Token-2022 na mint nogiZachowanie programu
TransferFee, InterestBearing, ScaledUiAmount, NonTransferableOdrzucane przy Create i Settle z błędem BlockedMintExtension
PermanentDelegate, Pausable, DefaultAccountState, freeze authority, MintCloseAuthorityAkceptowane; każda authority może działać na tokenach w escrow
ConfidentialTransferAkceptowane; kwoty na tej nodze są publiczne
TransferHookObsługiwane, do 32 dodatkowych kont na nogę
MemoTransfer na koncie docelowym lub koncie zwrotuAkceptowane; program emituje wymagane memo przed transferem

Opłata transferowa sprawiłaby, że strona odbierająca otrzymałaby mniej niż uzgodniona kwota. Interest Bearing i Scaled UI Amount nie zmieniają kwot surowych, ale ich stopa lub mnożnik mogą się zmienić między utworzeniem a rozliczeniem wymiany, co zmienia sposób wyświetlania uzgodnionych kwot. NonTransferable unieruchomiłby obie nogi.

Akceptowane authority mogą przenosić, zamrażać lub wstrzymywać tokeny w escrow przez cały czas trwania wymiany (zob. poniżej). Escrow nie może zostać skonfigurowany do odbierania transferów poufnych, więc kwoty w escrow i rozliczone na nodze poufnej są publiczne.

W przypadku mint z hookiem klient rozwiązuje dodatkowe konta hooka i dołącza je do Settle, Cancel, Reject, Reclaim i Recover. Limit 32 na nogę obejmuje program hooka i jego konto walidacyjne. Jeśli hook authority powiększy listę kont hooka ponad ten limit, każdy transfer na tej nodze zakończy się niepowodzeniem, w tym Reclaim, Cancel, Reject i Recover, dopóki authority tego nie cofnie. Limit kont w transakcji może zadziałać wcześniej niż limit na nogę, gdy obie nogi mają hooki.

Gdy konto docelowe lub konto zwrotu wymaga memo, program wywołuje program Memo przed transferem, dlatego każda instrukcja przenosząca tokeny z escrow przyjmuje konto memo_program.

Ograniczenia konfiguracji mint

Scaled UI Amount. Szablon tokenized-security w Mosaic, zestawie narzędzi Solana Foundation do emisji, zawsze dodaje Scaled UI Amount, więc program nie akceptuje mint utworzonego z tego szablonu jako nogi. Mint przeznaczony dla tego programu można utworzyć bez tego rozszerzenia (zob. Token Extensions); DvP z użyciem delegowania rozlicza się przez delegowaną authority i nie stosuje tej kontroli.

Mint domyślnie zamrożone. Escrow każdej wymiany to associated token account należące do program-derived address, który jest nowy dla danej wymiany. W mint z Default Account State ustawionym na frozen escrow jest tworzony jako zamrożony, a transfer zasilający go kończy się niepowodzeniem, dopóki nie zostanie odmrożony. W ramach Token ACL oznacza to, że emitent lub jego agent transferowy dopuszcza escrow wymiany przed zasileniem: albo przez dodanie adresu rekordu wymiany (właściciela escrow) do allowlisty, tak aby każdy mógł odmrozić escrow, jeśli konfiguracja Token ACL mint włącza odmrażanie bez uprawnień, albo przez odmrożenie go bezpośrednio przez freeze authority. Zamrożony escrow blokuje też Settle, Reclaim, Cancel, Reject i Recover na tej nodze, więc freeze authority, a w przypadku mint z hookiem hook authority, jest zaufaną stroną przez cały czas trwania wymiany. Ścieżka zamrożonego escrow jest opisana tutaj i nie jest wykorzystana w fragmentach kodu na devnet.

Limity

  • Brak dopasowywania zleceń, odkrywania cen ani księgi zleceń. Strony uzgadniają warunki poza ledgerem, a jedna z nich je zapisuje.
  • Brak nettingu, tylko rozliczenia dwustronne: jeden rekord to jedna wymiana między dwiema stronami.
  • Brak częściowych realizacji. Settle przenosi dokładnie uzgodnioną kwotę każdej nogi i zwraca ewentualną nadwyżkę stronie danej nogi.
  • Brak nogi poza ledgerem. Obie nogi to token accounts w Solanie; noga gotówkowa na istniejących torach rozlicza się osobno i jest uzgadniana.
  • Brak weryfikacji uprawnień, allowlisty ani KYC. Uprawnienia posiadaczy egzekwują własne mechanizmy kontroli mint.
  • Brak automatycznego rozliczenia. Settlement authority musi podpisać.
  • Brak zdarzeń i brak opłaty protokołu poza opłatami transakcyjnymi i rent.

Program eliminuje ryzyko kapitałowe między dwiema stronami: żadna noga się nie przesuwa, jeśli nie przesuną się obie. Nie eliminuje ryzyka kredytowego emitenta ani ryzyka wykupu żadnego z tokenów, możliwości działania freeze, pause lub permanent delegate authority mint na tokenach w escrow (zob. obsługiwane tokeny) ani zależności od tego, czy settlement authority będzie dostępna, by podpisać.

Weryfikacja przed zasileniem i rozliczeniem

CreateDvp nie wymaga uprawnień: podpisuje tylko płatnik, a strony i settlement authority nie. Każdy może więc utworzyć rekord wskazujący dowolne strony i dowolne warunki. Przed zasileniem oraz ponownie przed rozliczeniem odczytaj konto SwapDvp i potwierdź, że:

  1. Konto należy do programu DvP i ma dokładnie 458 bajtów. Konto, które nie należy do programu, można wypełnić bajtami dekodującymi się jak prawdziwy rekord.
  2. user_a, user_b, mint_a, mint_b i settlement_authority odpowiadają uzgodnionej wymianie.
  3. amount_a, amount_b, expiry_timestamp i earliest_settlement_timestamp odpowiadają uzgodnionym warunkom.
  4. Oba cele rozliczenia są uzgodnionymi adresami. Sfałszowany rekord może przekierować środki.

Zasilenie nóg wymienia weryfikujące czytniki wygenerowanych klientów i pokazuje tę kontrolę w kodzie.

Uznaj wymianę za rozliczoną, gdy transakcja Settle osiągnie finalized. To poziom commitment sieci; to, czy oznacza to także ostateczność rozliczenia w sensie prawnym, zależy od umów stron i obowiązujących reżimów prawnych.

Wersjonowanie

Konto SwapDvp nie ma pola wersji. Jego układ jest stały i wynosi 458 bajtów (SwapDvp::LEN), a weryfikujące czytniki odrzucają konto o jakiejkolwiek innej długości. IDL ma własną wersję, 0.1.0 według stanu na 2 października 2026.

Program można aktualizować w obu klastrach (zob. Program ID). Rekord wymiany istnieje tylko do momentu jej rozliczenia, anulowania lub odrzucenia, dlatego sprawdź w repozytorium, jak aktualizacja traktuje otwarte wymiany, i wygeneruj klientów ponownie z IDL wdrożonej wersji.

Repozytorium i audyt

Kod źródłowy, IDL, wygenerowani klienci i testy znajdują się w solana-foundation/dvp na licencji MIT. Program jest programem no_std Pinocchio; klienci są generowani z IDL za pomocą Codama. Zgłoszenia podatności przesyła się przez link do security advisory repozytorium, zgodnie z jego SECURITY.md.

Program został zaudytowany przez Cantina.

Powiązane

Is this page helpful?