@intuweb3/sdk
Version:
INTU SDK - Modern blockchain interaction toolkit
213 lines • 7.75 kB
JavaScript
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