Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,7 @@
# Destination token address (USDC) for cross-chain swaps
#NEXT_PUBLIC_GNOSIS_DESTINATION_TOKEN=0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83

# RPC used for GNOSIS calls
# NEXT_PUBLIC_GNOSIS_RPC=https://rpc.gnosischain.com
# Optional private RPC per chain (prepended to fallbacks; used first). Key = chain id.
# NEXT_PUBLIC_RPC_1=https://eth-mainnet.g.alchemy.com/v2/your-key
# NEXT_PUBLIC_RPC_100=https://rpc.gnosischain.com
# NEXT_PUBLIC_RPC_8453=https://mainnet.base.org
9 changes: 3 additions & 6 deletions src/app/components/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,22 +295,19 @@ export const handleExchangeRateUpdate = async (
* @returns A public client configured for the Gnosis chain
*/
export const getGnosisPublicClient = (rpcIndex: number = 0) => {
// Use global custom RPC URL if set, otherwise fall back to env variable
const envRpcUrl = process.env.NEXT_PUBLIC_GNOSIS_RPC;

// Same RPC list as wagmi (single source of truth; no duplicate list here)
const fallbackRpcs = getRpcUrlsForChain(gnosis.id)!;

let rpcUrl: string;
if (rpcIndex === 0) {
// Primary attempt: use custom/env RPC or first fallback
rpcUrl = globalCustomRpcUrl || envRpcUrl || fallbackRpcs[0];
// Primary attempt: use global custom RPC or first fallback
rpcUrl = globalCustomRpcUrl || fallbackRpcs[0];
} else {
// Fallback attempts: use specific fallback RPC
rpcUrl = fallbackRpcs[rpcIndex] ?? fallbackRpcs[fallbackRpcs.length - 1];
}

// We are using public RPC for the Gnosis chain unless a custom RPC is set or env variable is set
// We are using public RPC for the Gnosis chain unless a global custom RPC is set
const client = createPublicClient({
chain: gnosis,
transport: rpcUrl ? http(rpcUrl) : http(),
Expand Down
100 changes: 82 additions & 18 deletions src/app/wagmi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,16 @@ import {
// 3 RPCs per chain — each verified with eth_chainId; failed ones replaced
const RPC_FALLBACKS: Record<number, [string, string, string]> = {
[mainnet.id]: [
'https://eth-mainnet.g.alchemy.com/v2/IVUx4_9ohzDYHxS7mp4gG4FAyOJrggTV',
'https://eth.drpc.org',
'https://eth.llamarpc.com',
'https://ethereum.publicnode.com',
],
[gnosis.id]: [
'https://gnosis-mainnet.g.alchemy.com/v2/IVUx4_9ohzDYHxS7mp4gG4FAyOJrggTV',
'https://rpc.gnosischain.com',
'https://gnosis.drpc.org',
'https://gnosis-rpc.publicnode.com',
],
[base.id]: [
'https://base-mainnet.g.alchemy.com/v2/IVUx4_9ohzDYHxS7mp4gG4FAyOJrggTV',
'https://base.drpc.org',
'https://mainnet.base.org',
],
[base.id]: ['https://base.drpc.org', 'https://base.llamarpc.com', 'https://mainnet.base.org'],
[arbitrum.id]: [
'https://arb1.arbitrum.io/rpc',
'https://1rpc.io/arb',
Expand Down Expand Up @@ -148,6 +144,79 @@ const RPC_FALLBACKS: Record<number, [string, string, string]> = {
],
};

/** Optional private RPC per chain from .env.local (e.g. NEXT_PUBLIC_RPC_100). Prepended to fallbacks when set. */
function getPrivateRpcForChain(chainId: number): string | undefined {
switch (chainId) {
case mainnet.id:
return process.env.NEXT_PUBLIC_RPC_1;
case gnosis.id:
return process.env.NEXT_PUBLIC_RPC_100;
case base.id:
return process.env.NEXT_PUBLIC_RPC_8453;
case arbitrum.id:
return process.env.NEXT_PUBLIC_RPC_42161;
case optimism.id:
return process.env.NEXT_PUBLIC_RPC_10;
case avalanche.id:
return process.env.NEXT_PUBLIC_RPC_43114;
case bsc.id:
return process.env.NEXT_PUBLIC_RPC_56;
case celo.id:
return process.env.NEXT_PUBLIC_RPC_42220;
case polygon.id:
return process.env.NEXT_PUBLIC_RPC_137;
case mantle.id:
return process.env.NEXT_PUBLIC_RPC_5000;
case zksync.id:
return process.env.NEXT_PUBLIC_RPC_324;
case ink.id:
return process.env.NEXT_PUBLIC_RPC_57073;
case boba.id:
return process.env.NEXT_PUBLIC_RPC_288;
case cronos.id:
return process.env.NEXT_PUBLIC_RPC_25;
case gravity.id:
return process.env.NEXT_PUBLIC_RPC_1625;
case linea.id:
return process.env.NEXT_PUBLIC_RPC_59144;
case lisk.id:
return process.env.NEXT_PUBLIC_RPC_1135;
case metis.id:
return process.env.NEXT_PUBLIC_RPC_1088;
case mode.id:
return process.env.NEXT_PUBLIC_RPC_34443;
case polygonZkEvm.id:
return process.env.NEXT_PUBLIC_RPC_1101;
case scroll.id:
return process.env.NEXT_PUBLIC_RPC_534352;
case sei.id:
return process.env.NEXT_PUBLIC_RPC_1329;
case sonic.id:
return process.env.NEXT_PUBLIC_RPC_146;
case soneium.id:
return process.env.NEXT_PUBLIC_RPC_1868;
case taiko.id:
return process.env.NEXT_PUBLIC_RPC_167000;
case unichain.id:
return process.env.NEXT_PUBLIC_RPC_130;
case worldchain.id:
return process.env.NEXT_PUBLIC_RPC_480;
case sepolia.id:
return process.env.NEXT_PUBLIC_RPC_11155111;
default:
return undefined;
}
}

/** RPC URLs for a chain: optional private RPC from env prepended, then fallbacks. Same order as wagmi transport. */
function getEffectiveRpcUrls(chainId: number): readonly string[] | undefined {
const base = RPC_FALLBACKS[chainId];
if (!base) return undefined;
const privateUrl = getPrivateRpcForChain(chainId);
if (privateUrl?.trim()) return [privateUrl.trim(), ...base];
return base;
}

// No retries per URL so fallback kicks in immediately (e.g. 403 from whitelisted Alchemy on localhost).
const HTTP_TRANSPORT_OPTIONS = {
retryCount: 0,
Expand Down Expand Up @@ -191,10 +260,10 @@ const DEFAULT_POLLING_INTERVAL = 4_000;

/**
* Returns the RPC URL list for a chain (same order as wagmi transport fallbacks).
* Use this so Gnosis and other chain RPCs stay in sync with wagmi config.
* If a private RPC is set in env (e.g. NEXT_PUBLIC_RPC_100), it is first in the list.
*/
export function getRpcUrlsForChain(chainId: number): readonly [string, string, string] | undefined {
return RPC_FALLBACKS[chainId];
export function getRpcUrlsForChain(chainId: number): readonly string[] | undefined {
return getEffectiveRpcUrls(chainId);
}

/**
Expand All @@ -205,15 +274,10 @@ export function getPollingInterval(chainId: number): number {
return CHAIN_POLLING_INTERVALS[chainId] ?? DEFAULT_POLLING_INTERVAL;
}

// On localhost, try public RPCs first so whitelisted RPCs (e.g. Alchemy) don't 403.
const USE_PUBLIC_RPC_FIRST =
typeof window !== 'undefined' && window.location?.hostname === 'localhost';

function transportForChain(chainId: number) {
const urls = RPC_FALLBACKS[chainId];
if (!urls) return http(undefined, HTTP_TRANSPORT_OPTIONS);
const ordered = USE_PUBLIC_RPC_FIRST ? ([...urls].reverse() as [string, string, string]) : urls;
return fallback(ordered.map(url => http(url, HTTP_TRANSPORT_OPTIONS)));
const urls = getEffectiveRpcUrls(chainId);
if (!urls?.length) return http(undefined, HTTP_TRANSPORT_OPTIONS);
return fallback(urls.map(url => http(url, HTTP_TRANSPORT_OPTIONS)));
}

export const config = getDefaultConfig({
Expand Down
Loading