Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.cloak.ag/llms.txt

Use this file to discover all available pages before exploring further.

Use this page when you want the smallest possible end-to-end Cloak integration. This snippet covers:
  • shielded SOL deposit
  • full withdrawal (send)
  • partial withdrawal (keep change private)
import {
  CLOAK_PROGRAM_ID,
  NATIVE_SOL_MINT,
  createUtxo,
  createZeroUtxo,
  fullWithdraw,
  generateUtxoKeypair,
  partialWithdraw,
  transact,
} from "@cloak.dev/sdk";
import { Connection, Keypair } from "@solana/web3.js";

const connection = new Connection(
  "https://api.mainnet-beta.solana.com",
  "confirmed",
);
const signer = Keypair.fromSecretKey(/* Uint8Array secret key */);

const amount = 1_000_000_000n; // 1 SOL
const owner = await generateUtxoKeypair();
const depositOutput = await createUtxo(amount, owner, NATIVE_SOL_MINT);

const deposited = await transact(
  {
    inputUtxos: [await createZeroUtxo(NATIVE_SOL_MINT)],
    outputUtxos: [depositOutput],
    externalAmount: amount,
    depositor: signer.publicKey,
  },
  {
    connection,
    programId: CLOAK_PROGRAM_ID,
    depositorKeypair: signer,
    walletPublicKey: signer.publicKey,
  },
);

const recipient = Keypair.generate().publicKey;

// Full withdrawal: sends all spendable value externally.
await fullWithdraw(deposited.outputUtxos, recipient, {
  connection,
  programId: CLOAK_PROGRAM_ID,
  depositorKeypair: signer,
  walletPublicKey: signer.publicKey,
  cachedMerkleTree: deposited.merkleTree,
});

// OR partial withdrawal: withdraw a portion, keep private change in shielded state.
// await partialWithdraw(deposited.outputUtxos, recipient, 200_000_000n, {
//   connection,
//   programId: CLOAK_PROGRAM_ID,
//   depositorKeypair: signer,
//   walletPublicKey: signer.publicKey,
//   cachedMerkleTree: deposited.merkleTree,
// });
For minimal script generation, keep this exact interface:
  • file: send-sol-private.ts
  • command: npx tsx send-sol-private.ts <recipientPubkey> <lamports>
  • env vars: SOLANA_RPC_URL, KEYPAIR_PATH
Guardrails:
  • keep tx amounts as bigint
  • use KEYPAIR_PATH (file-based keypair), not raw private key env vars
  • do not parse SOL decimals with float math (parseFloat, AMOUNT_SOL)
  • read recipient/amount from process.argv (<recipientPubkey> <lamports>), not RECIPIENT_ADDRESS/SEND_LAMPORTS env vars
  • keep programId fixed to CLOAK_PROGRAM_ID internally
  • rely on SDK stale-root retries for standard flows
  • call process.exit(0) on success and process.exit(1) on failure

Mainnet smoke test (keypair bytes)

Use this when you want a first real send with explicit runtime values.
export SOLANA_RPC_URL="https://api.mainnet-beta.solana.com"

# Must be an absolute path. Do not rely on `~` expansion inside env files.
export KEYPAIR_PATH="/Users/you/.config/solana/id.json"
Sanity-check before running:
solana-keygen pubkey "$KEYPAIR_PATH"
solana balance "$(solana-keygen pubkey "$KEYPAIR_PATH")" --url "$SOLANA_RPC_URL"
Run your script entrypoint that wraps the snippet above:
npx tsx send.ts "<recipient-wallet-pubkey>" "50000000"

Funding and net amount

For SOL withdraw/send paths:
  • gross = abs(externalAmount)
  • fee = 5_000_000 + floor(gross * 3 / 1000)
  • net = gross - fee
Worked example (gross = 50_000_000, i.e. 0.05 SOL):
  • fee = 5_150_000 lamports (0.00515 SOL)
  • net = 44_850_000 lamports (0.04485 SOL)
Recommended first-run sender balance for this smoke test: 0.07 SOL or higher. Next: Code Examples