Utwórz transakcję

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.git
cd dvp && git checkout df9919ed02c25a93620e7f6820107c9050ce3b92
pnpm install && pnpm generate-clients && pnpm build
# The client is clients/typescript/src; copy or link it into your project as ./dvp
pnpm 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 asset
amountB: 998_500_000n, // 998.50 units of a 6-decimal cash token
expiryTimestamp: nowSeconds + 60n * 60n * 24n, // one day
nonce,
refString: "ORDER-2026-10-02-0001",
userASettlementDestination: null, // defaults to userA
userBSettlementDestination: null, // defaults to userB
earliestSettlementTimestamp: 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łędami SettlementDestinationIsSwapDvp, SelfDvp, SameMint, ZeroAmount, SettlementAuthorityIsParty lub ExpiryNotInFuture. Pełną listę znajdziesz w tabeli błędów.

Kolejne kroki

Is this page helpful?

Spis treści

Edytuj stronę