> ## 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.

# UTXO Transactions

> Transact, swap, viewing-key registration, and scanner-friendly transaction flows

The UTXO API is the current production path for SDK-based app integrations.

## Core APIs

* `transact(params, options)`
* `transfer(...)`
* `partialWithdraw(...)`
* `fullWithdraw(...)`
* `swapUtxo(...)`
* `swapWithChange(...)`

All are exported from `@cloak.dev/sdk`.

## Transaction semantics

```typescript theme={null}
import type { Utxo, TransactParams, TransactOptions } from "@cloak.dev/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 the resulting on-chain transaction.

## Fees

* SOL withdraw/swap:
  * `gross = abs(externalAmount)`
  * `fee = 5_000_000 + floor(gross * 3 / 1000)`
  * `net = gross - fee`
* SPL (USDC/USDT) withdraw: `fee = floor(gross * 3 / 1000)` only — no fixed component
* Deposits and shielded transfers have no protocol fee; SOL deposits must satisfy the
  min deposit (`10_000_000` lamports — SOL pool only, no program minimum for SPL)

SDK helpers you can call directly:

* SOL: `calculateSolFeeLamports`, `calculateSolNetAmountLamports`

## Create UTXOs

```typescript theme={null}
import {
  createUtxo,
  generateUtxoKeypair,
  NATIVE_SOL_MINT,
} from "@cloak.dev/sdk";

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

## Deposit example (`transact`)

<CodeGroup>
  ```typescript Keypair bytes theme={null}
  import {
    CLOAK_PROGRAM_ID,
    transact,
    createUtxo,
    generateUtxoKeypair,
  } from "@cloak.dev/sdk";
  import { Connection, Keypair } from "@solana/web3.js";

  const connection = new Connection(process.env.SOLANA_RPC_URL!, "confirmed");
  const programId = 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,
  depositorKeypair,
  }
  );

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

  ```

  ```typescript Wallet adapter theme={null}
  import { transact, createUtxo, createZeroUtxo, generateUtxoKeypair } from "@cloak.dev/sdk";
  import { useWallet } from "@solana/wallet-adapter-react";

  const { publicKey, signTransaction } = useWallet();

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

  const result = await transact(
    {
      inputUtxos: [await createZeroUtxo()],
      outputUtxos: [output],
      externalAmount: 20_000_000n,
      depositor: publicKey!,
    },
    {
      connection,
      programId,
      signTransaction: signTransaction!,
      depositorPublicKey: publicKey!,
      walletPublicKey: publicKey!,
    }
  );
  ```
</CodeGroup>

## Withdraw and transfer helpers

<CodeGroup>
  ```typescript Keypair bytes theme={null}
  import { transfer, partialWithdraw, fullWithdraw } from "@cloak.dev/sdk";

  await transfer(inputUtxos, recipientUtxoPublicKey, 30_000_000n, options);
  await partialWithdraw(inputUtxos, recipientWallet, 10_000_000n, options);
  await fullWithdraw(inputUtxos, recipientWallet, options);

  ```

  ```typescript Wallet adapter theme={null}
  import { transfer, partialWithdraw, fullWithdraw } from "@cloak.dev/sdk";

  const walletOptions = {
    connection,
    programId,
    relayUrl,
    walletPublicKey: publicKey!,
    signMessage: signMessage!,
  };

  await transfer(inputUtxos, recipientUtxoPublicKey, 30_000_000n, walletOptions);
  await partialWithdraw(inputUtxos, recipientWallet, 10_000_000n, walletOptions);
  await fullWithdraw(inputUtxos, recipientWallet, walletOptions);
  ```
</CodeGroup>

`partialWithdraw`/`fullWithdraw` use negative `externalAmount` semantics.

## Swap example

<CodeGroup>
  ```typescript Keypair bytes theme={null}
  import { swapWithChange } from "@cloak.dev/sdk";

  await swapWithChange(
  inputUtxos,
  50_000_000n, // swap amount (lamports)
  outputMint,
  recipientAta,
  1_000_000n, // min output
  {
  connection,
  programId,
  },
  recipientWallet
  );

  ```

  ```typescript Wallet adapter theme={null}
  import { swapWithChange } from "@cloak.dev/sdk";

  await swapWithChange(
    inputUtxos,
    50_000_000n,
    outputMint,
    recipientAta,
    1_000_000n,
    {
      connection,
      programId,
      relayUrl,
      walletPublicKey: publicKey!,
      signMessage: signMessage!,
    },
    publicKey!
  );
  ```
</CodeGroup>

`swapWithChange` sends a `TransactSwap` payload (`proof + 264-byte public inputs + swap params`).

## Viewing-key registration requirements

* Default behavior enforces viewing-key registration before protocol txs.
* `TransactOptions.enforceViewingKeyRegistration` defaults to `true`.
* Registration signs the fixed sign-in message and submits the registration record.

If viewing key is missing, history and decrypt flows cannot resolve your transaction data.

## Risk-oracle deposits

When deposits require Range/Switchboard validation, include:

* `riskOracleQueue`
* `riskQuoteUrl` (or `getRiskQuoteInstruction`)

The SDK can fetch risk quotes for you; you can also provide your own quote backend or a direct instruction callback.

If deposit account list is large, use v0 transactions with `addressLookupTableAccounts`.

## Operational notes

* Amounts use `bigint` in the UTXO API.
* Proof retries are built in for stale roots (`0x1001`).
* SDK-side Merkle reconstruction is preferred for older indices.
* Optional `cachedMerkleTree` helps sequential txs avoid extra rebuild/fetch.
* Optional `useUniqueNullifiers` helps repeated development runs avoid nullifier collisions.

## Troubleshooting

* `Invalid public inputs size`: the protocol 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

* Protocol behavior: [Shield Pool Program](/protocol/shield-pool)
* Runtime lifecycle: [Transaction Flows](/platform/transaction-flows)
* Full SDK exports: [API Reference](/sdk/api-reference)
