UNPKG

@intuweb3/sdk

Version:

INTU SDK - Modern blockchain interaction toolkit

213 lines 7.75 kB
class RpcOptimizer { gasPriceCache = new Map(); CACHE_DURATION = 120000; MAX_RETRIES = 3; BASE_DELAY = 1000; RPC_ENDPOINTS = { 421614: [ { url: "https://sepolia-rollup.arbitrum.io/rpc", priority: 3, isFallback: false, }, { url: "https://arbitrum-sepolia.public.blastapi.io", priority: 2, isFallback: false, }, { url: "https://arbitrum-sepolia.infura.io/v3/f0b33e4b953e4306b6d5e8b9f9d51567", priority: 1, isFallback: true, }, ], 42161: [ { url: "https://arb1.arbitrum.io/rpc", priority: 3, isFallback: false, }, { url: "https://arbitrum-mainnet.public.blastapi.io", priority: 2, isFallback: false, }, { url: "https://arbitrum-mainnet.infura.io/v3/f0b33e4b953e4306b6d5e8b9f9d51567", priority: 1, isFallback: true, }, ], }; isMetaMaskProvider(provider) { if (!provider) return false; if (provider.isMetaMask) return true; if (provider._metamask) return true; if (provider.isConnected && provider.selectedAddress) return true; if (provider.currentProvider?.isMetaMask) return true; if (provider.currentProvider?._metamask) return true; if (provider.eth?.accounts && provider.eth?.getAccounts) return true; return false; } async getCachedGasPrice(provider, chainId) { const cacheKey = `gasPrice_${chainId}`; const cached = this.gasPriceCache.get(cacheKey); if (cached && Date.now() - cached.timestamp < this.CACHE_DURATION) { return cached.price; } try { const price = await this.callWithFallback(async (endpoint) => { if (provider.eth?.getGasPrice) { return await provider.eth.getGasPrice(); } else if (provider.getGasPrice) { return await provider.getGasPrice(); } else { throw new Error("No gas price method available"); } }, chainId, this.isMetaMaskProvider(provider), provider); const gasPrice = BigInt(price); this.gasPriceCache.set(cacheKey, { price: gasPrice, timestamp: Date.now(), chainId, }); return gasPrice; } catch (error) { return BigInt("20000000000"); } } clearGasPriceCache() { this.gasPriceCache.clear(); } async batchRpcCalls(provider, calls, chainId) { if (calls.length === 0) return []; const batchRequest = calls.map((call, index) => ({ jsonrpc: "2.0", method: call.method, params: call.params, id: call.id || index.toString(), })); try { const response = await this.callWithFallback(async (endpoint) => { if (provider.sendBatch) { return await provider.sendBatch(batchRequest); } const results = []; for (const call of calls) { try { const result = await this.makeRpcCall(provider, call.method, call.params); results.push({ id: call.id, result }); } catch (error) { results.push({ id: call.id, error: { message: error.message } }); } } return results; }, chainId, this.isMetaMaskProvider(provider), provider); return response; } catch (error) { throw error; } } async makeRpcCall(provider, method, params) { if (provider.eth && provider.eth[method]) { return await provider.eth[method](...params); } else if (provider[method]) { return await provider[method](...params); } else { throw new Error(`RPC method ${method} not available on provider`); } } async callWithFallback(operation, chainId, isMetaMask = false, provider) { const endpoints = this.RPC_ENDPOINTS[chainId] || []; if (endpoints.length === 0) { throw new Error(`No RPC endpoints configured for chain ${chainId}`); } const sortedEndpoints = isMetaMask ? [...endpoints].sort((a, b) => { if (a.isFallback && !b.isFallback) return -1; if (!a.isFallback && b.isFallback) return 1; return a.priority - b.priority; }) : [...endpoints].sort((a, b) => a.priority - b.priority); for (const endpoint of sortedEndpoints) { try { if (provider.currentProvider && typeof provider.currentProvider.setEndpoint === "function") { provider.currentProvider.setEndpoint(endpoint.url); } const result = await this.retryWithBackoff(() => operation(endpoint.url), this.MAX_RETRIES, this.BASE_DELAY); return result; } catch (error) { continue; } } throw new Error(`All RPC endpoints failed for chain ${chainId}`); } async retryWithBackoff(operation, maxRetries, baseDelay) { for (let i = 0; i < maxRetries; i++) { try { return await operation(); } catch (error) { if (i === maxRetries - 1) throw error; const delay = baseDelay * Math.pow(2, i); await new Promise((resolve) => setTimeout(resolve, delay)); } } throw new Error("Max retries exceeded"); } async getOptimizedGasEstimate(contract, method, params, from, chainId, provider) { const cacheKey = `gasEstimate_${method}_${from}`; const cached = this.gasPriceCache.get(cacheKey); if (cached && Date.now() - cached.timestamp < this.CACHE_DURATION) { return cached.price; } try { const estimated = await this.callWithFallback(async () => { if (contract.methods && contract.methods[method]) { return await contract.methods[method](...params).estimateGas({ from, }); } else { throw new Error(`Method ${method} not found on contract`); } }, chainId, this.isMetaMaskProvider(provider), provider); const gasLimit = (BigInt(estimated) * BigInt(120)) / BigInt(100); this.gasPriceCache.set(cacheKey, { price: gasLimit, timestamp: Date.now(), chainId, }); return gasLimit; } catch (error) { throw new Error(`Gas estimation failed and no static limit available for ${method}`); } } getEndpointInfo(chainId) { return this.RPC_ENDPOINTS[chainId] || []; } } export const rpcOptimizer = new RpcOptimizer(); export default rpcOptimizer; //# sourceMappingURL=rpc-optimizer.js.map