Criar uma operação

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.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

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 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);

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 PartyNotSignerCapable se uma parte não for uma conta pertencente ao System Program (isso exclui um multisig do SPL Token), SettlementDestinationIsSwapDvp, SelfDvp, SameMint, ZeroAmount, SettlementAuthorityIsParty ou ExpiryNotInFuture. A lista completa está na tabela de erros.

Próximos passos

Is this page helpful?

Índice

Editar Página
© 2026 Fundação Solana. Todos os direitos reservados.