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 de código usam diretamente o cliente gerado, com endereços fictícios, para que as contas e os signatários envolvidos fiquem visíveis. Eles têm como alvo a devnet, onde o programa é implantado no mesmo endereço da mainnet-beta.
Instalação
Até 2 de outubro de 2026, nenhum pacote foi publicado para os clientes DvP. 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 código de todas as páginas de etapas se complementam: 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 de rent,
as duas partes e a autoridade de liquidação, por exemplo a partir de
createKeyPairSignerFromBytes. Os trechos em Rust montam as instruções e deixam a
assinatura e o envio a cargo de quem os 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, dos dois mints e de um nonce. Cada escrow é a associated token account do registro para um dos mints. Ambas as derivações não precisam 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 iria
usar. 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 gera um erro com 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 é
o local onde os recursos dessa parte chegam na liquidação, por exemplo a carteira de
depósito de um custodiante; o padrão é o próprio endereço da parte, e ele é fixado depois que
o registro existe. Reembolsos e resgates sempre vão para a token account da própria parte
para o mint dessa perna, nunca para um destino, independentemente de quem financiou o escrow.
O pagador financia a rent do registro, do tombstone do nonce e de ambos os escrows.
No encerramento, essa rent vai para quem assina a instrução de encerramento, e não de volta para
o pagador. O tombstone nunca é encerrado, então cada operação deixa um pequeno saldo
permanente. Ao planejar o 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 que getProgramDerivedAddress e getAddressEncoder
de @solana/kit, e DVP_SWAP_PROGRAM_PROGRAM_ADDRESS do cliente, estejam 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 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 da 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 on-chain, 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 pretendido de liquidação, evite financiar ou liquidar perto de qualquer um dos limites e não conte com a expiração para impor um prazo ao segundo. O README do programa sugere prever cerca de um minuto de desvio.
- Liquidação mais cedo. Opcional. Se definido, Settle é rejeitado antes dele, 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 (isso exclui um multisig do SPL Token),SettlementDestinationIsSwapDvp,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?