Skip to main content
The UTXO API is the current production path used by SDK and web transaction flows.

Core APIs

  • transact(params, options)
  • transfer(...)
  • partialWithdraw(...)
  • fullWithdraw(...)
  • swapUtxo(...)
  • swapWithChange(...)
All are exported from @cloak.ag/sdk.

Transaction semantics

import type { Utxo, TransactParams, TransactOptions } from "@cloak.ag/sdk";

interface TransactParams {
  inputUtxos: Utxo[];
  outputUtxos: Utxo[];
  recipient?: PublicKey;
  externalAmount?: bigint; // +deposit, -withdraw, 0=shield-to-shield
}
externalAmount rules:
  • > 0: public deposit into the pool
  • < 0: public withdrawal from the pool
  • 0: fully shielded transfer
Under the hood, SDK builds proof + 264-byte public inputs and submits to relay /transact.

Fees

  • gross = abs(externalAmount) when withdrawing/swapping
  • fee = 5_000_000 + gross * 3 / 1000
  • net = gross - fee
  • deposits have no protocol fee but still must satisfy min deposit (10_000_000 lamports)

Create UTXOs

import {
  createUtxo,
  generateUtxoKeypair,
  NATIVE_SOL_MINT,
} from "@cloak.ag/sdk";

const keypair = await generateUtxoKeypair();
const utxo = await createUtxo(100_000_000n, keypair, NATIVE_SOL_MINT);

Deposit example (transact)

import { transact, createUtxo, generateUtxoKeypair } from "@cloak.ag/sdk";
import { Connection, Keypair, PublicKey } from "@solana/web3.js";

const connection = new Connection(process.env.SOLANA_RPC_URL!, "confirmed");
const programId = new PublicKey(process.env.CLOAK_PROGRAM_ID!);
const depositorKeypair = Keypair.fromSecretKey(secretKeyBytes);

const owner = await generateUtxoKeypair();
const output = await createUtxo(20_000_000n, owner); // 0.02 SOL

const result = await transact(
  {
    inputUtxos: [],
    outputUtxos: [output],
    externalAmount: 20_000_000n,
  },
  {
    connection,
    programId,
    relayUrl: process.env.CLOAK_RELAY_URL,
    depositorKeypair,
  }
);

console.log(result.signature, result.commitmentIndices);

Withdraw and transfer helpers

import { transfer, partialWithdraw, fullWithdraw } from "@cloak.ag/sdk";

await transfer(inputUtxos, recipientUtxoPublicKey, 30_000_000n, options);
await partialWithdraw(inputUtxos, recipientWallet, 10_000_000n, options);
await fullWithdraw(inputUtxos, recipientWallet, options);
partialWithdraw/fullWithdraw use /transact with negative externalAmount.

Swap example (/transact_swap)

import { swapWithChange } from "@cloak.ag/sdk";

await swapWithChange(
  inputUtxos,
  50_000_000n,            // swap amount (lamports)
  outputMint,
  recipientAta,
  1_000_000n,             // min output
  {
    connection,
    programId,
    relayUrl: process.env.CLOAK_RELAY_URL,
  },
  recipientWallet
);
swapWithChange sends TransactSwap payload (proof + 264-byte public inputs + swap params) to relay /transact_swap.

Viewing-key registration requirements

  • Default behavior enforces viewing-key registration before protocol txs.
  • TransactOptions.enforceViewingKeyRegistration defaults to true.
  • Registration signs fixed sign-in message and posts to /viewing-key/register.
If viewing key is missing, web history/decrypt flows cannot resolve your transaction data.

Risk-oracle deposits

When deposits require Range/Switchboard validation, include:
  • riskOracleQueue
  • riskQuoteUrl (or getRiskQuoteInstruction)
Relay GET /risk-quote is not implemented (501), so provide your own quote backend or direct instruction callback. If deposit account list is too large for legacy transactions, use V0 + addressLookupTableAccounts.

Operational notes

  • Amounts use bigint in the UTXO API.
  • Proof retries are built in for stale roots (0x1001).
  • Relay-backed Merkle reconstruction (/commitments) is preferred for older indices.
  • Optional cachedMerkleTree helps sequential txs avoid extra rebuild/fetch.
  • Optional useUniqueNullifiers helps local/surfpool repeated test runs.

Verification commands

Run from sdk/:
npm run build
npm run example:verify
npx tsx examples/scan-history.ts

Troubleshooting

  • Invalid public inputs size: relay expects 264 bytes, not 232.
  • RootNotFound / 0x1001: regenerate proof with fresh root (SDK retry usually handles this).
  • 0x1020 in local repeated runs: set CLOAK_UNIQUE_NULLIFIERS=1.
  • viewing key not found: re-run a transaction with registration enabled, then rescan history.

Next