feat: add RPC fallback resilience for unchained outages#12017
feat: add RPC fallback resilience for unchained outages#12017kaladinlight merged 13 commits intodevelopfrom
Conversation
When unchained's RPC proxy returns 503s, first-class EVM chains lose all functionality. This adds public fallback RPCs so the app degrades gracefully instead of breaking completely. - Add FALLBACK_RPC_URLS constants and public fallback endpoints for all 32 EVM chains in viemClient.ts (viem transport layer) - Add EvmBaseAdapter fallback for getAccount, broadcastTransaction, and getGasFeeData that routes through the viem client when unchained fails - Add CSP connect-src entries for all fallback RPC origins via a shared fallbackRpcUrls.ts constants file Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughCentralizes fallback RPC URLs into a new FALLBACK_RPC_URLS export, updates viem client transports to use those fallbacks, extends CSP connect-src lists across ~35 chain files, and refactors EVM adapters/tests to use Viem PublicClient with direct-RPC fallbacks on HTTP failures. Changes
sequenceDiagram
participant Client
participant Adapter as EVM Adapter
participant HTTP as HTTP Endpoint
participant Viem as Viem PublicClient
participant RPC as Direct RPC
Client->>Adapter: request (getAccount / sendTx / estimate)
Adapter->>HTTP: attempt HTTP request
HTTP-->>Adapter: fails/error
Adapter->>Viem: call Viem client (getBalance/nonce/sendRawTx/estimateGas)
Viem->>RPC: direct RPC transport (envUrl + FALLBACK_RPC_URLS)
RPC-->>Viem: response (balance/tx/hash/fees)
Viem-->>Adapter: result
Adapter-->>Client: return fallback result
Note over Adapter: console.warn emitted when falling back
🎯 4 (Complex) | ⏱️ ~75 minutes
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
NeOMakinG
left a comment
There was a problem hiding this comment.
✅ LGTM — Important resilience improvement
Changes (35 files):
fallbackRpcUrls.ts— Centralized fallback RPC URLs for all 32 EVM chainsviemClient.ts—createFallbackTransport()helper wraps primary + fallback RPCsEvmBaseAdapter.ts— Fallback paths forgetAccount(),broadcastTransaction(), etc.- CSP headers — All fallback URLs added to chain CSP files
Why this matters:
- When unchained's MoralisService returns 503, the app no longer breaks completely
- Graceful degradation to public RPCs (LlamaRPC, official chain RPCs)
- Every EVM chain now has at least one fallback
CI passes ✅
🤖 Reviewed by Claude Code
🤖 QABot Test Report✅ 1/1 tests passed
Verified: Fallback RPC URLs added for all 32 EVM chains, createFallbackTransport helper works. 📊 Full Report: https://qabot-kappa.vercel.app/runs/4d89df7b-ae3a-4cb0-b95f-622ef0024ded 🤖 Automated QA by Claude Code |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/chain-adapters/src/evm/EvmBaseAdapter.ts (1)
646-648: Use structured logger instead ofconsole.warnin fallback paths.These warnings should go through the project logger with context fields (
chainId, operation, primary error, fallback status) for observability consistency.As per coding guidelines "ALWAYS log errors for debugging using structured logging with relevant context and error metadata".
Also applies to: 746-748, 991-993
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chain-adapters/src/evm/EvmBaseAdapter.ts` around lines 646 - 648, Replace the direct console.warn calls in EvmBaseAdapter.ts fallback paths with the adapter's structured logger (e.g., this.logger.warn) and include contextual fields: chainId (this.chainId), operation (e.g., "getAccount"), primaryError (the caught error variable), and fallback="true"; specifically update the warning before calling getAccountFallback(pubkey, viemClient) and the similar calls at the other locations (around the code at the other fallback sites) so the log message reads a concise descriptive string plus the structured context and the original error metadata.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@headers/csps/chains/bnbsmartchain.ts`:
- Line 17: The CSP connect-src list is missing two BSC fallback endpoints used
by the BscChainAdapter broadcast fallback; update the connect-src directive to
include the same hosts as FALLBACK_RPC_URLS.bsc (add
https://bsc-dataseed.binance.org and https://bsc-dataseed1.ninicoin.io) or
replace the current entries with a consolidated set derived from
FALLBACK_RPC_URLS.bsc so the adapter's fallback calls are not blocked; ensure
the change aligns with the CSP declaration that references the BscChainAdapter
broadcast logic and FALLBACK_RPC_URLS.bsc.
---
Nitpick comments:
In `@packages/chain-adapters/src/evm/EvmBaseAdapter.ts`:
- Around line 646-648: Replace the direct console.warn calls in
EvmBaseAdapter.ts fallback paths with the adapter's structured logger (e.g.,
this.logger.warn) and include contextual fields: chainId (this.chainId),
operation (e.g., "getAccount"), primaryError (the caught error variable), and
fallback="true"; specifically update the warning before calling
getAccountFallback(pubkey, viemClient) and the similar calls at the other
locations (around the code at the other fallback sites) so the log message reads
a concise descriptive string plus the structured context and the original error
metadata.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 88b7a4e7-20d6-45da-b65e-70af86f3c151
📒 Files selected for processing (36)
headers/csps/chains/arbitrum.tsheaders/csps/chains/avalanche.tsheaders/csps/chains/base.tsheaders/csps/chains/berachain.tsheaders/csps/chains/blast.tsheaders/csps/chains/bnbsmartchain.tsheaders/csps/chains/bob.tsheaders/csps/chains/celo.tsheaders/csps/chains/cronos.tsheaders/csps/chains/ethereum.tsheaders/csps/chains/flowEvm.tsheaders/csps/chains/gnosis.tsheaders/csps/chains/hemi.tsheaders/csps/chains/hyperevm.tsheaders/csps/chains/ink.tsheaders/csps/chains/katana.tsheaders/csps/chains/linea.tsheaders/csps/chains/mantle.tsheaders/csps/chains/megaeth.tsheaders/csps/chains/mode.tsheaders/csps/chains/monad.tsheaders/csps/chains/optimism.tsheaders/csps/chains/plasma.tsheaders/csps/chains/plume.tsheaders/csps/chains/polygon.tsheaders/csps/chains/scroll.tsheaders/csps/chains/soneium.tsheaders/csps/chains/sonic.tsheaders/csps/chains/story.tsheaders/csps/chains/unichain.tsheaders/csps/chains/worldchain.tsheaders/csps/chains/zksyncera.tspackages/chain-adapters/src/evm/EvmBaseAdapter.tspackages/contracts/src/fallbackRpcUrls.tspackages/contracts/src/index.tspackages/contracts/src/viemClient.ts
…peshift/web into feat/rpc-fallback-resilience
… leverage fallback urls
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
.env (1)
156-180: Static analysis: Environment variable ordering.The static analysis tool flags these variables as not being in alphabetical order. While this is a low-priority cosmetic issue and doesn't affect functionality, maintaining alphabetical order can improve maintainability for large
.envfiles.Given the "Chill" review mode, this is optional to address now or defer to a future cleanup PR.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.env around lines 156 - 180, The .env entries are not alphabetically ordered; to fix, reorder the VITE_* node URL variables into strict alphabetical order (for example ensure BOB, BERACHAIN, BLAST, CELO, ETHEREAL, FLOWEVM, HEMI, etc. appear in A–Z order) by sorting the keys like VITE_BERACHAIN_NODE_URL, VITE_BLAST_NODE_URL, VITE_BOB_NODE_URL, VITE_CELO_NODE_URL, VITE_ETHEREAL_NODE_URL, VITE_FLOWEVM_NODE_URL, VITE_HEMI_NODE_URL, VITE_HYPEREVM_NODE_URL, VITE_INK_NODE_URL, VITE_KATANA_NODE_URL, VITE_LINEA_NODE_URL, VITE_MEGAETH_NODE_URL, VITE_MODE_NODE_URL, VITE_MONAD_NODE_URL, VITE_MANTLE_NODE_URL, VITE_PLASMA_NODE_URL, VITE_PLUME_NODE_URL, VITE_SCROLL_NODE_URL, VITE_SONEIUM_NODE_URL, VITE_SONIC_NODE_URL, VITE_STORY_NODE_URL, VITE_UNICHAIN_NODE_URL, VITE_WORLDCHAIN_NODE_URL, VITE_ZKSYNC_ERA_NODE_URL so the file is consistently ordered for maintainability.packages/chain-adapters/src/evm/SecondClassEvmAdapter.ts (1)
122-122: Use a project error class instead of throwing a rawError.Prefer a custom error from
@shapeshiftoss/errorswith code/details so failures are consistently typed and localizable.
As per coding guidelines "ALWAYS use custom error classes from@shapeshiftoss/errorswith meaningful error codes for internationalization and relevant details in error objects".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chain-adapters/src/evm/SecondClassEvmAdapter.ts` at line 122, Replace the raw Error thrown when viemClient is missing with a custom error from `@shapeshiftoss/errors`: locate the check in SecondClassEvmAdapter (the line that reads if (!viemClient) throw new Error(...)) and throw an instance of the project error class (e.g., new Problem or a similar exported custom error) including a meaningful error code and a details object that contains the chainId (args.chainId) and context ("No viem client found for chainId"). Ensure you import the chosen error class from "@shapeshiftoss/errors" at the top of the file and use it consistently so failures are typed and localizable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/chain-adapters/src/evm/SecondClassEvmAdapter.ts`:
- Around line 313-325: The current parallel call to
viemClient.estimateFeesPerGas causes Promise.all to fail on chains that only
support legacy or only EIP-1559 and also calls .toString() on possibly undefined
fields; update the logic in SecondClassEvmAdapter to attempt EIP-1559 first (via
this.requestQueue.add(() => this.viemClient.estimateFeesPerGas({ type:
'eip1559', ... }))) and if it throws, catch and fallback to a legacy estimate,
and vice-versa if you prefer trying legacy first for compatibility; ensure you
only call .toString() after checking the field is defined (e.g., gasPrice,
maxFeePerGas, maxPriorityFeePerGas) and map missing fields to null/undefined or
appropriate fallback values when constructing the GasFeeData object so the
adapter tolerates legacy-only and EIP-1559-only chains while using
this.requestQueue and this.viemClient identifiers from the diff.
- Around line 440-441: Replace the unsafe "as any" on the debug_traceTransaction
call in SecondClassEvmAdapter by casting the request result to unknown,
introduce a CallTraceResponse type describing { value?, from?, to?, calls? } and
a type guard isCallTraceResponse(val: unknown): val is CallTraceResponse that
checks it's a non-null object (and optionally checks keys/types), then call
this.viemClient.request(...) inside requestQueue.add, cast its result to
unknown, and only pass the value into your existing extraction logic (e.g.,
extractCalls or whatever processes the trace) after narrowing with
isCallTraceResponse to safely access value/from/to/calls. Ensure you update
references to the debug_traceTransaction params typing (the object passed to
viemClient.request) to a concrete shape rather than using any.
---
Nitpick comments:
In @.env:
- Around line 156-180: The .env entries are not alphabetically ordered; to fix,
reorder the VITE_* node URL variables into strict alphabetical order (for
example ensure BOB, BERACHAIN, BLAST, CELO, ETHEREAL, FLOWEVM, HEMI, etc. appear
in A–Z order) by sorting the keys like VITE_BERACHAIN_NODE_URL,
VITE_BLAST_NODE_URL, VITE_BOB_NODE_URL, VITE_CELO_NODE_URL,
VITE_ETHEREAL_NODE_URL, VITE_FLOWEVM_NODE_URL, VITE_HEMI_NODE_URL,
VITE_HYPEREVM_NODE_URL, VITE_INK_NODE_URL, VITE_KATANA_NODE_URL,
VITE_LINEA_NODE_URL, VITE_MEGAETH_NODE_URL, VITE_MODE_NODE_URL,
VITE_MONAD_NODE_URL, VITE_MANTLE_NODE_URL, VITE_PLASMA_NODE_URL,
VITE_PLUME_NODE_URL, VITE_SCROLL_NODE_URL, VITE_SONEIUM_NODE_URL,
VITE_SONIC_NODE_URL, VITE_STORY_NODE_URL, VITE_UNICHAIN_NODE_URL,
VITE_WORLDCHAIN_NODE_URL, VITE_ZKSYNC_ERA_NODE_URL so the file is consistently
ordered for maintainability.
In `@packages/chain-adapters/src/evm/SecondClassEvmAdapter.ts`:
- Line 122: Replace the raw Error thrown when viemClient is missing with a
custom error from `@shapeshiftoss/errors`: locate the check in
SecondClassEvmAdapter (the line that reads if (!viemClient) throw new
Error(...)) and throw an instance of the project error class (e.g., new Problem
or a similar exported custom error) including a meaningful error code and a
details object that contains the chainId (args.chainId) and context ("No viem
client found for chainId"). Ensure you import the chosen error class from
"@shapeshiftoss/errors" at the top of the file and use it consistently so
failures are typed and localizable.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 08340400-257f-49af-ac20-959f83fa1209
📒 Files selected for processing (5)
.env.env.developmentpackages/chain-adapters/src/evm/SecondClassEvmAdapter.test.tspackages/chain-adapters/src/evm/SecondClassEvmAdapter.tspackages/contracts/src/constants.ts
💤 Files with no reviewable changes (1)
- packages/contracts/src/constants.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/chain-adapters/src/evm/EvmBaseAdapter.ts`:
- Around line 645-649: The getAccount path currently returns raw viem errors
when viemClient exists because getAccountFallback(pubkey, viemClient) is called
directly; wrap the fallback call in the adapter's ErrorHandler translation so
failures are normalized into the adapter's getAccount error shape. Specifically,
in the getAccount method replace the direct return of getAccountFallback with
invoking the same error-normalizing logic used elsewhere (e.g., call
ErrorHandler.translate or the adapter's existing error wrapper) around
getAccountFallback(pubkey, viemClient) so any thrown viem errors are caught and
rethrown/returned as the adapter's standardized getAccount error. Ensure you
reference getAccount, getAccountFallback, and ErrorHandler in the change.
- Around line 1032-1036: The fallback gas estimation calls
viemClient.estimateGas using value: BigInt(estimateGasBody.value) which throws
if estimateGasBody.value is undefined; update the call (and any similar usages
in EvmBaseAdapter.buildEstimateGasBody) to guard the conversion like in
SecondClassEvmAdapter (e.g., use estimateGasBody.value ?
BigInt(estimateGasBody.value) : undefined) so value is only converted when
present before passing to viemClient.estimateGas.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 9e253f6b-051a-403b-86f9-da6c6ebdd15a
📒 Files selected for processing (2)
packages/chain-adapters/src/evm/EvmBaseAdapter.tspackages/chain-adapters/src/evm/SecondClassEvmAdapter.ts
…peshift/web into feat/rpc-fallback-resilience
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
packages/chain-adapters/src/evm/EvmBaseAdapter.ts (2)
977-977: Limit helper visibility toprivateto avoid API surface creep.
getGasFeeDataFallbackappears internal-only; making itprivatekeeps the class contract tighter and reduces accidental external use.🔧 Suggested change
- async getGasFeeDataFallback(viemClient: PublicClient): Promise<GasFeeDataEstimate> { + private async getGasFeeDataFallback(viemClient: PublicClient): Promise<GasFeeDataEstimate> {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chain-adapters/src/evm/EvmBaseAdapter.ts` at line 977, The method getGasFeeDataFallback on EvmBaseAdapter is intended for internal use but is declared public; change its visibility to private to prevent API surface creep. Locate the getGasFeeDataFallback(viemClient: PublicClient): Promise<GasFeeDataEstimate> declaration in the EvmBaseAdapter class and update the modifier to private, ensuring any internal callers within the class still compile; if there are external references, refactor those call sites to use a new public wrapper or move callers inside the class.
654-655: Use structured logging for fallback warnings (include error metadata).These fallback logs are plain strings and drop error context. Prefer structured logger fields (e.g.,
chainId, operation, original error) so outage diagnostics are queryable.As per coding guidelines "ALWAYS log errors for debugging using structured logging with relevant context and error metadata".
Also applies to: 754-756, 1012-1014, 1035-1037
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chain-adapters/src/evm/EvmBaseAdapter.ts` around lines 654 - 655, Replace the plain console.warn fallback messages in EvmBaseAdapter (the one before calling getAccountFallback(pubkey, viemClient) and the other similar fallback sites) with structured logger calls that include the chainId, operation name (e.g., "getAccount"), the fallback action ("direct RPC"), and the original error object/stack as metadata; specifically, swap the console.warn usage for the adapter's logger (e.g., this.logger.warn or this.logger.error depending on severity) and pass a message plus an object like { chainId: this.chainId, operation: 'getAccount', fallback: 'getAccountFallback', error } so diagnostics are queryable, and apply the same pattern to the other fallback instances in this class.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/chain-adapters/src/evm/EvmBaseAdapter.ts`:
- Around line 1039-1044: The fallback estimateGas call in EvmBaseAdapter passes
estimateGasBody.data which can be an empty string; change the call to supply a
valid hex or omit data when empty by normalizing data before calling
viemClient.estimateGas (e.g., set const normalizedData = estimateGasBody.data &&
estimateGasBody.data !== '' ? estimateGasBody.data : '0x' or remove the data
field) and use normalizedData in the estimateGas invocation so
viemClient.estimateGas always receives a valid 0x-prefixed hex value.
---
Nitpick comments:
In `@packages/chain-adapters/src/evm/EvmBaseAdapter.ts`:
- Line 977: The method getGasFeeDataFallback on EvmBaseAdapter is intended for
internal use but is declared public; change its visibility to private to prevent
API surface creep. Locate the getGasFeeDataFallback(viemClient: PublicClient):
Promise<GasFeeDataEstimate> declaration in the EvmBaseAdapter class and update
the modifier to private, ensuring any internal callers within the class still
compile; if there are external references, refactor those call sites to use a
new public wrapper or move callers inside the class.
- Around line 654-655: Replace the plain console.warn fallback messages in
EvmBaseAdapter (the one before calling getAccountFallback(pubkey, viemClient)
and the other similar fallback sites) with structured logger calls that include
the chainId, operation name (e.g., "getAccount"), the fallback action ("direct
RPC"), and the original error object/stack as metadata; specifically, swap the
console.warn usage for the adapter's logger (e.g., this.logger.warn or
this.logger.error depending on severity) and pass a message plus an object like
{ chainId: this.chainId, operation: 'getAccount', fallback:
'getAccountFallback', error } so diagnostics are queryable, and apply the same
pattern to the other fallback instances in this class.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b9562440-e0a6-4aa1-80f2-a0ee9a0639cb
📒 Files selected for processing (1)
packages/chain-adapters/src/evm/EvmBaseAdapter.ts
Description
When unchained's MoralisService returns 503 errors, all first-class EVM chains break because their
VITE_*_NODE_URLenv vars point to unchained's/api/v1/jsonrpcproxy. This PR adds public fallback RPCs at both the viem transport layer and the chain adapter layer so the app degrades gracefully instead of failing completely.Three layers of change:
viemClient.ts— AddedFALLBACK_RPC_URLSconstants andcreateFallbackTransport()helper. All 32 EVM chain viem clients now have public fallback RPCs (LlamaRPC, official chain RPCs). Previously only ETH, BSC, and Base had any.EvmBaseAdapter.ts— Added fallback paths forgetAccount(),broadcastTransaction(), andgetGasFeeData()that route through the viem client (which now has public fallback transports) when unchained fails. Token balances and tx history gracefully degrade to empty during outage.CSP
connect-src— Createdheaders/csps/chains/fallbackRpcUrls.tsas a shared constants file. All 32 chain CSP configs now import and spread fallback RPC origins from this file.Issue (if applicable)
Risk
Medium — Adds fallback network paths but does not modify any happy-path behavior. When unchained is healthy, no change in behavior. Fallbacks only activate on unchained failure.
Testing
Engineering
.env:yarn devand connect a wallet with ETHUnchained getAccount failed for eip155:1, falling back to direct RPCOperations
This is infrastructure resilience — no user-facing UI changes. When unchained is healthy, behavior is identical. When unchained is down, users will see native balances (but not token balances) and can still send transactions, vs a completely broken experience today.
Screenshots (if applicable)
N/A — no UI changes
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Improvements