zkSVM

App

Litepaper

Documentation

Links

DocsBuild

Errors

Every refusal, and what causes it.

3 min read

Every way the program says no, and what the app and the SDK make of it. A refused operation is a failed Solana transaction: it changes nothing, and costs only the network fee.

Program errors

ErrorRaised whenUsual cause
BadFieldA field element in the arguments is at or above the BN254 modulus.Bytes were not produced by the SDK's frToBytes.
UnknownRootThe proof targets a root outside the last 64.More than 64 notes were added between building the proof and landing it. Rebuild against the current tree.
InvalidProofGroth16 verification failed.See below.
ZeroAmountA shield or unshield of 0.
NotYetA reclaim before the note's not_before.The validator clock has not reached the return time.
RateMovedA shield_stake whose note claims more tokens than the stake pool minted.The stake pool's rate changed between building and landing — it does once an epoch. Build again.
TreeFullThe tree holds 2²⁶ notes.

System program errors

ErrorMeaning
account already in useThe nullifier record exists: the note is already spent. This is the double-spend check.
insufficient lamportsThe payer cannot cover the shield amount, the rent for a nullifier record, or the fee.
insufficient funds for rentAn unshield to a brand-new address of less than the rent-exempt minimum (~0.00089 SOL).

InvalidProof

The proof binds more than the hidden values. It fails when anything it names differs from what the transaction carries:

OperationAlso bound into the proof
transferboth nullifiers, both commitments, the fee
unshieldthe recipient account and the amount
claim, redeemthe owner account that signed
reclaimnot_before
attestthreshold, scope, tag

So swapping the unshield recipient, signing a claim with a different address, lowering a not_before or raising a threshold after the fact all surface as InvalidProof.

The other cause is mismatched keys: a proof built with proving keys from one setup, checked by a program compiled with verifying keys from another. Every proof fails, for every user. Rebuild the app's artifacts from the same circuits/build as the program.

What the app shows

The app saysUnderlying cause
That note has already been spent.account already in use
The pool moved on while the proof was being built. Try again.UnknownRoot
The staking rate moved while this was in flight. Try again.RateMoved
Cancelled in the wallet.The wallet rejected the signature request.
The cached tree does not match the chain.The local mirror diverged — a reset cluster, or another deployment at the same id. The app resyncs by itself.
The pool account does not exist on this cluster.The program is deployed but not initialized, or the cluster is wrong.

User-facing fixes: Troubleshooting.

Handling them in a wallet

  • Mark inputs spent when you submit, not when the transaction lands, so a double click cannot race itself.
  • On UnknownRoot, resync and rebuild. Nothing was spent.
  • On already in use, resync and drop the note. The earlier transaction won.
  • Check an unshield's destination balance before proving, and refuse amounts below the rent-exempt minimum for new accounts.