diff --git a/.gitignore b/.gitignore index 62ce640..50e40b2 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ build/ # Finder (MacOS) folder config .DS_Store + +# Env files +.env diff --git a/README.md b/README.md index 8c36429..a9dd7b5 100644 --- a/README.md +++ b/README.md @@ -238,6 +238,16 @@ The mnemonic option supports hierarchical deterministic (HD) wallet derivation: - Store mnemonics securely - they provide access to all derived accounts - Consider using different account indices for different purposes + +#### Alchemy configuration + +```bash +export ALCHEMY_API_KEY="your-alchemy-api-key-here" +``` + +If you use [Alchemy](https://alchemy.com) and want to use its RPC endpoints, you can specify app's API key. +Otherwise, default JSON RPC rate-limited endpoints will be used. + #### API Keys (For ABI Fetching) ```bash diff --git a/src/core/alchemy.ts b/src/core/alchemy.ts new file mode 100644 index 0000000..22638a1 --- /dev/null +++ b/src/core/alchemy.ts @@ -0,0 +1,96 @@ +// Alchemy RPC URL mapping by chain +// Reference: https://www.alchemy.com/docs/reference/node-supported-chains + +import { + mainnet, + optimism, + arbitrum, + arbitrumNova, + base, + polygon, + polygonZkEvm, + avalanche, + bsc, + zksync, + linea, + celo, + scroll, + mantle, + blast, + metis, + moonbeam, + flowMainnet, + sepolia, + holesky, + optimismSepolia, + arbitrumSepolia, + baseSepolia, + polygonAmoy, + avalancheFuji, + bscTestnet, + zksyncSepoliaTestnet, + lineaSepolia, + celoAlfajores, + scrollSepolia, + mantleSepoliaTestnet, + blastSepolia, + metisSepolia, + flowTestnet, + type Chain, +} from 'viem/chains'; + +export const alchemyNetworkMap = new Map([ + // Mainnets + [mainnet, 'eth-mainnet'], + [optimism, 'opt-mainnet'], + [arbitrum, 'arb-mainnet'], + [arbitrumNova, 'arbnova-mainnet'], + [base, 'base-mainnet'], + [polygon, 'polygon-mainnet'], + [polygonZkEvm, 'polygonzkevm-mainnet'], + [avalanche, 'avax-mainnet'], + [bsc, 'bnb-mainnet'], + [zksync, 'zksync-mainnet'], + [linea, 'linea-mainnet'], + [celo, 'celo-mainnet'], + [scroll, 'scroll-mainnet'], + [mantle, 'mantle-mainnet'], + [blast, 'blast-mainnet'], + [metis, 'metis-mainnet'], + [moonbeam, 'moonbeam-mainnet'], + [flowMainnet, 'flow-mainnet'], + + // Testnets + [sepolia, 'eth-sepolia'], + [holesky, 'eth-holesky'], + [optimismSepolia, 'opt-sepolia'], + [arbitrumSepolia, 'arb-sepolia'], + [baseSepolia, 'base-sepolia'], + [polygonAmoy, 'polygon-amoy'], + [avalancheFuji, 'avax-fuji'], + [bscTestnet, 'bnb-testnet'], + [zksyncSepoliaTestnet, 'zksync-sepolia'], + [lineaSepolia, 'linea-sepolia'], + [celoAlfajores, 'celo-alfajores'], + [scrollSepolia, 'scroll-sepolia'], + [mantleSepoliaTestnet, 'mantle-sepolia'], + [blastSepolia, 'blast-sepolia'], + [metisSepolia, 'metis-sepolia'], + [flowTestnet, 'flow-testnet'], +]); + +const alchemyNetworkByChainId: Record = Object.fromEntries( + [...alchemyNetworkMap.entries()].map(([chain, network]) => [chain.id, network]) +); + +export function getAlchemyRpcUrl(chainId: number, apiKey: string): string | null { + const network = alchemyNetworkByChainId[chainId]; + if (!network) { + return null; + } + return `https://${network}.g.alchemy.com/v2/${apiKey}`; +} + +export function isAlchemySupported(chainId: number): boolean { + return chainId in alchemyNetworkByChainId; +} diff --git a/src/core/chains.ts b/src/core/chains.ts index 75115d3..6ed4a05 100644 --- a/src/core/chains.ts +++ b/src/core/chains.ts @@ -1,4 +1,5 @@ import { type Chain } from 'viem'; +import { getAlchemyRpcUrl } from './alchemy.js'; import { // Mainnets mainnet, @@ -342,7 +343,14 @@ export function getRpcUrl(chainIdentifier: number | string = DEFAULT_CHAIN_ID): const chainId = typeof chainIdentifier === 'string' ? resolveChainId(chainIdentifier) : chainIdentifier; - + + if (process.env.ALCHEMY_API_KEY) { + const alchemyUrl = getAlchemyRpcUrl(chainId, process.env.ALCHEMY_API_KEY); + if (alchemyUrl) { + return alchemyUrl; + } + } + return rpcUrlMap[chainId] || DEFAULT_RPC_URL; }