From 869d61bcc7ff7836fe2f0807ec4755e53513945a Mon Sep 17 00:00:00 2001 From: defiQUG Date: Tue, 7 Jul 2026 09:58:44 -0700 Subject: [PATCH] fix(token-aggregation): enforce wallet-safe Chain 138 RPC URLs Ensure MetaMask add-chain payloads never inherit LAN HTTP RPCs by routing Chain 138 wallet payloads through a public HTTPS-only resolver. Co-authored-by: Cursor --- .../src/config/chain138-rpc.ts | 29 +++++++++++++++++++ .../src/lib/wallet-add-ethereum-chain.ts | 4 +-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/services/token-aggregation/src/config/chain138-rpc.ts b/services/token-aggregation/src/config/chain138-rpc.ts index 6121864..78c129b 100644 --- a/services/token-aggregation/src/config/chain138-rpc.ts +++ b/services/token-aggregation/src/config/chain138-rpc.ts @@ -6,8 +6,25 @@ export const CHAIN138_PUBLIC_RPC_URLS = [ ] as const; const DEFAULT_CHAIN138_PUBLIC_RPC_URL = CHAIN138_PUBLIC_RPC_URLS[0]; +const DEFAULT_CHAIN138_WALLET_RPC_URL = CHAIN138_PUBLIC_RPC_URLS[1]; const DEFAULT_CHAIN138_SERVER_RPC_URL = 'http://192.168.11.235:8545'; +export function isWalletSafeRpcUrl(url: string | undefined): boolean { + if (!url) return false; + try { + const parsed = new URL(url); + if (parsed.protocol !== 'https:') return false; + const host = parsed.hostname.toLowerCase(); + if (host === 'localhost' || host === '127.0.0.1' || host.endsWith('.local')) return false; + if (/^10\./.test(host) || /^192\.168\./.test(host) || /^172\.(1[6-9]|2\d|3[0-1])\./.test(host)) { + return false; + } + return true; + } catch { + return false; + } +} + /** HTTPS FQDN for wallets, MetaMask, and browser JSON-RPC (same on LAN and external). */ export function resolveChain138PublicRpcUrl(): string { return String( @@ -19,6 +36,18 @@ export function resolveChain138PublicRpcUrl(): string { ).trim(); } +export function resolveChain138WalletRpcUrl(): string { + const candidates = [ + process.env.CHAIN138_WALLET_RPC_URL, + process.env.CHAIN138_RPC_HTTP_PUBLIC_FQDN, + process.env.RPC_URL_138_PUBLIC, + process.env.CHAIN138_RPC_URL_PUBLIC, + process.env.CHAIN_138_RPC_URL, + DEFAULT_CHAIN138_WALLET_RPC_URL, + ]; + return candidates.map((url) => String(url || '').trim()).find(isWalletSafeRpcUrl) || DEFAULT_CHAIN138_WALLET_RPC_URL; +} + export function resolveChain138PublicRpcUrls(): string[] { const fromEnv = process.env.CHAIN138_PUBLIC_RPC_URLS?.trim(); if (fromEnv) { diff --git a/services/token-aggregation/src/lib/wallet-add-ethereum-chain.ts b/services/token-aggregation/src/lib/wallet-add-ethereum-chain.ts index 3be56c1..0b79b19 100644 --- a/services/token-aggregation/src/lib/wallet-add-ethereum-chain.ts +++ b/services/token-aggregation/src/lib/wallet-add-ethereum-chain.ts @@ -1,5 +1,5 @@ import type { NetworkEntry } from '../config/networks'; -import { resolveChain138PublicRpcUrl } from '../config/chain138-rpc'; +import { resolveChain138WalletRpcUrl } from '../config/chain138-rpc'; import { filterMetamaskSafeIconUrls } from '../config/metamask-wallet-images'; /** EIP-3085 fields accepted by MetaMask `wallet_addEthereumChain` (strict subset). */ @@ -28,7 +28,7 @@ export function toWalletAddEthereumChainParams( const httpsList = httpsOnly(network.rpcUrls) ?? ['https://rpc-http-pub.d-bis.org']; // MetaMask rejects some multi-rpc addEthereumChain payloads; ship one canonical HTTPS URL. const primaryRpc = - network.chainId === '0x8a' ? resolveChain138PublicRpcUrl() : httpsList[0]; + network.chainId === '0x8a' ? resolveChain138WalletRpcUrl() : httpsList[0]; const rpcUrls = primaryRpc ? [primaryRpc] : httpsList.slice(0, 1); const out: WalletAddEthereumChainParams = { chainId: network.chainId,