Skip to main content
The Rust SDK exposes the engine layer directly. — every flow goes through transact() with a shaped TransactOptions.

Entrypoint: transact

use cloak_sdk::core::transact::{transact, TransactOptions, TransactResult};

pub async fn transact(opts: TransactOptions) -> Result<TransactResult>;
One function, every flow. The TransactOptions.swap field routes to /transact_swap; the sign of external_amount classifies deposit/transfer/withdraw.

TransactOptions

pub struct TransactOptions {
    // Params
    pub inputs: Vec<Utxo>,
    pub outputs: Vec<Utxo>,
    pub external_amount: i64,
    pub mint: Option<Pubkey>,
    pub recipient: Option<Pubkey>,

    // Relay + submission
    pub relay_url: Option<String>,
    pub max_root_retries: Option<u32>,
    pub relayer: Option<Pubkey>,
    pub relayer_fee: u64,

    // Risk oracle
    pub enable_risk_check: bool,
    pub risk_quote_url: Option<String>,

    // Viewing key
    pub require_viewing_key: bool,
    pub chain_note_nk: Option<[u8; 32]>,

    // Caching / chaining
    pub cached_merkle_tree: Option<MerkleTree>,
    pub address_lookup_table_accounts: Vec<AddressLookupTableAccount>,

    // ALT + retry tuning
    pub alt_warmup_ms: Option<u64>,
    pub transport_backoff_base_ms: Option<u64>,

    // Swap-specific
    pub swap: Option<SwapOptions>,
    pub swap_status_max_attempts: Option<u32>,
    pub swap_status_delay_ms: Option<u64>,

    // Handles
    pub rpc: Option<Arc<dyn RpcProvider>>,
    pub payer: Option<Arc<Keypair>>,
}
All fields except inputs, outputs, and external_amount have Default values. Build with struct-update syntax:
TransactOptions {
    inputs: vec![...],
    outputs: vec![...],
    external_amount: 100_000_000,
    rpc: Some(rpc),
    payer: Some(payer),
    ..Default::default()
}

TransactResult

pub struct TransactResult {
    pub signature: Signature,
    pub nullifiers: [Fr; 2],
    pub output_commitments: [Fr; 2],
    pub output_indices: [Option<u32>; 2],
    pub sibling_commitments: [Option<Fr>; 2],
    pub pre_transaction_left_sibling: Option<Fr>,
    pub cached_merkle_tree: Option<MerkleTree>,
    pub address_lookup_table_accounts: Vec<AddressLookupTableAccount>,
}
Thread cached_merkle_tree and address_lookup_table_accounts into the next TransactOptions to skip re-work.

SwapOptions

pub struct SwapOptions {
    pub output_mint: Pubkey,
    pub recipient_ata: Pubkey,
    pub min_output_amount: u64,
    pub slippage_bps: u16,
    pub dexes: Option<Vec<String>>,
    pub exclude_dexes: Option<Vec<String>>,
}
Set opts.swap = Some(swap) to route through /transact_swap. Direct-path swap is rejected (relay owns DEX account wiring).

UTXO primitives

use cloak_sdk::core::utxo::{Utxo, UtxoKeypair, compute_commitment};

let kp = UtxoKeypair::generate()?;
let utxo = Utxo::new(amount, kp, mint)?;
let commitment: Fr = compute_commitment(&utxo)?;
let nullifier: Fr = utxo.compute_nullifier()?;      // requires utxo.index set
let zero = Utxo::zero(Some(mint), None)?;           // for padding (random salt)
UtxoKeypair is a field-element keypair for the circuit — not a Solana keypair.

RPC

use cloak_sdk::rpc::{RpcProvider, SolanaRpc};

let rpc = SolanaRpc::new("https://api.mainnet-beta.solana.com");
// or with commitment:
let rpc = SolanaRpc::with_commitment(url, CommitmentConfig::confirmed());
RpcProvider is object-safe (Arc<dyn RpcProvider>). The trait surface:
async fn get_account_data(&self, pubkey: &Pubkey) -> Result<Vec<u8>>;
async fn latest_blockhash(&self) -> Result<Hash>;
async fn send_and_confirm(&self, wire_tx: &[u8]) -> Result<Signature>;
async fn get_slot(&self) -> Result<u64>;
Tests use rpc::test_support::MockRpc with scripted responses.

Relay

use cloak_sdk::services::relay::{
    Relay, CommitmentsQuery, build_merkle_tree_from_relay,
    TransactRequest, TransactSwapRequest,
    fetch_risk_quote, wait_for_swap_completion,
};

let relay = Relay::new("https://api.cloak.ag");

// High-level endpoints (typed responses):
relay.commitments(&query).await?;       // GET /commitments
relay.merkle_root(&mint).await?;         // GET /merkle-root
relay.transact(&body).await?;            // POST /transact
relay.transact_swap(&body).await?;       // POST /transact_swap
relay.status(&request_id).await?;        // GET /status/{id}
relay.viewing_key_challenge(&user).await?;
relay.viewing_key_register(&body).await?;
relay.range_quote(&query).await?;        // GET /range-quote

Instruction builders

Direct-submission instruction builders live under solana::instructions:
use cloak_sdk::solana::instructions::{
    build_transact_ix, build_transact_swap_ix,
    TransactAccounts, SplAccounts, SwapParams,
    nullifier_bytes_from_public_inputs,
};
The orchestrator assembles these automatically. Call them directly if you need a custom submission path.

Proof generation

use cloak_sdk::utils::proof_generation::{
    ensure_circuit_artifacts,
    generate_transaction_proof,
    TransactionCircuitInputs,
    TransactionProof,
};

let artifacts = ensure_circuit_artifacts().await?;
let proof = generate_transaction_proof(&inputs, &artifacts).await?;
Called internally by the engine; exposed for custom proof pipelines. Artifacts cache at the platform cache dir.

Error type

use cloak_sdk::error::{Error, Result};

pub enum Error {
    Config(&'static str),
    Validation(&'static str),
    InsufficientFunds { available: u64, required: u64 },
    MissingUtxos,
    Storage(&'static str),
    Signer(&'static str),
    Crypto(&'static str),
    RootNotFound,
    BlockhashExpired,
    StaleProofState,
    Relay(&'static str),
}
See Error handling for retry semantics.

Constants

use cloak_sdk::constants::{
    CLOAK_PROGRAM_ID, NATIVE_SOL_MINT,
    MIN_DEPOSIT_LAMPORTS,          // 10_000_000
    DEFAULT_MAX_ROOT_RETRIES,      // 40
    MERKLE_TREE_HEIGHT,            // 32
    ENVELOPE_VERSION,              // 0x02
    DISCRIMINATOR_TRANSACT,        // 0x00
    DISCRIMINATOR_TRANSACT_SWAP,   // 0x01
    CIRCUITS_BASE_URL,
};
use cloak_sdk::core::chain_note::CHAIN_NOTE_TAG; // 2
Fee constants live under utils::fees.