A rate limit whose viem args carry a nonce is classified as `nonce`, not `rpc`
- Dominant language
- TypeScript
- Stars
- 1
- Forks
- 2
- Avg merge
- 6d 8h
- Merged PRs (30d)
- 10
Description
## What happens
A rate-limited buy whose formatted viem `Request Arguments` contain a nonce is classified as
`nonce`, not `rpc`, and the player is told:
> Nonce error — please try again in a few seconds
for what is actually a rate limit. The advice is wrong (waiting a few seconds does not clear a rate
limit any faster than it clears itself) and the analytics category is wrong, which matters because
`rpc` volume is the signal we use to decide whether Forno needs replacing.
## Root cause
`RULES` in `apps/web/src/lib/buyErrors.ts` is first-match-wins, and `nonce` is **rule 1** while `rpc`
is **rule 9**. Rule 1's test is a bare substring check:
```ts
{
category: 'nonce',
test: (hay) => hay.includes('nonce'),
...
}
```
viem includes a formatted `Request Arguments:` block in the message of many transport errors, and that
block contains a `nonce:` line whenever the request carried one. So a 429 on a request that had a
nonce matches rule 1 before rule 9 can see it.
## Reachability — NOT confirmed, and that is the point of this issue
Raised during review of #246 and deliberately recorded as **reachability-unverified**:
- The mechanism is real and reproducible — there is a constructed fixture in
`apps/web/src/__tests__/lib/buyErrors.test.ts` that demonstrates it, and it is pinned there as
known behaviour so a deliberate reorder shows up as a test change.
- **No real captured viem error has been produced** showing the nonce token in a rate-limited buy.
The fixture is hand-built. Whether Forno's 429 responses on this app's buy path actually carry a
nonce in the formatted args is unknown.
This is filed so the finding does not evaporate, **not** as a confirmed defect. Treat the first task
as answering the reachability question, not as writing a fix.
## Suggested fix — only if reachable
Tighten rule 1 so it matches a nonce *error* rather than the presence of the word. Something like
requiring `nonce too low` / `nonce too high` / `invalid nonce` / `nonce has already been used`, rather
than a bare `includes('nonce')`.
**Do not simply move `rpc` above `nonce`.** The rule ordering is load-bearing in both directions and
the file says so; reordering to fix this could hand a genuine nonce error to `rpc`. The ordering guard
added in #246 will go red if anyone tries, which is the intended behaviour.
**What this does NOT fix:** nothing about the player-facing copy for genuine nonce errors, and nothing
about `rpc` classification generally — #246 already widened the haystack that fed this path.
## Non-goals
No second classifier. No change to `GENERIC_RETRY_MESSAGE` behaviour for other categories.
## Depends on / blocks
Depends on #246 (which pins the current behaviour). Blocks nothing. **Explicitly not a blocker** for
#246 or #251.
## Acceptance criteria
- [ ] Reachability answered first: either a captured real viem error showing the nonce token in a
rate-limited buy, or a written finding that it does not occur and why — recorded here either way
- [ ] If reachable: rule 1 matches nonce *errors*, not the substring — pinned in a test
- [ ] If reachable: the rate-limit-with-nonce fixture in `buyErrors.test.ts` moves from the
known-migration list to asserting `rpc`
- [ ] If not reachable: the pinned fixture keeps its comment, updated to say so with the evidence
- [ ] Rule ordering guard from #246 still green either way
- [ ] Mutation count stated in the PR
## How we'd know it's fixed
The `nonce` category stops appearing in analytics alongside 429s. Today that correlation cannot be
checked because both collapse into the same bucket — which is itself part of the reason to answer the
reachability question with captured data rather than reasoning.
Contributor guide
Assessment
This issue has not been assessed yet.