Skip to content

Fix /vaults discovery: vetted RPC transports, one indexer query, device-known fallback - #31

Merged
fielding merged 2 commits into
mainfrom
fix/vaults-discovery-rpc
Sep 5, 2026
Merged

Fix /vaults discovery: vetted RPC transports, one indexer query, device-known fallback#31
fielding merged 2 commits into
mainfrom
fix/vaults-discovery-rpc

Conversation

@fielding

@fielding fielding commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Why

A BNB Chain user reported slow vault loads, a console full of CORS errors, and vaults that "looked merged" with balances "a bit off". Probing prod on 2026-09-05 turned up three independent causes; this PR fixes all three.

Root causes

  1. CORS storm / no Ethereum reads. wagmi was on viem's built-in default RPC per chain. Ethereum's default (eth.merkle.io) now returns 405 to the CORS preflight and 429 to POSTs. RainbowKit runs a mainnet ENS lookup on every page whenever chain 1 is configured (it has been since the 7-chain expansion), so every page load retried a dead endpoint. Noise for BNB users, but it also meant Ethereum mainnet had no working read transport at all.
  2. Broken indexer fallback. When the Sablier Envio indexer is throttled (it allows 250 req/min per IP; see Sablier subgraph health check failed #25/Sablier subgraph health check failed #27), /vaults scanned Transfer logs in 10k-block chunks (50k on Base). Every default public RPC except Arbitrum caps eth_getLogs below that (BSC: 1,000 blocks), so the fallback failed instantly on 6 of 7 chains. On BNB it would need ~74k sequential calls even at the cap. The cross-chain "where are my vaults" probe also fanned out 6 more indexer requests per empty result.
  3. Misleading labels. Lock-until / Panic Lock streams are created with total = cliff + 1s (Sablier requires cliff < total), so the "One Drop" branch was unreachable and they read "Wait, then reloads" on any device without the stored preset label. The aggregate header reused each card's "Total locked" eyebrow and summed fully-claimed test locks: this user's 3,030 + 1,010 + a claimed 5 rendered as one "4,045 Total locked".

What changed

  • config/chains.ts: each chain carries an ordered rpcUrls list (every URL checked for a permissive preflight from ripguard.xyz). NEXT_PUBLIC_RPC_URLS (JSON keyed by chainId) prepends a keyed provider per chain, public list stays as fallback. streamStartBlock / logChunkSize removed. New DEPLOYMENT_CHAINS.
  • config/wagmi.ts: explicit fallback(http…) transports from the registry.
  • lib/vaults.ts (new, tested): one indexer query for all deployment chains via GraphQL variables, parsed at the boundary into bigint/seconds, typed IndexerError with retries only on 429/5xx/network. Fallback reads stream IDs the device already knows (/create's ripguard:lock:* keys plus a cache of the last indexer result) via multicall, filtered by ownerOf. getScheduleType treats the 1-second tail as One Drop.
  • lib/retry.ts: shouldRetry option (user rejections still never retried).
  • config/abis.ts: ownerOf.
  • /vaults: rewired onto the module; header now reads "Across N vaults on " with Still locked (deposits − claimed − claimable) instead of repeating "Total locked"; honest copy for the indexer-down and device-known states.
  • AGENTS.md, README, .env.example: sharp-edge note and the new env var.

Verification

  • tsc --noEmit, eslint, vitest run (151 tests, 42 new), and the Vercel-style build (CI=1 pnpm install && next build inside packages/app) all pass.
  • Live run of the new module for the reporting wallet: indexer returns its 3 BNB streams in one ~0.7s call; the on-chain fallback for [1644, 1645, 1646, 99999, 1643] returns exactly the same three records (foreign + nonexistent IDs filtered); fallback([merkle, publicnode]) resolves vitalik.eth past the dead endpoint.
  • next start smoke: /, /create, /vaults 200; no eth.merkle.io requests.

Preview click-test

Connect the BNB wallet on /vaults: expect three cards (two "Lock until Sep 24, 2026" / "One Drop", one claimed 5 USDC), header "Across 3 vaults on BNB Chain · 0 Claimable now · 4,040 Still locked · 5 Claimed", and a clean console apart from the Vercel Analytics 404.

Notes / follow-ups

  • Stacked on docs: deploy + on-chain verification conventions #30 (docs only); merge that first or this PR carries its commit.
  • After merge: git push origin origin/main:testnet.
  • Vercel Web Analytics is not enabled on the ripguard project (/_vercel/insights/script.js 404s), so contract_error events are dropped. Tracked as tix ripguard-75b8f7.
  • For real robustness, drop a keyed RPC (Alchemy/dRPC, referrer-locked) into NEXT_PUBLIC_RPC_URLS and redeploy.

🤖 Generated with Claude Code

fielding and others added 2 commits September 1, 2026 21:26
Capture the conventions the strict-payouts block surfaced: testnet branch
fast-forward after prod merges, NEXT_PUBLIC_* redeploy requirement, the
eth_simulateV1 wallet-less verification pattern with its shared-block-gas
pitfall, and the on-chain-probed Sablier tranche facts (500 cap, exact-sum
requirement).
…ce-known fallback

A BNB Chain user reported slow vault loads, a wall of CORS errors, and
vaults that looked merged. Three separate causes:

- wagmi used viem's default RPC per chain. Ethereum's (eth.merkle.io) now
  rejects CORS preflight, and RainbowKit runs mainnet ENS lookups on every
  page when chain 1 is configured — hence the console storm, and no working
  read transport on Ethereum at all. Each chain now carries a CORS-verified
  rpcUrls list; wagmi builds fallback(http…) transports from it, and
  NEXT_PUBLIC_RPC_URLS can prepend a keyed provider per chain.
- The indexer-down fallback scanned Transfer logs in chunks larger than
  every public RPC's eth_getLogs cap (BSC: 1,000 blocks vs 10k), so it failed
  on 6 of 7 chains, and on BNB would need ~74k calls even at the cap. The
  scan is gone. Discovery is one indexer query for all deployment chains
  (the indexer allows 250 req/min per IP; the per-chain fan-out is gone too)
  with typed retries on 429/5xx, falling back to stream IDs the device
  already knows (/create's label keys + a cache of the last indexer result),
  read via multicall and filtered by ownerOf.
- Lock-until and Panic Lock streams are created with total = cliff + 1s, so
  the "One Drop" label was unreachable and they read "Wait, then reloads".
  The aggregate header repeated each card's "Total locked" eyebrow and
  summed fully-claimed test locks, which is how two vaults read as one
  merged 4,045. It now shows "Still locked" across N vaults.

Verified: tsc, eslint, 151 vitest tests, Vercel-style build, and a live
run against the indexer + BSC RPC for the reporting wallet (indexer and
on-chain fallback return identical records; the fallback transport
resolves ENS past the dead merkle endpoint).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 5, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
ripguard Ready Ready Preview Sep 5, 2026 6:14pm UTC
ripguard-sol Ready Ready Preview Sep 5, 2026 6:14pm UTC
ripguard-testnet Ready Ready Preview Sep 5, 2026 6:14pm UTC

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