Errors
Errors
Every contract error decodes to a human message in the UI, a raw Error(Contract, #N) never reaches a user. The full table, with likely cause and remediation:
| Code | Variant | Message | Cause & remediation |
|---|---|---|---|
| 1 | Unauthorized | This wallet is not authorized for that issuer action. | Only the pool issuer can update the accredited set or advance the epoch. Connect the issuer wallet. |
| 2 | InvalidProof | The zero-knowledge proof is invalid. | The proof did not verify against the committed key. Regenerate it; check you used the current root and the correct circuit artifacts. |
| 3 | RootMismatch | Accreditation changed; request a fresh Merkle path and proof. | The issuer committed a new root after you fetched your path. Request a fresh accreditation and re-prove. |
| 4 | EpochMismatch | The subscription epoch changed; regenerate the nullifier and proof. | The issuer advanced the epoch between proving and subscribing. Regenerate the nullifier (it depends on epoch) and re-prove. |
| 5 | NullifierUsed | This investor already subscribed in the current epoch. | This secret already subscribed in the current epoch. Wait for the next epoch or use a different accreditation. |
| 6 | AmountInvalid | The amount is invalid or differs from the proof. | The amount sent to subscribe must equal the amount in the proof and stay within the cap and 64-bit range. |
| 7 | PaymentFailed | Mock-USDC settlement failed; obtain faucet funds and retry. | The Testnet USDC transfer failed, usually for lack of balance. Use the faucet, then retry. |
| 8 | NotInitialized | Pool state is not initialized. | The pool has not been initialized. This should not happen for the deployed demo pool. |
Decode either ContractError(n) or Error(Contract, #n). Wallet-authorization failures surface as Soroban host authentication errors and map to a friendly “signature declined” or “authorization failed” message.
How an error reaches you
The contract returns a numeric code. The frontend never shows that raw code. lib/errors.ts runs a single decodeError pass that:
- extracts the contract code from any of Error(Contract, #n), ContractError(n), or a bare #n;
- maps it to the variant name and the human message in the table above;
- falls through to wallet and network heuristics (declined signature, missing extension, insufficient balance, network timeout) when no contract code is present;
- returns a generic, safe message rather than leaking internals when nothing matches.
The result renders as a toast with a title and a one-line description. A raw stack trace never reaches the UI.
The two most common recoveries
The two errors a normal user actually hits are round changes and double subscriptions. Both are expected, not bugs:
- Round moved (codes 3, 4). The issuer committed a new root or advanced the epoch between your proof and your submission. The nullifier and the root are part of the proof, so request a fresh accreditation and prove again. The UI narrates this in plain language rather than surfacing the code.
- Already subscribed (code 5). Your one-time tag for this round is spent. Wait for the next epoch. A same-nullifier replay is exactly what should fail, and it finalizes FAILED on chain (see the for-judges page for the live replay transaction).
Payment failures (code 7)
A PaymentFailed almost always means the investor wallet lacks Testnet USDC. Use the faucet, wait for the mint to settle, then resubmit. The proof itself does not need regenerating, since the amount and round did not change.