Multi-Chain RPC: How to Connect Your dApp to Multiple Blockchains
Most dApps start on one chain. Then the team gets requests for Arbitrum support. Then Base. Then Polygon. Before long, you are managing five RPC endpoints, three API keys, and two billing accounts, and your config files are a mess.
Multi-chain is the direction Web3 is moving. This guide covers how to structure your RPC setup for multiple chains, what to watch out for as you scale, and how to keep things manageable.
Why Multi-Chain Complexity Grows Fast
The promise of multi-chain is simple: deploy your contracts on multiple networks, reach more users, reduce gas costs for your users by letting them choose their chain.
The infrastructure reality is more involved:
- Each chain needs its own RPC endpoint
- Each endpoint needs its own authentication
- You need WebSocket connections on chains where you subscribe to events
- Testnets and mainnets are separate endpoints
- Each provider has different rate limits, different plan structures, different billing
A team building on 5 chains with a testnet for each suddenly has 10 endpoints to manage. If they use different providers for different chains, that multiplies the operational surface.
The Two Approaches to Multi-Chain RPC
Option 1: Single Provider, Multiple Networks
Use one RPC provider that supports all the chains you need. One API key, one billing account, consistent authentication format across all endpoints.
// Same provider, same auth pattern, different network slugs
const providers = {
ethereum: new ethers.JsonRpcProvider(
'https://eu.endpoints.matrixed.link/rpc/ethereum?auth=YOUR_KEY'
),
arbitrum: new ethers.JsonRpcProvider(
'https://eu.endpoints.matrixed.link/rpc/arbitrum?auth=YOUR_KEY'
),
base: new ethers.JsonRpcProvider(
'https://eu.endpoints.matrixed.link/rpc/base?auth=YOUR_KEY'
),
polygon: new ethers.JsonRpcProvider(
'https://eu.endpoints.matrixed.link/rpc/polygon?auth=YOUR_KEY'
),
optimism: new ethers.JsonRpcProvider(
'https://eu.endpoints.matrixed.link/rpc/optimism?auth=YOUR_KEY'
),
};
Advantages: One key to rotate, one bill, one support contact, consistent endpoint format.
Limitations: You are dependent on one provider’s uptime across all chains simultaneously. If the provider has an incident, all chains are affected.
Option 2: Multiple Providers, One Per Chain
Use different providers for different chains. Some teams do this to optimize for per-chain performance, or to use providers that specialize in specific ecosystems (Solana-native providers, Starknet-native providers).
Advantages: Reduces blast radius of any single provider outage.
Limitations: Multiple API keys, multiple bills, multiple SLAs to monitor, more complex config management.
For most teams: Start with a single provider. Add a secondary for failover if uptime is critical. Only split by provider if you have a specific reason tied to one chain.
Structuring Your Config for Multi-Chain
The worst pattern is hardcoded endpoint URLs scattered through your codebase. The first time you rotate an API key or switch providers, you are doing find-and-replace across dozens of files.
Better pattern: centralized config
// config/rpc.js
const BASE_URL = 'https://eu.endpoints.matrixed.link/rpc';
const AUTH_KEY = process.env.RPC_AUTH_KEY;
export const RPC_ENDPOINTS = {
// Mainnets
ethereum: `${BASE_URL}/ethereum?auth=${AUTH_KEY}`,
arbitrum: `${BASE_URL}/arbitrum?auth=${AUTH_KEY}`,
base: `${BASE_URL}/base?auth=${AUTH_KEY}`,
polygon: `${BASE_URL}/polygon?auth=${AUTH_KEY}`,
optimism: `${BASE_URL}/optimism?auth=${AUTH_KEY}`,
avax: `${BASE_URL}/avax?auth=${AUTH_KEY}`,
bsc: `${BASE_URL}/bsc?auth=${AUTH_KEY}`,
zksync: `${BASE_URL}/zksync?auth=${AUTH_KEY}`,
linea: `${BASE_URL}/linea?auth=${AUTH_KEY}`,
// Testnets
hoodi: `${BASE_URL}/ethereum-hoodi?auth=${AUTH_KEY}`,
polygonAmoy: `${BASE_URL}/polygon-amoy?auth=${AUTH_KEY}`,
};
export const WSS_ENDPOINTS = {
ethereum: `wss://eu.endpoints.matrixed.link/ws/ethereum?auth=${AUTH_KEY}`,
arbitrum: `wss://eu.endpoints.matrixed.link/ws/arbitrum?auth=${AUTH_KEY}`,
base: `wss://eu.endpoints.matrixed.link/ws/base?auth=${AUTH_KEY}`,
// Add others as needed
};
Now changing a key or switching providers is a one-line change.
WebSocket Connections in Multi-Chain Apps
WebSocket connections are stateful and persistent. Managing them across multiple chains adds overhead.
Key rules:
-
Don’t open a WebSocket for every chain by default. Only open connections for chains where you are actively subscribing to events. A WebSocket sitting idle still consumes server resources and counts against connection limits.
-
Open on demand, close when done. If a user switches to a different chain in your UI, open the WebSocket for that chain when needed and close it when the user leaves.
-
Handle reconnection. WebSocket connections drop. Your application should reconnect automatically:
function createReconnectingProvider(wssUrl) {
let provider = new ethers.WebSocketProvider(wssUrl);
provider.websocket.on('close', () => {
console.log('WebSocket closed — reconnecting...');
setTimeout(() => {
provider = createReconnectingProvider(wssUrl);
}, 2000);
});
return provider;
}
const ethWss = createReconnectingProvider(WSS_ENDPOINTS.ethereum);
- Use HTTP for one-off queries. Only use WebSocket for subscriptions (new blocks, pending transactions, contract events). For everything else (balance checks, contract reads, transaction submission), use HTTP. It is simpler and scales better.
Handling Chain ID Correctly
Every EVM-compatible chain has a unique chain ID. Submitting a transaction signed for the wrong chain ID causes it to fail or replay.
// Always verify you are on the right chain before signing
async function sendTransactionSafely(provider, transaction, expectedChainId) {
const network = await provider.getNetwork();
if (network.chainId !== BigInt(expectedChainId)) {
throw new Error(
`Chain ID mismatch. Expected ${expectedChainId}, got ${network.chainId}`
);
}
// Proceed with transaction
}
Common EVM chain IDs for reference:
| Chain | Chain ID |
|---|---|
| Ethereum | 1 |
| Arbitrum One | 42161 |
| Base | 8453 |
| Polygon | 137 |
| Optimism | 10 |
| Avalanche C-Chain | 43114 |
| BNB Chain | 56 |
| zkSync Era | 324 |
| Linea | 59144 |
Managing Testnets vs Mainnets
A common pattern: use environment variables to switch between testnet and mainnet endpoints. This prevents accidentally running test transactions against mainnet.
// config/rpc.js
const IS_PRODUCTION = process.env.NODE_ENV === 'production';
export const ETHEREUM_RPC = IS_PRODUCTION
? `https://eu.endpoints.matrixed.link/rpc/ethereum?auth=${AUTH_KEY}`
: `https://eu.endpoints.matrixed.link/rpc/ethereum-hoodi?auth=${AUTH_KEY}`;
export const POLYGON_RPC = IS_PRODUCTION
? `https://eu.endpoints.matrixed.link/rpc/polygon?auth=${AUTH_KEY}`
: `https://eu.endpoints.matrixed.link/rpc/polygon-amoy?auth=${AUTH_KEY}`;
Adding a Fallback Provider
For production applications where uptime is critical, configure a fallback provider. ethers.js v6 has FallbackProvider built in:
import { ethers } from 'ethers';
// Primary + fallback for Ethereum
const primary = new ethers.JsonRpcProvider(
'https://eu.endpoints.matrixed.link/rpc/ethereum?auth=YOUR_KEY'
);
const fallback = new ethers.JsonRpcProvider(
'https://your-fallback-provider.com/ethereum'
);
// FallbackProvider tries primary first, fails over to secondary
const provider = new ethers.FallbackProvider([
{ provider: primary, priority: 1, weight: 2 },
{ provider: fallback, priority: 2, weight: 1 },
]);
This keeps your application online even during provider maintenance windows.
Non-EVM Chains in a Multi-Chain Setup
Not all chains use JSON-RPC over HTTP/WebSocket. If you are adding non-EVM support:
Starknet: Uses its own RPC spec (starknet_getBalance, starknet_call, etc.) but the transport layer is the same (HTTP/WebSocket). Most starknet.js operations work with a standard endpoint URL.
Solana: Uses a different JSON-RPC spec entirely. Methods like getBalance, getTransaction, sendTransaction are Solana-specific. Solana JSON-RPC is not compatible with ethers.js. Use @solana/web3.js or similar.
// Solana connection (separate library)
import { Connection } from '@solana/web3.js';
const solanaConnection = new Connection(
'https://eu.endpoints.matrixed.link/rpc/solana?auth=YOUR_KEY'
);
Keep EVM and non-EVM providers in separate config sections to avoid confusion.
BoltRPC Supports 20+ Networks From One Account
Rather than managing multiple provider accounts, BoltRPC gives you a single API key that works across all supported networks:
EVM mainnets: Ethereum, Arbitrum, Base, Polygon, Optimism, Avalanche, BNB Chain, zkSync Era, Linea, Scroll, Sonic, Moonbeam, Moonriver, X Layer, ApeChain
Non-EVM: Starknet, Solana (beta)
Testnets: Ethereum Hoodi, Polygon Amoy, Ethereum Beacon Chain, Enjin Matrix Chain
All networks support HTTP. All networks except Hyperliquid support WebSocket. One key, one billing account, consistent endpoint format across all chains.
Trusted by Chainlink, Tiingo, Gains Network, Enjin. Processing 2 billion daily requests.
Start your free 2-week trial: trial.boltrpc.io
Explore all supported networks: boltrpc.io/networks
FAQ
Do I need a separate API key for each chain?
With BoltRPC, no. One API key works across all 22 supported networks. The network is determined by the URL slug, not the key.
Should I use the same provider for mainnet and testnet?
Yes, where possible. Consistent endpoint format and a single API key simplify your config. Switching between mainnet and testnet becomes a URL change, not an account change.
How do I handle rate limits when calling multiple chains simultaneously?
If your application makes concurrent calls across multiple chains, your total request rate is the sum of all chains. Check that your plan’s request allocation covers peak concurrent usage across all chains you support.
What is the easiest way to add a new chain to an existing dApp?
If you are already using a multi-chain provider, adding a new chain is typically: (1) confirm the provider supports it, (2) add the new endpoint URL to your config, (3) update your chain ID validation, (4) test on testnet first. With a unified provider and centralized config, this takes minutes.
Is WebSocket available on all chains?
With BoltRPC, HTTP is available on all networks. WebSocket is available on all networks except Hyperliquid (which uses an HTTP-only API). Check your provider’s documentation for per-chain WebSocket availability.