CreateDvp registra os termos de uma operação e cria as duas contas de escrow que
as partes irão financiar. Ele não move nenhum token. Qualquer pessoa pode enviá-lo,
portanto o registro pode vir de uma contraparte, do operador da autoridade de
liquidação ou de um terceiro, e todos os demais participantes verificam os termos
armazenados antes de agir com base neles (veja financiar as pernas).
Os trechos usam o cliente gerado diretamente, com endereços de exemplo, para que as contas e os signatários envolvidos fiquem visíveis. Eles têm como alvo a devnet, onde o programa está implantado no mesmo endereço da mainnet-beta.
Instalação
Nenhum pacote foi publicado para os clientes DvP até 2 de outubro de 2026. Ambos são gerados a partir do IDL do programa no repositório e usados a partir do código-fonte.
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
Estas páginas foram escritas com base no commit df9919e, com @solana/kit 8 em
TypeScript e os crates solana-* 2.x em Rust.
Os trechos de cada página de etapa se acumulam: cada página usa o cliente, os
signatários e os endereços definidos nas páginas anteriores. payerSigner, userASigner,
userBSigner e authoritySigner são TransactionSigners para o pagador do rent,
as duas partes e a autoridade de liquidação, por exemplo a partir de
createKeyPairSignerFromBytes. Os trechos em Rust montam instruções e deixam a
assinatura e o envio a cargo de quem as chama.
Derivar os endereços
O registro da operação é um Program Derived Address calculado a partir da autoridade de liquidação, das duas partes, das duas mints e de um nonce. Cada escrow é a associated token account do registro para uma das mints. Nenhuma das derivações precisa de acesso à rede.
Use um nonce aleatório de 64 bits para cada operação. Como a criação não exige
permissão, um nonce previsível permite que um terceiro ocupe o endereço que uma
operação usaria. Passe o nonce e os valores como bigint em TypeScript; o cliente
gerado rejeita um number.
Substitua cada placeholder ..._HERE por um endereço real antes de executar.
address() valida base58 e lança um erro ao receber um placeholder, e pubkey!
falha na compilação.
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,});
Registrar os termos
CreateDvp recebe os dois valores em unidades base, uma expiração, o nonce e quatro
campos opcionais: uma referência opaca de até 64 bytes, um destino de liquidação
para cada lado e um horário mínimo de liquidação (veja Notas). O destino é onde os
recursos dessa parte chegam na liquidação, por exemplo a carteira de depósito de um
custodiante; o padrão é o endereço da própria parte, e ele fica fixo depois que o
registro existe. Reembolsos e resgates sempre vão para a token account da própria
parte referente à mint daquela perna, nunca para um destino, independentemente de
quem financiou o escrow.
O pagador financia o rent do registro, do tombstone do nonce e dos dois escrows.
No fechamento, esse rent vai para quem assina a instrução de fechamento, e não de
volta ao pagador. O tombstone nunca é fechado, então cada operação deixa um pequeno
saldo permanente. Ao planejar o orçamento de SOL do pagador, derive os valores de rent
com o método RPC getMinimumBalanceForRentExemption em vez de fixá-los no código.
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);
O trecho em TypeScript precisa ter getProgramDerivedAddress e getAddressEncoder
de @solana/kit, e DVP_SWAP_PROGRAM_PROGRAM_ADDRESS do cliente, no escopo. O
builder em Rust mantém o system program e o associated token program em seus
valores padrão.
Notas
- Expiração. Somente o Settle verifica a expiração. Reclaim, Cancel e Reject funcionam depois dela, e um depósito que chegue após a expiração pode ser recuperado pela parte dessa perna. A expiração pode ser, no máximo, um ano após a criação.
- O tempo é o tempo da rede. O programa lê o relógio onchain, que pode diferir do horário real, às vezes em dezenas de segundos, e pode saltar para a frente. Deixe uma margem entre a expiração e o horário de liquidação pretendido, evite financiar ou liquidar perto de qualquer um dos limites e não conte com a expiração para impor um prazo exato ao segundo. O README do programa sugere reservar cerca de um minuto de deriva.
- Horário mínimo de liquidação. Opcional. Se definido, o Settle é rejeitado antes desse horário, e ele não pode ser posterior à expiração.
- Verificações na criação. A criação falha com
PartyNotSignerCapablese uma parte não for uma conta pertencente ao system program (o que exclui um multisig do SPL Token), ou comSettlementDestinationIsSwapDvp,SelfDvp,SameMint,ZeroAmount,SettlementAuthorityIsPartyouExpiryNotInFuture. A lista completa está na tabela de erros.
Próximos passos
- Financiar as pernas: cada parte transfere sua perna para o escrow da operação
Is this page helpful?