Skip to main content

Overview

The transact instruction is the core function for private SOL transactions. It verifies a zero-knowledge proof and executes either a deposit (positive ext_amount) or withdrawal (negative ext_amount) of SOL. This instruction prevents double-spending by creating nullifier accounts that mark UTXOs as spent.

Function Signature

Parameters

Proof
required
Zero-knowledge proof containing:
  • proof_a: First proof component (64 bytes)
  • proof_b: Second proof component (128 bytes)
  • proof_c: Third proof component (64 bytes)
  • root: Merkle tree root (32 bytes)
  • public_amount: Public transaction amount (32 bytes)
  • ext_data_hash: Hash of external data (32 bytes)
  • input_nullifiers: Array of 2 nullifiers (32 bytes each)
  • output_commitments: Array of 2 commitments (32 bytes each)
ExtDataMinified
required
Minified external data containing:
  • ext_amount: Amount to deposit (positive) or withdraw (negative) as i64
  • fee: Transaction fee in lamports as u64
Vec<u8>
required
Encrypted data for the first output UTXO. This allows the recipient to decrypt and spend it later.
Vec<u8>
required
Encrypted data for the second output UTXO (often used for change).

Accounts

AccountLoader<MerkleTreeAccount>
required
The merkle tree account. PDA: ["merkle_tree"].
  • Mutable: Yes
Account<NullifierAccount>
required
First nullifier account. PDA: ["nullifier0", proof.input_nullifiers[0]].
  • Mutable: Yes
  • Initialized: Yes (created by this instruction)
  • Prevents: Double-spending of first input UTXO
Account<NullifierAccount>
required
Second nullifier account. PDA: ["nullifier1", proof.input_nullifiers[1]].
  • Mutable: Yes
  • Initialized: Yes (created by this instruction)
  • Prevents: Double-spending of second input UTXO
SystemAccount
required
Cross-check nullifier. PDA: ["nullifier0", proof.input_nullifiers[1]].
  • Must not exist: Prevents nullifier position swapping attacks
SystemAccount
required
Cross-check nullifier. PDA: ["nullifier1", proof.input_nullifiers[0]].
  • Must not exist: Prevents nullifier position swapping attacks
Account<TreeTokenAccount>
required
Account holding deposited SOL. PDA: ["tree_token"].
  • Mutable: Yes
Account<GlobalConfig>
required
Global configuration. PDA: ["global_config"].
UncheckedAccount
required
Recipient account for withdrawals.
  • Mutable: Yes
  • Can be any account type: PDA, wallet, etc.
UncheckedAccount
required
Fee recipient account.
  • Mutable: Yes
Signer
required
Transaction signer (pays for nullifier account creation and deposits).
  • Mutable: Yes
Program<System>
required
Solana system program.

Behavior

Deposits (ext_amount > 0)

  1. Validates deposit amount is within the limit
  2. Transfers SOL from signer to tree_token_account
  3. Validates and deducts deposit fee
  4. Verifies zero-knowledge proof
  5. Appends output commitments to merkle tree
  6. Emits commitment events with encrypted outputs

Withdrawals (ext_amount < 0)

  1. Verifies merkle root is known
  2. Validates zero-knowledge proof
  3. Transfers SOL from tree_token_account to recipient
  4. Deducts and transfers withdrawal fee
  5. Appends output commitments to merkle tree
  6. Emits commitment events with encrypted outputs

Code Example

Events

This instruction emits two CommitmentData events:
  • index: Position in the merkle tree
  • commitment: The UTXO commitment hash
  • encrypted_output: Encrypted UTXO data for the recipient

Validations

The proof’s root must exist in the tree’s root history (last 100 roots).
The hash of the external data must match the hash in the proof.
Validates: public_amount = ext_amount - fee (mod field_size)
  • Deposits: Fee must be within error margin of amount * deposit_fee_rate
  • Withdrawals: Fee must be within error margin of amount * withdrawal_fee_rate
  • Error margin default: 5%
Groth16 proof verification using the program’s verifying key.
Deposit amount must not exceed max_deposit_amount (default: 1,000 SOL).
Nullifier accounts must not already exist (prevents double-spending).

Errors

Error
The merkle root in the proof is not found in the tree’s root history.
Error
The calculated external data hash doesn’t match the proof’s ext_data_hash.
Error
Public amount calculation is incorrect.
Error
Zero-knowledge proof verification failed.
Error
Deposit amount exceeds the maximum allowed deposit.
Error
Tree token account has insufficient SOL for the withdrawal.
Error
Tree token account has insufficient SOL to pay the fee.
Error
Fee is below the minimum required amount.

See Also