Solana

Solana Beta is live. Try BoltRPC Solana endpoints free - start your trial now.

Multi-Chain RPC: How to Connect Your dApp to Multiple Blockchains

Building on more than one chain? Here's how to manage multi-chain RPC endpoints, avoid common pitfalls, and keep your infrastructure simple as you scale.

BoltRPC
BoltRPC Team
8 min read
Multi-Chain RPC: How to Connect Your dApp to Multiple Blockchains

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:

  1. 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.

  2. 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.

  3. 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);
  1. 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:

ChainChain ID
Ethereum1
Arbitrum One42161
Base8453
Polygon137
Optimism10
Avalanche C-Chain43114
BNB Chain56
zkSync Era324
Linea59144

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.

Frequently asked questions

Ready to build with high-performance RPC?

Start your free trial today. No credit card required. Access 20+ networks instantly.

Disclaimer: The content in this article is for informational purposes only and does not constitute financial, legal, or technical advice. Code examples and configurations are provided as-is. Always verify information with official documentation and test thoroughly in your own environment before deploying to production.

Continue reading