Skip to content

perf(wallet): pace and dedupe the signer's token-name lookups - #421

Open
QSchlegel wants to merge 1 commit into
preprodfrom
claude/daily-2026-10-23-asset-metadata-fanout
Open

QSchlegel wants to merge 1 commit into
preprodfrom
claude/daily-2026-10-23-asset-metadata-fanout

Conversation

@QSchlegel

Copy link
Copy Markdown
Collaborator

What

Connecting a wallet looks up the signer's own token names (/assets/{unit}) for the Deposit page. Both connect paths in connect-wallet.tsx, CIP-30 and UTXOS, did this one request at a time, with no pacing, and repeated every lookup each time the asset list changed. This was the only Blockfrost fan-out in the browser that the throttle from #414 (266a74e) didn't cover, and it uses the same public key and the same 500-request burst / 10 req/s budget as the wallet refresh and tx building.

Both paths now call fetchUserAssetMetadata in the new src/utils/user-asset-metadata.ts. It:

  • skips lovelace, duplicate units, and units the store already has a name for, so a re-run on an unchanged wallet sends 0 requests (previously N);
  • joins a lookup that is already in flight for the same network and unit, for example from a second mounted connector or a re-run effect, instead of sending it again;
  • paces the rest through createBlockfrostThrottle, with 4 in flight and one start every 250 ms. That is 4 req/s, so one run alongside the refresh's 5 req/s stays under 10 req/s. Repeated 429s trip the throttle's breaker, which stops the batch;
  • writes each name as it arrives through onEntry, so names still appear one by one like before;
  • leaves failed lookups out, so the token falls back to its unit, as before, and is tried again on the next run.

Numbers

These are worked out from the code, not measured in a browser. N is the number of tokens and RTT the round-trip time to Blockfrost.

before after
Requests on a re-run, nothing new N 0
Time for a first load N × RTT (serial) ≈ max(N × 250 ms, N × RTT / 4)
40 tokens, RTT 300 ms ~12 s ~10 s
40 tokens, RTT 100 ms ~4 s ~10 s (paced to 4 req/s on purpose)
Peak rate on the shared key 1/RTT, unpaced ≤ 4 req/s per run

The first load is not always faster: on a fast connection the pacing makes it slower on purpose. The gains are fewer requests and staying under the shared rate limit. Bundle: / is 232.6 → 232.7 KB, and every route is still within budget.

Behaviour changes to be aware of

  • Token names are cached per session by unit only, as userAssetMetadata already is. After a network switch, a unit that exists on both networks keeps the first network's name. This is rare: it needs the same policy and asset name minted on both networks.
  • A lookup that succeeds but returns no name stores the unit as the name, and is not retried during that session. Before, every run fetched it again.
  • If a lookup B joins one that A started, and A's throttle breaker is open, B also gets no name this run.

Tests

  • New file src/__tests__/userAssetMetadata.test.ts, 13 tests. It covers: skip/dedupe, choosing the name field, no request when everything is known, only new units fetched, concurrency limit, failures left out, re-run is a no-op, names reported progressively, in-flight join, no join across networks, default pacing of 250 ms (fake timers), and the breaker stopping after 3 × 429.
  • npx tsc --noEmit: clean.
  • node scripts/run-tests.mjs: 1669 passed, 2 skipped (151 suites), plus 97 passed (11 suites).
  • SKIP_ENV_VALIDATION=1 npx next build --webpack and node scripts/check-bundle-budget.mjs --report: within budget.
  • An independent review agent read the diff cold, twice. Its findings are fixed: progressive writes, no results dropped on cancel, accurate pacing comments, a network-keyed join, and the extra tests.

Not verified: a manual check in a browser with a real wallet. Playwright runs in CI.


Generated by Claude Code

Connecting a wallet looked up every token's name with one /assets/{unit}
call at a time, unpaced, and repeated all of them whenever the asset list
changed. That is a serial waterfall on the site's shared Blockfrost key,
the one fan-out in the browser the wallet refresh's throttle did not cover.

Both connect paths (CIP-30 and UTXOS) now go through
fetchUserAssetMetadata, which skips units already named this session,
joins a lookup another caller has in flight, runs the rest 4 at a time
through a 4/s Blockfrost throttle, and writes each name as it arrives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2MdLjx7a7nPPhihZNDoMF
@vercel

vercel Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
multisig Ready Ready Preview Oct 23, 2026 5:12am UTC

Request Review

This branch was successfully deployed

1 active deployment
Preview — 60720051 Deployed Oct 23, 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.

2 participants