Skip to content

fix(e2e): stop demo balance footers from rate-limiting the faucet - #985

Merged
snario merged 3 commits into
mainfrom
claude/fix-faucet-e2e-rate-limit
Oct 14, 2026
Merged

snario merged 3 commits into
mainfrom
claude/fix-faucet-e2e-rate-limit

Conversation

@snario

@snario snario commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Summary

fund a connected passkey wallet via faucet wasn't flaky because of the faucet itself. The demo's balances footer sent enough RPC traffic to trigger the public RPC's per-client rate limit, which then rejected the faucet request. The same failure explains send a payment with OUSD fees, the most frequent live-test failure since Oct 9.

Two related fixes landed while this PR was open:

Readers still use the public RPC, so the footer's request volume still matters. This PR builds on both changes:

  • Footer (src/components/guides/Demo.tsx): one Transfer filter per direction covers every footer token (2 filters instead of 2 per token), and a transfer refreshes only the affected token's balance. The footer reads symbol() instead of full token metadata, since the symbol is all it displays.
  • Faucet tests (e2e/faucet.test.ts): both tests use expectDemoSuccess, so a faucet or RPC error fails in seconds with the step's message instead of after 120s. The passkey test requests funds after the footer shows all five balances, as a person would, instead of ~100ms after connecting, inside the page's initial read burst.
  • New Demo.test.tsx: covers the footer's watcher shape, the balance query-key invalidation (checked against wagmi's real getBalance key), and the step's alert role. It fails if role="alert" is removed. One demo-success fixture now covers an alert nested inside step content instead of a class-based case.

Root cause

The balances footer rate-limited the page. Container.BalancesFooter watched Transfer events with two filters per token. Moderato's 1s block time made viem poll every 500ms, so the faucet page's 5-token footer polled 10 filters about 20 times per second. On connect it also read 6–10 metadata fields per token (batch.multicall: false makes each field a separate eth_call). The footer also re-subscribed on every render because it passed useWatchTransfer a new inline callback each time. Each 429 from Cloudflare in front of rpc.moderato.tempo.xyz (-32005, "rate limited, try again in ~500ms" or "too many connections from this IP") is retried by viem up to 16 times (4 client retries × 4 fallback retries).

The retry trace from the failing CI job on #979 (run 38493328846) shows:

Count
RPC HTTP requests in 2 minutes 5,692
429 responses 4,429
tempo_fundAddress attempts 16 (all 429, within ~6s)

The step showed "An unknown RPC error occurred." (first attempt) and "WebSocket request failed." (retry) within seconds. But #962 had removed the bg-destructiveTint class that expectDemoSuccess matched, so the test waited 120s for "Add more funds".

Each attempt used a fresh virtual passkey, and so a fresh address. The retry ran after every other test in its shard had finished. So this isn't per-address faucet limiting or contention between shards: one page exceeds the limit. The public RPC's limit isn't documented in this repo; /docs/api/rate-limits covers the Tempo API.

I didn't raise timeouts or retries. The live check still gates merges: with the footer fixed, a 429 on the faucet means the page or the RPC changed, which is worth failing on.

Evidence

Failure frequency (Verify runs Sep 13 – Oct 14: 356 runs, 961 E2E jobs, all attempts):

Test First-attempt failures Failed retry too
fund a connected passkey wallet via faucet 2 2
send a payment with OUSD fees 8 6

The faucet failures were on Oct 9 and Oct 14 (#979). The OUSD-fees reports I checked show the same step error, "Request exceeds defined limit." (-32005), on a page with a 4-token footer. Together these two tests caused 8 of the 22 runs with a hard E2E failure since Oct 9. I found no live-test failures before Oct 9, when E2E moved to ubuntu-24.04-arm runners with 4 browsers (#959).

Local A/B against production builds made like CI (VITE_E2E=true VITE_USE_HTTP=true pnpm build:site, served with dist/preview.js). Each build ran this PR's e2e/faucet.test.ts, one at a time:

Build Passkey test passed Passkey test time JSON-RPC calls per run 429s per run tempo_fundAddress attempts
main before #957 (c2513b7) 0/4 Footer never showed balances (failed at 30s) 1,584–2,450 in 30s 1,427–2,269 Never reached
This PR on c2513b7 8/8 3.9–13.3s 53–230 8–161 1–4
Current main (with #957) 8/8 10.0–27.0s 231–536 142–377 1–6
This PR merged with main 8/8 3.6–9.4s 43–111 10–86 1–3

On current main, the single-filter footer cuts the passkey run's RPC calls by about 5× and its 429s by about 8×, and halves the test's run time. "Fund an address" passed in every run of every build. After merging #984, three more runs on the merged build passed (passkey test 4.4–5.9s; the local runs use no RPC key, so they still go through the public limit).

Checks: pnpm check, pnpm check:types, and pnpm test (including #957's demo-rpc.test.ts) pass. Playwright demo-success.test.ts passes (5/5).

Co-Authored-By: Liam Horne [email protected]

Prompted by: Liam

🤖 Generated with Claude Code

The balances footer watched Transfer events with two filters per token,
so the faucet page (5 tokens) polled rpc.moderato.tempo.xyz with 10
filters every 500ms and read ~10 metadata fields per token on connect.
viem retries each 429 up to 16 times, so a connected page sent ~5,700
RPC requests in 2 minutes and the RPC's per-client limit rejected every
tempo_fundAddress attempt. The step showed the error within seconds, but
expectDemoSuccess no longer saw it: #962 replaced the step's
bg-destructiveTint class that its locator matched.

- Watch transfers for every footer token with one filter per direction
  and read only each token's symbol.
- Render step errors with role="alert" and locate them by role, so live
  tests fail fast with the step's message again.
- Make both faucet tests use expectDemoSuccess, and request funds after
  the footer's initial balance reads finish.
- Unit-test the footer's watcher shape and the step's alert role.

Co-Authored-By: Liam Horne <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
@snario snario self-assigned this Oct 14, 2026
@vercel

vercel Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
tempo-docs Ready Ready Preview Oct 14, 2026 5:47am UTC

Request Review

…-rate-limit

# Conflicts:
#	e2e/demo-success.test.ts
#	src/components/guides/Demo.tsx
…-rate-limit

# Conflicts:
#	e2e/faucet.test.ts
@snario
snario enabled auto-merge (squash) October 14, 2026 05:27
@snario
snario merged commit 1189fce into main Oct 14, 2026
14 checks passed
@snario
snario deleted the claude/fix-faucet-e2e-rate-limit branch October 14, 2026 05:49

This branch was successfully deployed

1 active deployment
Preview — 60fb52cc Deployed Oct 14, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant