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 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.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 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 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 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 PartyNotSignerCapable se uma parte não for uma conta pertencente ao system program (o que exclui um multisig do SPL Token), ou com 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.