# Debugging

> When something goes wrong end-to-end, here's where to look first.

When something goes wrong end-to-end, here's where to look first.

## Decode a PoolVault revert

Reverts come back as 4-byte selectors. Decode:

```bash
cast sig "PoolVault_NullifierAlreadySpent()"    # → 0x66f527f1
cast sig "PoolVault_InvalidTransactProof()"     # → 0x...

# Or brute-force against a list:
for E in PoolVault_NullifierAlreadySpent PoolVault_InvalidProcessor PoolVault_InvalidTransactProof PoolVault_ProofContextMismatch; do
    echo "$E:" $(cast sig "${E}()")
done
```

## Trace relayer 4xx

When the [relayer](/operations/relaying) rejects a submission, `session.relayTransfer` throws a `RelayerRejected` whose message carries the relayer's HTTP status (e.g. `"HTTP 400: Bad Request"`). The SDK surfaces the status, status text, and up to 500 characters of the response body in `err.message`. Check `err.message` first, since the relayer's body usually names the rejection reason. `RelayerRejected` is type-only in the public package, so branch on `err.name`.

```ts
try {
    await session.relayTransfer(prepared.relayOptions[0]);
} catch (err) {
    if (err instanceof Error && err.name === "RelayerRejected") {
        console.error("Relayer rejected:", err.message);
    }
    throw err;
}
```

## Verify proving-key vs verifier match

If you see `PoolVault_InvalidTransactProof` on a transact that should otherwise be valid:

1.  Confirm your SDK and circuits belong to the same deployment set.
2.  Confirm your pool address matches the verifier those circuits were compiled against.
3.  See [Sepolia V9 addresses](../deployments/sepolia#contract-addresses-sepolia-v9).

## Local state drift recovery

If `NoteNotFoundError` or `PoolVault_NullifierAlreadySpent` hits after one transaction lands and the follow-up flow fails:

1.  `session.discoverNotes()` to re-reconcile.
2.  If [notes](/concepts/notes) are still wrong, clear local cache: `localStorage` keys with prefix `privacy-pool:` (the default namespace set in `LocalStorageService`).
3.  Next `discoverNotes()` after the clear rescans from genesis (the cursor resets to `0x0`), so pass `fromBlock` with the pool's deploy block to bound it.

## Builder validation errors

`InvalidBuilderConfig` messages carry the failing field as `path: message` from the first Zod issue. When several fields are wrong, only the first is shown, so wrap your call site and dump the config object to see the rest.

## RPC rate limits

Symptom: [discovery](/operations/manage-notes) hangs for many minutes, eventual 429 errors in the network tab. Fixes:

-   Configure `aspUrl` so the SDK uses the snapshot fast-path.
-   Pass `fromBlock` to `session.discoverNotes({ fromBlock })` so scans start from the pool's deploy block instead of genesis.
-   Switch to a paid RPC tier.

## Stale bundler cache after an SDK swap

After swapping an SDK tarball, `pnpm install` reshuffles the `.pnpm/` identities, but a stale build cache can still hold compiled references to the old paths. In the Next.js-based Payroll PoC the symptom is `ENOENT: ...privacy-pools-v2-sdk@{old-hash}.tgz...`, fixed by clearing the `.next/` cache:

```bash
rm -rf .next
pnpm dev
```

The current v2 frontend is an Expo app rather than Next.js, so the equivalent reset there is clearing the Metro bundler cache with `expo start -c`.
