From 53bfa60b9b314a7412b265fdea6e7de4454e49ea Mon Sep 17 00:00:00 2001 From: Genaro Bonavita <98661193+genaroibc@users.noreply.github.com> Date: Thu, 26 Oct 2023 11:58:03 -0300 Subject: [PATCH] fix: use a different rpc url for each chain (#230) * fix: use a different rpc url for each chain * fix: catch errors when fetching balances * refactor: skip iteration if no token is found * fix: use injected rpc url for tokens not supporting multicall * fix radix --------- Co-authored-by: jmd3v --- src/index.ts | 10 ++- src/services/getEvmBalances.ts | 107 ++++++++++++++++++++++----------- 2 files changed, 80 insertions(+), 37 deletions(-) diff --git a/src/index.ts b/src/index.ts index d3f368b..2462df0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -771,10 +771,18 @@ export class Squid { }): Promise { // remove invalid and duplicate chains and convert to number const filteredChains = new Set(chains.map(Number).filter(c => !isNaN(c))); + const chainRpcUrls = this.chains.reduce( + (acc, chain) => ({ + ...acc, + [chain.chainId]: chain.rpc + }), + {} + ); return getAllEvmTokensBalance( this.tokens.filter(t => filteredChains.has(Number(t.chainId))), - userAddress + userAddress, + chainRpcUrls ); } diff --git a/src/services/getEvmBalances.ts b/src/services/getEvmBalances.ts index 6d2532a..ad9e019 100644 --- a/src/services/getEvmBalances.ts +++ b/src/services/getEvmBalances.ts @@ -10,19 +10,15 @@ import { type ContractAddress = `0x${string}`; const CHAINS_WITHOUT_MULTICALL = [314, 3141]; // Filecoin, & Filecoin testnet -const CHAINS_WITHOUT_MULTICALL_RPC_URLS: Record = { - 314: "https://rpc.ankr.com/filecoin", - 3141: "https://rpc.ankr.com/filecoin" -}; const getTokensBalanceSupportingMultiCall = async ( tokens: TokenData[], + chainRpcUrl: string, userAddress?: ContractAddress ): Promise => { if (!userAddress) return []; - const ETHEREUM_RPC_URL = "https://eth.meowrpc.com"; - const provider = new ethers.providers.JsonRpcProvider(ETHEREUM_RPC_URL); + const provider = new ethers.providers.JsonRpcProvider(chainRpcUrl); const contractCallContext: ContractCallContext[] = tokens.map(token => { const isNativeToken = @@ -57,35 +53,44 @@ const getTokensBalanceSupportingMultiCall = async ( tryAggregate: true }); - const { results } = (await multicallInstance.call(contractCallContext)) ?? { - results: {} - }; + try { + const { results } = (await multicallInstance.call(contractCallContext)) ?? { + results: {} + }; + const tokenBalances: TokenBalance[] = []; - const tokenBalances: TokenBalance[] = []; + for (const symbol in results) { + const data = results[symbol].callsReturnContext[0] ?? {}; - for (const symbol in results) { - const data = results[symbol].callsReturnContext[0] ?? {}; + const token = tokens.find(t => t.symbol === symbol); - const { decimals = 18, address = "0x" } = - tokens.find(t => t.symbol === symbol) ?? {}; + if (!token) continue; - const mappedBalance: TokenBalance = { - symbol, - address, - decimals, - // balance in wei - balance: parseInt(data.returnValues[0]?.hex ?? "0", 16).toString() - }; + const { decimals, address } = token; - tokenBalances.push(mappedBalance); - } + const mappedBalance: TokenBalance = { + symbol, + address, + decimals, + // balance in wei + balance: parseInt(data.returnValues[0]?.hex ?? "0", 10).toString() + }; + + tokenBalances.push(mappedBalance); + } - return tokenBalances; + return tokenBalances; + } catch (error) { + return []; + } }; const getTokensBalanceWithoutMultiCall = async ( tokens: TokenData[], - userAddress: ContractAddress + userAddress: ContractAddress, + rpcUrlsPerChain: { + [chainId: string]: string; + } ): Promise => { const balances: (TokenBalance | null)[] = await Promise.all( tokens.map(async t => { @@ -94,12 +99,14 @@ const getTokensBalanceWithoutMultiCall = async ( if (t.address === NATIVE_EVM_TOKEN_ADDRESS) { balance = await fetchBalance({ token: t, - userAddress + userAddress, + rpcUrl: rpcUrlsPerChain[t.chainId] }); } else { balance = await fetchBalance({ token: t, - userAddress + userAddress, + rpcUrl: rpcUrlsPerChain[t.chainId] }); } @@ -116,7 +123,10 @@ const getTokensBalanceWithoutMultiCall = async ( export const getAllEvmTokensBalance = async ( evmTokens: TokenData[], - userAddress: string + userAddress: string, + chainRpcUrls: { + [chainId: string]: string; + } ): Promise => { try { // Some tokens don't support multicall, so we need to fetch them with Promise.all @@ -136,14 +146,39 @@ export const getAllEvmTokensBalance = async ( const tokensNotSupportingMulticall = splittedTokensByMultiCallSupport[0]; const tokensSupportingMulticall = splittedTokensByMultiCallSupport[1]; - const tokensMulticall = await getTokensBalanceSupportingMultiCall( - tokensSupportingMulticall, - userAddress as ContractAddress + const tokensByChainId = tokensSupportingMulticall.reduce( + (groupedTokens, token) => { + if (!groupedTokens[token.chainId]) { + groupedTokens[token.chainId] = []; + } + + groupedTokens[token.chainId].push(token); + + return groupedTokens; + }, + {} as Record ); + const tokensMulticall: TokenBalance[] = []; + + for (const chainId in tokensByChainId) { + const tokens = tokensByChainId[chainId]; + const rpcUrl = chainRpcUrls[chainId]; + + if (!rpcUrl) continue; + + const tokensBalances = await getTokensBalanceSupportingMultiCall( + tokens, + rpcUrl, + userAddress as ContractAddress + ); + + tokensMulticall.push(...tokensBalances); + } const tokensNotMultiCall = await getTokensBalanceWithoutMultiCall( tokensNotSupportingMulticall, - userAddress as ContractAddress + userAddress as ContractAddress, + chainRpcUrls ); return [...tokensMulticall, ...tokensNotMultiCall]; @@ -156,16 +191,16 @@ export const getAllEvmTokensBalance = async ( type FetchBalanceParams = { token: TokenData; userAddress: ContractAddress; + rpcUrl: string; }; async function fetchBalance({ token, - userAddress + userAddress, + rpcUrl }: FetchBalanceParams): Promise { try { - const provider = new ethers.providers.JsonRpcProvider( - CHAINS_WITHOUT_MULTICALL_RPC_URLS[Number(token.chainId)] - ); + const provider = new ethers.providers.JsonRpcProvider(rpcUrl); const tokenAbi = ["function balanceOf(address) view returns (uint256)"]; const tokenContract = new ethers.Contract(