diff --git a/src/config/networks.json b/src/config/networks.json index eb08c278..98c90881 100644 --- a/src/config/networks.json +++ b/src/config/networks.json @@ -285,6 +285,126 @@ } ] }, + { + "type": "evm", + "networkId": "eip155:421614", + "slug": "arb-sepolia", + "name": "Arbitrum Sepolia", + "shortName": "Arb Sepolia", + "description": "Arbitrum testnet for developers", + "currency": "ETH", + "color": "#28A0F0", + "isTestnet": true, + "logo": "assets/networks/421614.svg", + "links": [ + { + "name": "Bridge", + "url": "https://bridge.arbitrum.io", + "description": "Bridge from Sepolia" + }, + { + "name": "Docs", + "url": "https://docs.arbitrum.io", + "description": "Developer documentation" + } + ] + }, + { + "type": "evm", + "networkId": "eip155:11155420", + "slug": "op-sepolia", + "name": "Optimism Sepolia", + "shortName": "OP Sepolia", + "description": "Optimism testnet for developers", + "currency": "ETH", + "color": "#FF0420", + "isTestnet": true, + "logo": "assets/networks/11155420.svg", + "links": [ + { + "name": "Bridge", + "url": "https://app.optimism.io/bridge", + "description": "Bridge from Sepolia" + }, + { + "name": "Docs", + "url": "https://docs.optimism.io", + "description": "Developer documentation" + } + ] + }, + { + "type": "evm", + "networkId": "eip155:84532", + "slug": "base-sepolia", + "name": "Base Sepolia", + "shortName": "Base Sepolia", + "description": "Base testnet for developers", + "currency": "ETH", + "color": "#0052FF", + "isTestnet": true, + "logo": "assets/networks/84532.svg", + "links": [ + { + "name": "Faucet", + "url": "https://www.coinbase.com/faucets/base-ethereum-sepolia-faucet", + "description": "Get testnet ETH" + }, + { + "name": "Docs", + "url": "https://docs.base.org", + "description": "Developer documentation" + } + ] + }, + { + "type": "evm", + "networkId": "eip155:80002", + "slug": "polygon-amoy", + "name": "Polygon Amoy", + "shortName": "Amoy", + "description": "Polygon testnet for developers", + "currency": "POL", + "color": "#8247E5", + "isTestnet": true, + "logo": "assets/networks/80002.svg", + "links": [ + { + "name": "Faucet", + "url": "https://faucet.polygon.technology", + "description": "Get testnet POL" + }, + { + "name": "Docs", + "url": "https://docs.polygon.technology", + "description": "Developer documentation" + } + ] + }, + { + "type": "evm", + "networkId": "eip155:43113", + "slug": "avax-fuji", + "name": "Avalanche Fuji", + "shortName": "Fuji", + "description": "Avalanche testnet for developers", + "currency": "AVAX", + "color": "#E84142", + "isTestnet": true, + "logo": "assets/networks/43113.svg", + "links": [ + { + "name": "Faucet", + "url": "https://faucet.avax.network", + "description": "Get testnet AVAX" + }, + { + "name": "Docs", + "url": "https://docs.avax.network", + "description": "Developer documentation" + } + ] + }, { "type": "solana", "networkId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", diff --git a/src/services/DataService.ts b/src/services/DataService.ts index 29c26b5a..6c8d5dab 100644 --- a/src/services/DataService.ts +++ b/src/services/DataService.ts @@ -1,8 +1,13 @@ import { type SupportedChainId, type SupportedSolanaChainId, - ClientFactory, + ArbitrumClient, + BaseClient, BitcoinClient, + ClientFactory, + EthereumClient, + OptimismClient, + PolygonClient, } from "@openscan/network-connectors"; import { AdapterFactory } from "./adapters/adaptersFactory"; @@ -13,6 +18,28 @@ import type { NetworkConfig, RpcUrlsContextType } from "../types"; import { getRPCUrls } from "../config/rpcConfig"; import { getNetworkRpcKey, getChainIdFromNetwork } from "../utils/networkResolver"; +type EVMClientConfig = { + rpcUrls: string[]; + type: "fallback" | "parallel" | "race"; +}; + +type EVMTestnetClient = + | ArbitrumClient + | OptimismClient + | BaseClient + | PolygonClient + | EthereumClient; + +// EVM testnets not yet registered in @openscan/network-connectors ClientFactory. +// Mapped to their L1 family's client since they share the same JSON-RPC surface. +const EVM_TESTNET_CLIENTS: Record EVMTestnetClient> = { + 421614: (config) => new ArbitrumClient(config), + 11155420: (config) => new OptimismClient(config), + 84532: (config) => new BaseClient(config), + 80002: (config) => new PolygonClient(config), + 43113: (config) => new EthereumClient(config), +}; + /** * DataService supports EVM, Bitcoin, and Solana networks * The adapter type varies based on network type @@ -58,10 +85,10 @@ export class DataService { } else { // Create EVM client and adapter const chainId = getChainIdFromNetwork(network) as SupportedChainId; - const networkClient = ClientFactory.createTypedClient(chainId, { - rpcUrls, - type: strategy, - }); + const clientConfig = { rpcUrls, type: strategy }; + const networkClient = + EVM_TESTNET_CLIENTS[chainId as number]?.(clientConfig) ?? + ClientFactory.createTypedClient(chainId, clientConfig); this.networkAdapter = AdapterFactory.createAdapter(chainId, networkClient); } } diff --git a/src/services/MetadataService.ts b/src/services/MetadataService.ts index ecd6e8d0..85660515 100644 --- a/src/services/MetadataService.ts +++ b/src/services/MetadataService.ts @@ -7,7 +7,7 @@ import networksData from "../config/networks.json"; import { logger } from "../utils/logger"; import { extractChainIdFromNetworkId } from "../utils/networkResolver"; -export const METADATA_VERSION = "1.2.0-alpha.0"; +export const METADATA_VERSION = "1.2.1-alpha.0"; const METADATA_BASE_URL = `https://cdn.jsdelivr.net/npm/@openscan/metadata@${METADATA_VERSION}/dist`; export interface NetworkLink { diff --git a/src/services/adapters/ArbitrumAdapter/ArbitrumAdapter.ts b/src/services/adapters/ArbitrumAdapter/ArbitrumAdapter.ts index 78b65827..b037da64 100644 --- a/src/services/adapters/ArbitrumAdapter/ArbitrumAdapter.ts +++ b/src/services/adapters/ArbitrumAdapter/ArbitrumAdapter.ts @@ -23,7 +23,7 @@ import type { ArbitrumClient, EthereumClient } from "@openscan/network-connector export class ArbitrumAdapter extends NetworkAdapter { private client: ArbitrumClient; - constructor(networkId: 42161, client: ArbitrumClient) { + constructor(networkId: 42161 | 421614, client: ArbitrumClient) { super(networkId); this.client = client; this.initTxSearch(client as unknown as EthereumClient); diff --git a/src/services/adapters/BaseAdapter/BaseAdapter.ts b/src/services/adapters/BaseAdapter/BaseAdapter.ts index dd23c7f0..60939d41 100644 --- a/src/services/adapters/BaseAdapter/BaseAdapter.ts +++ b/src/services/adapters/BaseAdapter/BaseAdapter.ts @@ -22,7 +22,7 @@ import type { BaseClient, EthereumClient } from "@openscan/network-connectors"; export class BaseAdapter extends NetworkAdapter { private client: BaseClient; - constructor(networkId: 8453, client: BaseClient) { + constructor(networkId: 8453 | 84532, client: BaseClient) { super(networkId); this.client = client; this.initTxSearch(client as unknown as EthereumClient); diff --git a/src/services/adapters/OptimismAdapter/OptimismAdapter.ts b/src/services/adapters/OptimismAdapter/OptimismAdapter.ts index cb1935e4..fd629682 100644 --- a/src/services/adapters/OptimismAdapter/OptimismAdapter.ts +++ b/src/services/adapters/OptimismAdapter/OptimismAdapter.ts @@ -22,7 +22,7 @@ import type { OptimismClient, EthereumClient } from "@openscan/network-connector export class OptimismAdapter extends NetworkAdapter { private client: OptimismClient; - constructor(networkId: 10, client: OptimismClient) { + constructor(networkId: 10 | 11155420, client: OptimismClient) { super(networkId); this.client = client; this.initTxSearch(client as unknown as EthereumClient); diff --git a/src/services/adapters/PolygonAdapter/PolygonAdapter.ts b/src/services/adapters/PolygonAdapter/PolygonAdapter.ts index eaf106eb..942e305a 100644 --- a/src/services/adapters/PolygonAdapter/PolygonAdapter.ts +++ b/src/services/adapters/PolygonAdapter/PolygonAdapter.ts @@ -12,7 +12,7 @@ import { import { normalizeBlockNumber } from "../shared/normalizeBlockNumber"; import { mergeMetadata } from "../shared/mergeMetadata"; -import type { PolygonClient, SupportedChainId, EthereumClient } from "@openscan/network-connectors"; +import type { PolygonClient, EthereumClient } from "@openscan/network-connectors"; /** * Polygon blockchain service @@ -21,7 +21,7 @@ import type { PolygonClient, SupportedChainId, EthereumClient } from "@openscan/ export class PolygonAdapter extends NetworkAdapter { private client: PolygonClient; - constructor(networkId: SupportedChainId, client: PolygonClient) { + constructor(networkId: 137 | 80002, client: PolygonClient) { super(networkId); this.client = client; this.initTxSearch(client as unknown as EthereumClient); diff --git a/src/services/adapters/adaptersFactory.ts b/src/services/adapters/adaptersFactory.ts index d59f5737..faa3cbcc 100644 --- a/src/services/adapters/adaptersFactory.ts +++ b/src/services/adapters/adaptersFactory.ts @@ -29,7 +29,7 @@ export class AdapterFactory { * Create an EVM network adapter */ static createAdapter( - networkId: SupportedChainId, + networkId: SupportedChainId | number, client: | EthereumClient | OptimismClient @@ -45,19 +45,24 @@ export class AdapterFactory { case 1: case 11155111: case 43114: + case 43113: return new EVMAdapter(networkId, client as unknown as EthereumClient); case 31337: return new HardhatAdapter(client as HardhatClient); case 10: + case 11155420: return new OptimismAdapter(networkId, client as OptimismClient); case 56: case 97: return new BNBAdapter(networkId, client as BNBClient); case 137: + case 80002: return new PolygonAdapter(networkId, client as PolygonClient); case 8453: + case 84532: return new BaseAdapter(networkId, client as BaseClient); case 42161: + case 421614: return new ArbitrumAdapter(networkId, client as ArbitrumClient); default: throw new Error(`Unknown adapter for networkId: ${networkId}`); diff --git a/src/types/index.ts b/src/types/index.ts index ee904ab4..ff27f57b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -10,10 +10,10 @@ export type NetworkType = "evm" | "bitcoin" | "solana"; /** * All EVM chain IDs supported by the app. - * Maps directly to the connector library's SupportedChainId. - * When adding a new EVM network, add its chain ID to network-connectors first. + * Extends the connector library's SupportedChainId with testnet chain IDs + * that reuse their L1 family's client (not yet registered in network-connectors). */ -export type AppChainId = SupportedChainId; +export type AppChainId = SupportedChainId | 43113 | 421614 | 11155420 | 84532 | 80002; // ==================== CORE DOMAIN TYPES ====================