Troubleshooting
Each entry gives the symptom as it appears, what causes it, and what to do. Entries link to the page that explains the mechanism.
Verifying presentations
| Symptom | Cause | Fix |
|---|---|---|
PROOF_INVALID from verifyZkPresentationProof | The proof does not verify against the circuit artifact the verifier loaded. Usually a zk-artifacts version that does not match the prover's. | Pin credential and zk-artifacts to the same release on both sides and verify the artifact digest as the artifact package describes. |
PUBLIC_INPUT_MISMATCH (with publicInput naming the field) | The expected inputs the verifier built differ from the ones the proof was made for: a different challenge, audience, time, programme or country. | Build the expected inputs from the request your backend stored, never from the submitter. See Verify presentations. |
ISSUER_ROOT_MISMATCH or REVOCATION_ROOT_INVALID from POST /v1/verify, or your own "root not accepted on chain" | The wallet proved against a root the chain no longer accepts: the issuer set or revocation tree changed and the root window has passed, or the wallet's lists were stale. | Ask the holder to refresh and prove again. If it repeats, check that the verifier reads the contracts on the intended chain. |
STALE_CURRENT_TIME | The request's currentTime is more than 300 seconds from the wallet's clock. | Fix the clock on whichever side is wrong. The limit is deliberate; do not widen it. |
CONSUMED_REQUEST, EXPIRED_REQUEST, UNKNOWN_REQUEST | The presentation request was already answered, has expired, or was never created on this server. | Create a new request with POST /v1/presentation-requests. In the member app, use New check after expiry and Retry status after a polling error. |
Issuing credentials and registering
| Symptom | Cause | Fix |
|---|---|---|
SUBJECT_NOT_ELIGIBLE or SUBJECT_ENROLMENT_PENDING | The networkId was not registered through this member, or the registration transaction has not landed. | Register first with POST /v1/registrations and wait for confirmation. The reference policy approves exactly the identities it registered. |
LEVEL_BELOW_MINIMUM | The device key's attested security level is below the issuer's MIN_DEVICE_SECURITY_LEVEL. Dev builds on the software keystore send no attestation chain. | For local work set the issuer's minimum to SOFTWARE. For anything else keep TRUSTED_ENVIRONMENT or higher and use a release build on a device with a hardware keystore. |
DEVICE_ATTESTATION_REJECTED or DEVICE_ATTESTATION_UNAVAILABLE | The attestation chain failed verification, or the wallet sent none. | Rebuild the wallet so it regenerates its keys with an attestation challenge; see the user app README. |
STATUS_LIST_UNAVAILABLE | The issuer could not reach Google's attestation status list. | Allow outbound access to it from the issuer service. |
MEMBER_NOT_APPROVED | The member's Semaphore commitment is not in the approved-member group, so it cannot produce the proof a registry write needs. | Finish admission through governance, and confirm MEMBER_IDENTITY_SECRET matches the commitment that was admitted. |
NOT_CONFIGURED or CHAIN_MISSING | A route needs settings the service was started without. | Check the issuer service configuration. |
| Revocations disappear after a restart | ALLOW_IN_MEMORY_REVOCATION=true keeps them in process memory and never writes the chain. | Use it only in local tests. See Keep the safe defaults. |
| Indexer stops finding a user's accounts after a key change | Rows written under the old ENCRYPTION_KEY or LOOKUP_HASH_KEY are never scanned again. | Before changing either key, DELETE /keys/:networkId for each user; after, POST /keys again with the original fromBlock. See Protect your secrets. |
| Service refuses a key or token at startup | MEMBER_SERVER_TOKEN is under 16 characters, or a 32-byte key is in the wrong format. | The indexer takes 0x plus 64 hex characters; the issuer service takes 64 hex characters with no prefix. |
Wallet and member apps
| Symptom | Cause | Fix |
|---|---|---|
| The app will not run in Expo Go | The wallet needs two native modules, the keystore and the prover, that Expo Go does not ship. | Make a dev build: yarn workspace user-app android. |
| "This wallet needs Android 12 or newer" | Android below API 31 has no key-agreement purpose in the keystore. | Use a device or emulator on Android 12 or later. |
PROOF_FAILED returned to the member app | The native prover library failed to load on the first proof. It loads lazily so that a load failure does not stop the wallet from starting. | Rebuild the wallet from a clean prebuild; check that noir-prover-ffi is installed for the device's ABI. |
First launch hangs with SocketTimeoutException | Metro advertised the laptop's LAN address, which a phone on USB only cannot route to. | Reverse the ports with adb reverse and pin the hostname as the user app README shows. |
| Installing Member B removed Member A | Both member apps were built from the same prebuild and share an application id. | Build each with its own prebuild: prebuild:a --clean then android:a, and prebuild:b --clean then android:b. See the Sepolia demo. |
| A deep link got no answer | It arrived while the wallet was replacing its keys after a lock-screen change, or the wallet was not running. | Retry the request. |
| A credential record will not open; the wallet throws on read | The encrypted store was written under a different key. | Settings, then Erase everything, and re-enrol. |
| The dev launcher's error screen crashes the app | A known crash in expo-dev-launcher 6.0.21 when its buttons are tapped. | Relaunch from the launcher without tapping the error screen. |
Local and testnet stack
| Symptom | Cause | Fix |
|---|---|---|
| The stack refuses the deployment file | The committed 11155111.json describes another network, owned by a different OWNER and with governance never configured, or the file's chain id or contract code does not match. | Deploy your own network as the Sepolia demo shows. The deploy rewrites the file, so the stack finds it without DEPLOYMENT_FILE. |
eth_getLogs errors or events never arrive | The RPC provider caps log ranges below STACK_LOG_WINDOW_BLOCKS (10,000 by default). Alchemy's free tier allows 10 blocks. | Use a provider that serves the range, or lower STACK_LOG_WINDOW_BLOCKS to what it allows. |
| Nonce errors during genesis | Genesis and the serving stack both sign as OWNER. | Stop the stack before yarn stack:genesis. A rerun of genesis only sends what is missing. |
| The preflight refuses with an ETH figure | OWNER or DEPLOYER holds less than the deploy or genesis needs. | Fund to the figure it prints. Rough totals are in the Sepolia demo. |
| Dashboard or proxy answers 421 | The request's Host was a DNS name; the testnet stack accepts an IPv4 address only. | Use the IPv4 address that STACK_HOST names. |
| Governance events show up half a minute late | Member servers and the dashboard read events two blocks after they land. | Wait; --check already does. |
| A rerun of the deploy script changed the file's block | Every deploy uses a fixed salt, so a rerun reuses the contracts and rewrites the block the stack reads logs from. | Keep the first run's file and never rerun after genesis. For a fresh network, withdraw the stake, then create new secrets as the Sepolia demo shows. |