Batch and cache Alchemy price API requests to reduce quota usage - #2593
Merged
MusabShakeel576 merged 4 commits intoSep 28, 2026
Merged
Conversation
…quota Production has been getting 429s with "Your payg app has exceeded its limit of 10000 token_price requests per 1 hours" (Sentry SOLID-PJ, SOLID-DV). That quota counts requests, and one request can carry 25 tokens, but every price was its own request and nothing was reused: - useBalances refreshed every 5s, and each refresh sent five by-symbol GETs (ETH once per chain, FUSE, BNB) plus one per token still unpriced. That is about 3,600 requests an hour for a single open app, so two or three users used up the whole app's quota. - fusePriceUsd / ethPriceUsd polled every 5s on top of that. Changes: - lib/batchedLoader merges lookups started in the same tick into requests of up to 25. It caches prices for a minute (misses for five), keeps serving the last price while a refresh fails, and pauses both Prices endpoints after a 429. - fetchTokenPriceUsd and the new fetchTokenPricesBySymbol go through it. By-address requests stay within the documented limits of 25 addresses and 3 networks. - Balance fallback poll goes from 5s to 30s (SSE already invalidates on every balance event), native price refresh from 5s to 60s, and vault balance poll from 3s to 30s. - publicClient keeps one client per chain with Multicall3 batching, so reads made together share one eth_call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMuZxhszcH7Kc5s7dc2NNq
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…o Alchemy Prices now come from POST /accounts/v1/prices, which serves one cache shared by every user and every pod. So the app key's Alchemy token_price quota no longer scales with the number of open apps. The backend is only an optimisation for the app. On any failure it asks Alchemy directly with its own key, exactly as before, and skips the backend for five minutes. Failures include a network error, a timeout, an older backend without the route (404), an expired session (401), the per-user rate limit (429), a token the backend can't price right now (503), or a reply that isn't the expected shape. That keeps every combination safe: - an app build or OTA that ships before the backend route is deployed - a backend rollback - web and native (cookie or Bearer auth) Old builds keep calling Alchemy directly. Symbols and addresses the route's validation would reject are left out of the request, so one malformed token can't fail the lookup for the rest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMuZxhszcH7Kc5s7dc2NNq
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
The backend's price route leaves out tokens on chains it has no Alchemy network for, and the app takes a token left out as having no price and remembers that for five minutes. A chain added to ALCHEMY_NETWORKS (which an OTA can do) before the backend knows it would lose its prices that way. Address lookups are now chunked by whether the backend covers the chain (BACKEND_PRICE_CHAIN_IDS, mirroring its ALCHEMY_PRICE_NETWORKS), and tokens on any other chain are priced from Alchemy directly, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMuZxhszcH7Kc5s7dc2NNq
… polls The slower polls were there to save the Prices API quota. Price lookups are now cached for a minute, in the app and by the backend's shared cache, so polling more often no longer costs token_price requests, and the slower intervals only made balance updates visibly lag. Balances poll every 5s again (staleTime 5s), native-token prices every 5s, and vault balances every 3s, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMuZxhszcH7Kc5s7dc2NNq
MusabShakeel576
deleted the
claude/alchemy-threshold-limit-emails-k9buga
branch
September 28, 2026 11:33
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Refactored token price fetching from Alchemy to batch multiple lookups into single requests and cache results, reducing API quota consumption from ~40 requests per balance refresh to just 2-3 requests total.
Key Changes
New
BatchedLoaderutility (lib/batchedLoader.ts): Generic batching and caching layer that merges lookups started in the same tick into minimal upstream requests, with configurable TTLs, stale-value handling, and shared rate-limit pausing across loaders.Refactored Alchemy price fetching (
lib/api.ts):fetchTokenPriceUsdintofetchTokenPricesBySymbol(batch up to 25 symbols per request) and kept the original as a wrapperfetchTokenPricesByAddressto use batching with network-aware chunking (respects 3-network limit per request)clearAlchemyPriceCache()for test isolationUpdated balance fetching (
hooks/useBalances.ts):fetchTokenPricesBySymbolOptimized viem client usage (
lib/wagmi.ts):Polling intervals unchanged from
master: balances every 5s, native prices every 5s, vaults every 3s. An earlier commit slowed these down, and 0de3947 restores them: price lookups are now cached for a minute (in the app and by the backend), so polling this often no longer costs Prices API requests.Comprehensive test coverage (
lib/__tests__/batchedLoader.test.ts,lib/__tests__/alchemyTokenPrices.test.ts):Implementation Details
Cooldownobject, allowing symbol and address loaders to pause each other when either hits a 429chainId:addresskeyFollow-up: prices from the backend's shared cache
POST /accounts/v1/prices, added in Solid-Money/solid-backend#1871). It serves one cache shared by every user, so open copies of the app no longer each spend this key's token_price quota.BACKEND_PRICE_CHAIN_IDS). A chain added toALCHEMY_NETWORKSbefore the backend has it (for example by an OTA) is still priced from Alchemy, instead of coming back unpriced and being cached as a miss.appVersionruntime policy.Tested:
alchemyTokenPrices.test.ts(32 tests) covers the backend path, each fallback, the pause and its expiry, input filtering, and the uncovered-chain case.tscreports only the 9 errors that were already there.skipTheLine.test.tstests, which fail the same way without these changes.🤖 Generated with Claude Code
https://claude.ai/code/session_01UMuZxhszcH7Kc5s7dc2NNq
<source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v2.svg"><img alt="View with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg">https://backend.blacksmith.sh/track/enable-autofix?expires=1793102576&installation_model_id=437840&pr_number=2593&ref=codesmith_pr_footer&repository=Solid-Money%2Fsolid-ui&return_to=https%3A%2F%2Fgithub.com%2FSolid-Money%2Fsolid-ui%2Fpull%2F2593&signature=df3bb7c772bedc96f5b419c2a2dcb7bcd75d5cf70de878f54abbbe4f02f5b58f"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img alt="Autofix with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg">Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.