Crear una operación

CreateDvp registra las condiciones de una operación y crea las dos cuentas de custodia que financiarán las partes. No mueve ningún token. Cualquiera puede enviarlo, por lo que el registro puede provenir de una contraparte, del operador de la autoridad de liquidación o de un tercero, y cada uno de los demás participantes verifica las condiciones almacenadas antes de actuar en consecuencia (consulta financiar los tramos).

Los fragmentos de código usan el cliente generado directamente, con direcciones de marcador de posición, para que se vean las cuentas y los firmantes implicados. Apuntan a devnet, donde el programa está desplegado en la misma dirección que en mainnet-beta.

Instalación

A fecha del 2 de octubre de 2026, no se ha publicado ningún paquete para los clientes de DvP. Ambos se generan a partir del IDL del programa en el repositorio y se usan desde el código fuente.

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 están redactadas sobre el commit df9919e, con @solana/kit 8 en TypeScript y los crates solana-* 2.x en Rust.

Los fragmentos de cada página de pasos se mantienen entre sí: cada página usa el cliente, los firmantes y las direcciones definidos en páginas anteriores. payerSigner, userASigner, userBSigner y authoritySigner son TransactionSigner correspondientes al pagador de rent, las dos partes y la autoridad de liquidación, por ejemplo obtenidos con createKeyPairSignerFromBytes. Los fragmentos de Rust construyen instrucciones y dejan la firma y el envío en manos de quien las llama.

Derivar las direcciones

El registro de la operación es una program-derived address calculada a partir de la autoridad de liquidación, las dos partes, los dos mints y un nonce. Cada custodia es la associated token account del registro para uno de los mints. Ninguna de las dos derivaciones necesita acceso a la red.

Usa un nonce aleatorio de 64 bits para cada operación. Como la creación no requiere permisos, un nonce predecible permite que un tercero ocupe la dirección que iba a usar una operación. Pasa el nonce y los importes como bigint en TypeScript; el cliente generado rechaza un number.

Sustituye cada marcador ..._HERE por una dirección real antes de ejecutar. address() valida base58 y lanza un error con un marcador de posición, y pubkey! no compila.

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 las condiciones

CreateDvp recibe los dos importes en unidades base, una caducidad, el nonce y cuatro campos opcionales: una referencia opaca de hasta 64 bytes, un destino de liquidación para cada lado y una hora mínima de liquidación (consulta las Notas). El destino es donde se depositan los fondos de esa parte en la liquidación, por ejemplo la wallet de depósito de un custodio; por defecto es la dirección de la propia parte y queda fijado una vez que existe el registro. Los reembolsos y las recuperaciones siempre van a la token account propia de la parte para el mint de ese tramo, nunca a un destino, sea quien sea quien haya financiado la custodia.

El pagador financia la rent del registro, de la tombstone del nonce y de ambas custodias. Al cerrar, esa rent va a quien firme la instrucción de cierre, no de vuelta al pagador. La tombstone nunca se cierra, por lo que cada operación deja un pequeño saldo permanente. Al presupuestar los SOL del pagador, deriva los importes de rent con el método RPC getMinimumBalanceForRentExemption en lugar de codificarlos a mano.

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

El fragmento de TypeScript necesita tener en el ámbito getProgramDerivedAddress y getAddressEncoder de @solana/kit, y DVP_SWAP_PROGRAM_PROGRAM_ADDRESS del cliente. El builder de Rust deja el System Program y el Associated Token Program en sus valores predeterminados.

Notas

  • Caducidad. Solo Settle comprueba la caducidad. Reclaim, Cancel y Reject funcionan después de ella, y un depósito que llegue tras la caducidad puede ser recuperado por la parte del tramo. La caducidad puede ser como máximo un año posterior a la creación.
  • El tiempo es el de la red. El programa lee el reloj onchain, que puede diferir de la hora real, a veces en decenas de segundos, y puede dar saltos hacia delante. Deja un margen entre la caducidad y la hora de liquidación prevista, evita financiar o liquidar cerca de cualquiera de los dos límites y no confíes en la caducidad para imponer un plazo exacto al segundo. El README del programa sugiere presupuestar aproximadamente un minuto de desfase.
  • Liquidación mínima. Opcional. Si se establece, Settle se rechaza antes de esa hora, y no debe ser posterior a la caducidad.
  • Comprobaciones en la creación. Create falla con PartyNotSignerCapable si una parte no es una cuenta propiedad del System Program (esto descarta un multisig de SPL Token), o con SettlementDestinationIsSwapDvp, SelfDvp, SameMint, ZeroAmount, SettlementAuthorityIsParty o ExpiryNotInFuture. La lista completa está en la tabla de errores.

Siguientes pasos

Is this page helpful?

Tabla de Contenidos

Editar Página