Overview
Thetransact 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
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)
Minified external data containing:
ext_amount: Amount to deposit (positive) or withdraw (negative) as i64fee: Transaction fee in lamports as u64
Encrypted data for the first output UTXO. This allows the recipient to decrypt and spend it later.
Encrypted data for the second output UTXO (often used for change).
Accounts
The merkle tree account. PDA:
["merkle_tree"].- Mutable: Yes
First nullifier account. PDA:
["nullifier0", proof.input_nullifiers[0]].- Mutable: Yes
- Initialized: Yes (created by this instruction)
- Prevents: Double-spending of first input UTXO
Second nullifier account. PDA:
["nullifier1", proof.input_nullifiers[1]].- Mutable: Yes
- Initialized: Yes (created by this instruction)
- Prevents: Double-spending of second input UTXO
Cross-check nullifier. PDA:
["nullifier0", proof.input_nullifiers[1]].- Must not exist: Prevents nullifier position swapping attacks
Cross-check nullifier. PDA:
["nullifier1", proof.input_nullifiers[0]].- Must not exist: Prevents nullifier position swapping attacks
Account holding deposited SOL. PDA:
["tree_token"].- Mutable: Yes
Global configuration. PDA:
["global_config"].Recipient account for withdrawals.
- Mutable: Yes
- Can be any account type: PDA, wallet, etc.
Fee recipient account.
- Mutable: Yes
Transaction signer (pays for nullifier account creation and deposits).
- Mutable: Yes
Solana system program.
Behavior
Deposits (ext_amount > 0)
- Validates deposit amount is within the limit
- Transfers SOL from signer to tree_token_account
- Validates and deducts deposit fee
- Verifies zero-knowledge proof
- Appends output commitments to merkle tree
- Emits commitment events with encrypted outputs
Withdrawals (ext_amount < 0)
- Verifies merkle root is known
- Validates zero-knowledge proof
- Transfers SOL from tree_token_account to recipient
- Deducts and transfers withdrawal fee
- Appends output commitments to merkle tree
- Emits commitment events with encrypted outputs
Code Example
Events
This instruction emits twoCommitmentData events:
- index: Position in the merkle tree
- commitment: The UTXO commitment hash
- encrypted_output: Encrypted UTXO data for the recipient
Validations
Merkle Root Verification
Merkle Root Verification
The proof’s root must exist in the tree’s root history (last 100 roots).
External Data Hash
External Data Hash
The hash of the external data must match the hash in the proof.
Public Amount Calculation
Public Amount Calculation
Validates:
public_amount = ext_amount - fee (mod field_size)Fee Validation
Fee Validation
- 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%
Zero-Knowledge Proof
Zero-Knowledge Proof
Groth16 proof verification using the program’s verifying key.
Deposit Limit
Deposit Limit
Deposit amount must not exceed
max_deposit_amount (default: 1,000 SOL).Nullifier Uniqueness
Nullifier Uniqueness
Nullifier accounts must not already exist (prevents double-spending).
Errors
The merkle root in the proof is not found in the tree’s root history.
The calculated external data hash doesn’t match the proof’s ext_data_hash.
Public amount calculation is incorrect.
Zero-knowledge proof verification failed.
Deposit amount exceeds the maximum allowed deposit.
Tree token account has insufficient SOL for the withdrawal.
Tree token account has insufficient SOL to pay the fee.
Fee is below the minimum required amount.
See Also
- transact_spl - Transact with SPL tokens
- initialize - Initialize the program
- update_deposit_limit - Update deposit limits