CreateDvp zapisuje warunki transakcji i tworzy dwa konta escrow, które strony
zasilą środkami. Nie przenosi żadnych tokenów. Może go wysłać każdy, więc rekord
może pochodzić od kontrahenta, operatora organu rozliczeniowego lub osoby trzeciej,
a każdy inny uczestnik weryfikuje zapisane warunki, zanim podejmie na ich podstawie
jakiekolwiek działania (zobacz zasilenie nóg transakcji).
Fragmenty kodu korzystają bezpośrednio z wygenerowanego klienta i używają adresów zastępczych, dzięki czemu widać, jakie konta i podpisujący są zaangażowani. Dotyczą sieci devnet, w której program jest wdrożony pod tym samym adresem co w mainnet-beta.
Instalacja
Na dzień 2 października 2026 r. nie opublikowano żadnego pakietu klientów DvP. Oba klienty są generowane z IDL programu w repozytorium i używane ze źródeł.
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
Te strony powstały na podstawie commita df9919e, z użyciem @solana/kit 8
w TypeScript oraz crate'ów solana-* 2.x w Rust.
Fragmenty kodu na każdej stronie kroku są ze sobą powiązane: każda strona używa
klienta, podpisujących i adresów zdefiniowanych na wcześniejszych stronach.
payerSigner, userASigner, userBSigner i authoritySigner to obiekty
TransactionSigner odpowiednio dla płatnika rent, obu stron i organu
rozliczeniowego, uzyskane na przykład za pomocą createKeyPairSignerFromBytes.
Fragmenty w Rust budują instrukcje, a podpisywanie i wysyłanie pozostawiają
wywołującemu.
Wyprowadzanie adresów
Rekord transakcji jest adresem wyprowadzanym przez program (Program Derived Address) na podstawie organu rozliczeniowego, obu stron, obu mintów i nonce. Każdy escrow to associated token account rekordu dla jednego z mintów. Obie derywacje nie wymagają dostępu do sieci.
Dla każdej transakcji używaj losowego 64-bitowego nonce. Ponieważ tworzenie nie
wymaga uprawnień, przewidywalny nonce pozwala osobie trzeciej zająć adres, którego
miała użyć transakcja. Przekazuj nonce i kwoty jako bigint w TypeScript;
wygenerowany klient odrzuca number.
Przed uruchomieniem zastąp każdy symbol zastępczy ..._HERE prawdziwym adresem.
address() weryfikuje format base58 i zgłasza błąd dla symbolu zastępczego,
a pubkey! nie skompiluje się.
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,});
Zapisz warunki
CreateDvp przyjmuje dwie kwoty w jednostkach bazowych, termin wygaśnięcia, nonce
oraz cztery pola opcjonalne: nieprzezroczysty identyfikator referencyjny o długości
do 64 bajtów, miejsce docelowe rozliczenia dla każdej ze stron oraz najwcześniejszy
czas rozliczenia (zobacz Uwagi). Miejsce docelowe to adres, na który trafiają środki
danej strony przy rozliczeniu, na przykład portfel depozytowy powiernika; domyślnie
jest to własny adres strony i po utworzeniu rekordu nie można go zmienić. Zwroty
i odzyskiwanie środków zawsze trafiają na własne token account strony dla mintu
danej nogi transakcji, nigdy na miejsce docelowe, niezależnie od tego, kto zasilił escrow.
Płatnik pokrywa rent dla rekordu, znacznika nonce (tombstone) i obu kont escrow.
Przy zamknięciu ten rent trafia do osoby podpisującej instrukcję zamknięcia,
nie z powrotem do płatnika. Tombstone nigdy nie jest zamykany, więc każda transakcja
pozostawia niewielkie stałe saldo. Planując budżet SOL płatnika, wyznaczaj kwoty rent
za pomocą metody RPC getMinimumBalanceForRentExemption, zamiast wpisywać je na sztywno.
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);
Fragment w TypeScript wymaga, aby w zakresie były dostępne getProgramDerivedAddress
i getAddressEncoder z @solana/kit oraz DVP_SWAP_PROGRAM_PROGRAM_ADDRESS
z klienta. Builder w Rust pozostawia System Program i Associated Token Program
z wartościami domyślnymi.
Uwagi
- Wygaśnięcie. Termin wygaśnięcia sprawdza tylko Settle. Reclaim, Cancel i Reject działają po jego upływie, a depozyt, który wpłynie po wygaśnięciu, może odzyskać strona danej nogi transakcji. Termin wygaśnięcia może przypadać najwyżej rok po utworzeniu.
- Czas to czas sieciowy. Program odczytuje zegar onchain, który może różnić się od czasu rzeczywistego, czasem o dziesiątki sekund, i może skoczyć do przodu. Zachowaj margines między terminem wygaśnięcia a planowanym czasem rozliczenia, unikaj zasilania lub rozliczania w pobliżu którejkolwiek z granic i nie polegaj na terminie wygaśnięcia jako na wyznaczniku terminu co do sekundy. README programu sugeruje uwzględnienie około minuty rozbieżności.
- Najwcześniejsze rozliczenie. Opcjonalne. Jeśli jest ustawione, Settle zostanie odrzucone przed tym czasem, który nie może być późniejszy niż termin wygaśnięcia.
- Kontrole przy tworzeniu. Create kończy się błędem
PartyNotSignerCapable, jeśli strona nie jest kontem należącym do System Program (wyklucza to multisig SPL Token), a także błędamiSettlementDestinationIsSwapDvp,SelfDvp,SameMint,ZeroAmount,SettlementAuthorityIsPartylubExpiryNotInFuture. Pełną listę znajdziesz w tabeli błędów.
Kolejne kroki
- Zasil nogi transakcji: każda strona przelewa swoją nogę na escrow transakcji
Is this page helpful?