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
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 i64fee: 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)
- 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
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
- transact_spl - Transact with SPL tokens
- initialize - Initialize the program
- update_deposit_limit - Update deposit limits