diff --git a/indexer-compose.yml b/indexer-compose.yml index a2a33f3b..61d81c33 100644 --- a/indexer-compose.yml +++ b/indexer-compose.yml @@ -23,24 +23,24 @@ services: IPFS_GATEWAYS: ${IPFS_GATEWAYS} COINGECKO_API_KEY: ${COINGECKO_API_KEY} GRAPHILE_LICENSE: ${GRAPHILE_LICENSE} - SEPOLIA_RPC_URL: ${SEPOLIA_RPC_URL} - POLYGON_MUMBAI_RPC_URL: ${POLYGON_MUMBAI_RPC_URL} - AVALANCHE_RPC_URL: ${AVALANCHE_RPC_URL} - OPTIMISM_RPC_URL: ${OPTIMISM_RPC_URL} + SEPOLIA_RPC_URLS: ${SEPOLIA_RPC_URLS} + POLYGON_MUMBAI_RPC_URLS: ${POLYGON_MUMBAI_RPC_URLS} + AVALANCHE_RPC_URLS: ${AVALANCHE_RPC_URLS} + OPTIMISM_RPC_URLS: ${OPTIMISM_RPC_URLS} SENTRY_DSN: ${SENTRY_DSN} PGN_TESTNET_RPC_URL: ${PGN_TESTNET_RPC_URL} - ARBITRUM_GOERLI_RPC_URL: ${ARBITRUM_GOERLI_RPC_URL} - FANTOM_RPC_URL: ${FANTOM_RPC_URL} - BASE_RPC_URL: ${BASE_RPC_URL} - PGN_RPC_URL: ${PGN_RPC_URL} - GOERLI_RPC_URL: ${GOERLI_RPC_URL} - AVALANCHE_FUJI_RPC_URL: ${AVALANCHE_FUJI_RPC_URL} - ARBITRUM_RPC_URL: ${ARBITRUM_RPC_URL} - SEI_MAINNET_RPC_URL: ${SEI_MAINNET_RPC_URL} - MAINNET_RPC_URL: ${MAINNET_RPC_URL} - POLYGON_RPC_URL: ${POLYGON_RPC_URL} - METIS_ANDROMEDA_RPC_URL: ${METIS_ANDROMEDA_RPC_URL} - SCROLL_SEPOLIA_RPC_URL: ${SCROLL_SEPOLIA_RPC_URL} + ARBITRUM_GOERLI_RPC_URLS: ${ARBITRUM_GOERLI_RPC_URLS} + FANTOM_RPC_URLS: ${FANTOM_RPC_URLS} + BASE_RPC_URLS: ${BASE_RPC_URLS} + PGN_RPC_URLS: ${PGN_RPC_URLS} + GOERLI_RPC_URLS: ${GOERLI_RPC_URLS} + AVALANCHE_FUJI_RPC_URLS: ${AVALANCHE_FUJI_RPC_URLS} + ARBITRUM_RPC_URLS: ${ARBITRUM_RPC_URLS} + SEI_MAINNET_RPC_URLS: ${SEI_MAINNET_RPC_URLS} + MAINNET_RPC_URLS: ${MAINNET_RPC_URLS} + POLYGON_RPC_URLS: ${POLYGON_RPC_URLS} + METIS_ANDROMEDA_RPC_URLS: ${METIS_ANDROMEDA_RPC_URLS} + SCROLL_SEPOLIA_RPC_URLS: ${SCROLL_SEPOLIA_RPC_URLS} DATABASE_URL: "postgresql://postgres:postgres@db:5432/grants_stack_indexer" index: diff --git a/src/config.ts b/src/config.ts index e78f8e0f..80712735 100644 --- a/src/config.ts +++ b/src/config.ts @@ -40,7 +40,7 @@ export type Subscription = { }; export type Chain = { - rpc: string; + rpcs: string[]; name: string; id: ChainId; pricesFromTimestamp: number; @@ -49,15 +49,15 @@ export type Chain = { maxGetLogsRange?: number; }; -const rpcUrl = z.string().url(); +const rpcUrl = z.array(z.string().url()); const CHAINS: Chain[] = [ { id: 1, name: "mainnet", - rpc: rpcUrl - .default("https://mainnet.infura.io/v3/") - .parse(process.env.MAINNET_RPC_URL), + rpcs: rpcUrl + .default(["https://mainnet.infura.io/v3/"]) + .parse(process.env.MAINNET_RPC_URLS), pricesFromTimestamp: Date.UTC(2022, 11, 1, 0, 0, 0), tokens: [ { @@ -152,9 +152,9 @@ const CHAINS: Chain[] = [ { id: 10, name: "optimism", - rpc: rpcUrl - .default("https://optimism-rpc.publicnode.com") - .parse(process.env.OPTIMISM_RPC_URL), + rpcs: rpcUrl + .default(["https://optimism-rpc.publicnode.com"]) + .parse(process.env.OPTIMISM_RPC_URLS), pricesFromTimestamp: Date.UTC(2022, 11, 1, 0, 0, 0), tokens: [ { @@ -268,9 +268,9 @@ const CHAINS: Chain[] = [ { id: 11155111, name: "sepolia", - rpc: rpcUrl - .default("https://ethereum-sepolia.publicnode.com") - .parse(process.env.SEPOLIA_RPC_URL), + rpcs: rpcUrl + .default(["https://ethereum-sepolia.publicnode.com"]) + .parse(process.env.SEPOLIA_RPC_URLS), pricesFromTimestamp: Date.UTC(2023, 11, 1, 0, 0, 0), tokens: [ { @@ -372,9 +372,9 @@ const CHAINS: Chain[] = [ { id: 250, name: "fantom", - rpc: rpcUrl - .default("https://rpcapi.fantom.network") - .parse(process.env.FANTOM_RPC_URL), + rpcs: rpcUrl + .default(["https://rpcapi.fantom.network"]) + .parse(process.env.FANTOM_RPC_URLS), pricesFromTimestamp: Date.UTC(2022, 11, 1, 0, 0, 0), tokens: [ { @@ -471,9 +471,9 @@ const CHAINS: Chain[] = [ { id: 58008, name: "pgn-testnet", - rpc: rpcUrl - .default("https://sepolia.publicgoods.network") - .parse(process.env.PGN_TESTNET_RPC_URL), + rpcs: rpcUrl + .default(["https://sepolia.publicgoods.network"]) + .parse(process.env.PGN_TESTNET_RPC_URLS), pricesFromTimestamp: Date.UTC(2023, 5, 2, 0, 0, 0), tokens: [ { @@ -524,9 +524,9 @@ const CHAINS: Chain[] = [ { id: 424, name: "pgn-mainnet", - rpc: rpcUrl - .default("https://rpc.publicgoods.network") - .parse(process.env.PGN_RPC_URL), + rpcs: rpcUrl + .default(["https://rpc.publicgoods.network"]) + .parse(process.env.PGN_RPC_URLS), pricesFromTimestamp: Date.UTC(2023, 5, 2, 0, 0, 0), tokens: [ { @@ -602,9 +602,9 @@ const CHAINS: Chain[] = [ { id: 42161, name: "arbitrum", - rpc: rpcUrl - .default("https://arb-mainnet.g.alchemy.com/v2/") - .parse(process.env.ARBITRUM_RPC_URL), + rpcs: rpcUrl + .default(["https://arb-mainnet.g.alchemy.com/v2/"]) + .parse(process.env.ARBITRUM_RPC_URLS), pricesFromTimestamp: Date.UTC(2023, 7, 1, 0, 0, 0), tokens: [ { @@ -715,9 +715,9 @@ const CHAINS: Chain[] = [ { id: 80001, name: "polygon-mumbai", - rpc: rpcUrl - .default("https://rpc-mumbai.maticvigil.com/") - .parse(process.env.POLYGON_MUMBAI_RPC_URL), + rpcs: rpcUrl + .default(["https://rpc-mumbai.maticvigil.com/"]) + .parse(process.env.POLYGON_MUMBAI_RPC_URLS), pricesFromTimestamp: Date.UTC(2023, 8, 19, 0, 0, 0), tokens: [ { @@ -795,9 +795,9 @@ const CHAINS: Chain[] = [ { id: 137, name: "polygon", - rpc: rpcUrl - .default("https://polygon-rpc.com") - .parse(process.env.POLYGON_RPC_URL), + rpcs: rpcUrl + .default(["https://polygon-rpc.com"]) + .parse(process.env.POLYGON_RPC_URLS), pricesFromTimestamp: Date.UTC(2023, 8, 19, 0, 0, 0), tokens: [ { @@ -893,9 +893,9 @@ const CHAINS: Chain[] = [ { id: 8453, name: "base", - rpc: rpcUrl - .default("https://mainnet.base.org/") - .parse(process.env.BASE_RPC_URL), + rpcs: rpcUrl + .default(["https://mainnet.base.org/"]) + .parse(process.env.BASE_RPC_URLS), pricesFromTimestamp: Date.UTC(2023, 12, 1, 0, 0, 0), tokens: [ { @@ -973,9 +973,9 @@ const CHAINS: Chain[] = [ { id: 324, name: "zksync-era-mainnet", - rpc: rpcUrl - .default("https://mainnet.era.zksync.io") - .parse(process.env.ZKSYNC_RPC_URL), + rpcs: rpcUrl + .default(["https://mainnet.era.zksync.io"]) + .parse(process.env.ZKSYNC_RPC_URLS), pricesFromTimestamp: Date.UTC(2023, 12, 1, 0, 0, 0), tokens: [ { @@ -1088,9 +1088,9 @@ const CHAINS: Chain[] = [ { id: 300, name: "zksync-era-testnet", - rpc: rpcUrl - .default("https://sepolia.era.zksync.dev") - .parse(process.env.ZKSYNC_TESTNET_RPC_URL), + rpcs: rpcUrl + .default(["https://sepolia.era.zksync.dev"]) + .parse(process.env.ZKSYNC_TESTNET_RPC_URLS), pricesFromTimestamp: Date.UTC(2023, 12, 1, 0, 0, 0), tokens: [ { @@ -1128,9 +1128,9 @@ const CHAINS: Chain[] = [ { id: 43114, name: "avalanche", - rpc: rpcUrl - .default("https://rpc.ankr.com/avalanche") - .parse(process.env.AVALANCHE_RPC_URL), + rpcs: rpcUrl + .default(["https://rpc.ankr.com/avalanche"]) + .parse(process.env.AVALANCHE_RPC_URLS), pricesFromTimestamp: Date.UTC(2023, 8, 19, 0, 0, 0), tokens: [ { @@ -1208,9 +1208,9 @@ const CHAINS: Chain[] = [ { id: 43113, name: "avalanche-fuji", - rpc: rpcUrl - .default("https://avalanche-fuji-c-chain.publicnode.com") - .parse(process.env.AVALANCHE_FUJI_RPC_URL), + rpcs: rpcUrl + .default(["https://avalanche-fuji-c-chain.publicnode.com"]) + .parse(process.env.AVALANCHE_FUJI_RPC_URLS), pricesFromTimestamp: Date.UTC(2023, 8, 19, 0, 0, 0), tokens: [ { @@ -1278,9 +1278,9 @@ const CHAINS: Chain[] = [ { id: 534351, name: "scroll-sepolia", - rpc: rpcUrl - .default("https://sepolia-rpc.scroll.io") - .parse(process.env.SCROLL_SEPOLIA_RPC_URL), + rpcs: rpcUrl + .default(["https://sepolia-rpc.scroll.io"]) + .parse(process.env.SCROLL_SEPOLIA_RPC_URLS), pricesFromTimestamp: Date.UTC(2024, 0, 1, 0, 0, 0), maxGetLogsRange: 2000, tokens: [ @@ -1349,9 +1349,9 @@ const CHAINS: Chain[] = [ { id: 534352, name: "scroll", - rpc: rpcUrl - .default("https://rpc.scroll.io") - .parse(process.env.SCROLL_RPC_URL), + rpcs: rpcUrl + .default(["https://rpc.scroll.io"]) + .parse(process.env.SCROLL_RPC_URLS), pricesFromTimestamp: Date.UTC(2024, 0, 1, 0, 0, 0), maxGetLogsRange: 9000, tokens: [ @@ -1435,9 +1435,9 @@ const CHAINS: Chain[] = [ { id: 713715, name: "sei-devnet", - rpc: rpcUrl - .default("https://evm-rpc-arctic-1.sei-apis.com") - .parse(process.env.SEI_DEVNET_RPC_URL), + rpcs: rpcUrl + .default(["https://evm-rpc-arctic-1.sei-apis.com"]) + .parse(process.env.SEI_DEVNET_RPC_URLS), pricesFromTimestamp: Date.UTC(2024, 0, 1, 0, 0, 0), tokens: [ { @@ -1485,9 +1485,9 @@ const CHAINS: Chain[] = [ { id: 1329, name: "sei-mainnet", - rpc: rpcUrl - .default("https://evm-rpc.sei-apis.com") - .parse(process.env.SEI_MAINNET_RPC_URL), + rpcs: rpcUrl + .default(["https://evm-rpc.sei-apis.com"]) + .parse(process.env.SEI_MAINNET_RPC_URLS), pricesFromTimestamp: Date.UTC(2024, 0, 1, 0, 0, 0), maxGetLogsRange: 10000, tokens: [ @@ -1536,9 +1536,9 @@ const CHAINS: Chain[] = [ { id: 42, name: "lukso-mainnet", - rpc: rpcUrl - .default("https://42.rpc.thirdweb.com") - .parse(process.env.LUKSO_MAINNET_RPC_URL), + rpcs: rpcUrl + .default(["https://42.rpc.thirdweb.com"]) + .parse(process.env.LUKSO_MAINNET_RPC_URLS), pricesFromTimestamp: Date.UTC(2024, 0, 1, 0, 0, 0), tokens: [ { @@ -1586,9 +1586,9 @@ const CHAINS: Chain[] = [ { id: 4201, name: "lukso-testnet", - rpc: rpcUrl - .default("https://4201.rpc.thirdweb.com") - .parse(process.env.LUKSO_TESTNET_RPC_URL), + rpcs: rpcUrl + .default(["https://4201.rpc.thirdweb.com"]) + .parse(process.env.LUKSO_TESTNET_RPC_URLS), pricesFromTimestamp: Date.UTC(2024, 0, 1, 0, 0, 0), tokens: [ { @@ -1627,9 +1627,9 @@ const CHAINS: Chain[] = [ { id: 42220, name: "celo-mainnet", - rpc: rpcUrl - .default("https://forno.celo.org") - .parse(process.env.CELO_MAINNET_RPC_URL), + rpcs: rpcUrl + .default(["https://forno.celo.org"]) + .parse(process.env.CELO_MAINNET_RPC_URLS), pricesFromTimestamp: Date.UTC(2024, 0, 1, 0, 0, 0), tokens: [ { @@ -1686,9 +1686,9 @@ const CHAINS: Chain[] = [ { id: 44787, name: "celo-testnet", - rpc: rpcUrl - .default("https://alfajores-forno.celo-testnet.org") - .parse(process.env.CELO_TESTNET_RPC_URL), + rpcs: rpcUrl + .default(["https://alfajores-forno.celo-testnet.org"]) + .parse(process.env.CELO_TESTNET_RPC_URLS), pricesFromTimestamp: Date.UTC(2024, 0, 1, 0, 0, 0), tokens: [ { @@ -1727,9 +1727,9 @@ const CHAINS: Chain[] = [ { id: 1088, name: "metisAndromeda", - rpc: rpcUrl - .default("https://andromeda.metis.io/?owner=1088") - .parse(process.env.METIS_ANDROMEDA_RPC_URL), + rpcs: rpcUrl + .default(["https://andromeda.metis.io/?owner=1088"]) + .parse(process.env.METIS_ANDROMEDA_RPC_URLS), pricesFromTimestamp: Date.UTC(2024, 0, 1, 0, 0, 0), tokens: [ { diff --git a/src/index.ts b/src/index.ts index 449d5871..4e475c23 100644 --- a/src/index.ts +++ b/src/index.ts @@ -164,9 +164,9 @@ async function main(): Promise { (c) => c.name + " (rpc: " + - c.rpc.slice(0, 25) + + c.rpcs[0].slice(0, 25) + "..." + - c.rpc.slice(-5, -1) + + c.rpcs[0].slice(-5, -1) + ")" ), }); @@ -188,7 +188,7 @@ async function main(): Promise { const chain = getChainConfigById(chainId); const client = createPublicClient({ - transport: http(chain.rpc), + transport: http(chain.rpcs[0]), }); const block = await client.getBlock({ blockNumber }); @@ -565,14 +565,34 @@ async function catchupAndWatchChain( const indexerLogger = chainLogger.child({ subsystem: "DataUpdater" }); - const viemRpcClient = createPublicClient({ - transport: http(config.chain.rpc), - }); + let publicRpcClient: ReturnType | null = null; + + for (const rpcUrl of config.chain.rpcs) { + try { + publicRpcClient = createPublicClient({ + transport: http(rpcUrl), + }); + + const blockNumber = await publicRpcClient.getBlockNumber(); + chainLogger.info( + `Connected to RPC at ${rpcUrl} at block ${blockNumber}` + ); + break; + } catch (error) { + chainLogger.warn( + `Failed to connect to RPC at ${rpcUrl}, trying next one...` + ); + } + } + + if (!publicRpcClient) { + throw new Error("All RPC connections failed"); + } const eventHandlerContext: EventHandlerContext = { chainId: config.chain.id, db, - rpcClient: viemRpcClient, + rpcClient: publicRpcClient, ipfsGet: cachedIpfsGet, priceProvider, blockTimestampInMs: blockTimestampInMs, @@ -583,7 +603,7 @@ async function catchupAndWatchChain( retryDelayMs: 1000, maxConcurrentRequests: 10, maxRetries: 3, - url: config.chain.rpc, + url: config.chain.rpcs[0], onRequest({ method, params }) { chainLogger.trace({ msg: `RPC Request ${method}`, params }); },