From 625cc1e5bad78f459e76cfe2eef08157d4701d98 Mon Sep 17 00:00:00 2001 From: Jon Tzeng Date: Fri, 3 Nov 2023 17:33:40 -0700 Subject: [PATCH] fixup! Use eth-balance-checker contracts for batch token balance queries --- src/ethereum/EthereumNetwork.ts | 147 +++++++++++++++----------------- 1 file changed, 67 insertions(+), 80 deletions(-) diff --git a/src/ethereum/EthereumNetwork.ts b/src/ethereum/EthereumNetwork.ts index bf4c2eb8f..51c005af9 100644 --- a/src/ethereum/EthereumNetwork.ts +++ b/src/ethereum/EthereumNetwork.ts @@ -503,20 +503,37 @@ export class EthereumNetwork { return await resultRaw.json() } - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type async fetchPostRPC( method: string, params: Object, networkId: number, url: string - ) { + ): Promise { const body = { id: networkId, jsonrpc: '2.0', method, params } + url = this.addRpcApiKey(url) + + const response = await this.ethEngine.fetchCors(url, { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json' + }, + method: 'POST', + body: JSON.stringify(body) + }) + + const parsedUrl = parse(url, {}, true) + if (!response.ok) { + this.throwError(response, 'fetchPostRPC', parsedUrl.hostname) + } + return await response.json() + } + addRpcApiKey(url: string): string { const regex = /{{(.*?)}}/g const match = regex.exec(url) if (match != null) { @@ -533,21 +550,7 @@ export class EthereumNetwork { throw new Error('Incorrect apikey type for RPC') } } - - const response = await this.ethEngine.fetchCors(url, { - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json' - }, - method: 'POST', - body: JSON.stringify(body) - }) - - const parsedUrl = parse(url, {}, true) - if (!response.ok) { - this.throwError(response, 'fetchPostRPC', parsedUrl.hostname) - } - return await response.json() + return url } // eslint-disable-next-line @typescript-eslint/explicit-function-return-type @@ -1515,87 +1518,71 @@ export class EthereumNetwork { */ // @ts-expect-error async checkEthBalChecker(): Promise { - const { builtinTokens, networkInfo, walletLocalData, currencyInfo } = + const { allTokensMap, networkInfo, walletLocalData, currencyInfo } = this.ethEngine const { chainParams, rpcServers, ethBalCheckerContract } = networkInfo const tokenBal: { [currencyCode: string]: string } = {} - // TODO: Consider just using a specific rpc server for ethers use - const jsonRpcUrl = rpcServers.find( - rpcServer => - !rpcServer.includes('alchemyApiKey') && - !rpcServer.includes('infuraProjectId') && - !rpcServer.includes('poktPortalApiKey') - ) - if (ethBalCheckerContract != null && jsonRpcUrl != null) { + if (ethBalCheckerContract == null) return tokenBal + + const mainnetAssetAddr = '0x0000000000000000000000000000000000000000' + const balanceQueryAddrs = [ + ...Object.values(allTokensMap).map(token => + token.networkLocation?.contractAddress.toLowerCase() + ), + mainnetAssetAddr // to query ETH on ETH network, MATIC on MATIC, etc. + ] + + const promises: any[] = [] + rpcServers.forEach(rpcServer => { + const rpcServerWithApiKey = this.addRpcApiKey(rpcServer) const ethProvider = new ethers.providers.JsonRpcProvider( - jsonRpcUrl, + rpcServerWithApiKey, chainParams.chainId ) - const mainnetTokenAddr = '0x0000000000000000000000000000000000000000' - const tokenAddrs = [ - ...Object.values(builtinTokens) - .map(tokenInfo => tokenInfo.networkLocation) - .filter((location): location is JsonObject => location != null) - .map(location => location.contractAddress), - mainnetTokenAddr - ] - const contract = new ethers.Contract( ethBalCheckerContract, ETH_BAL_CHECKER_ABI, ethProvider ) - const balances = await contract - .balances([walletLocalData.publicKey], tokenAddrs) - .catch((e: any) => { - this.logError('checkEthBalChecker', e) - }) - - for (let i = 0; i < balances.length; i++) { - const tokenAddr = tokenAddrs[i] - const balanceBnHex = balances[i] - - let unitName - let multiplier - let balanceCurrencyCode - if (tokenAddr === mainnetTokenAddr) { - unitName = 'ether' - const { currencyCode, denominations } = currencyInfo - balanceCurrencyCode = currencyCode - multiplier = denominations[0].multiplier - } else { - const builtinToken = Object.values(builtinTokens).find( - tokenInfo => - tokenInfo.networkLocation?.contractAddress === tokenAddr - ) - if (builtinToken == null) { - this.logError( - 'checkEthBalChecker', - new Error(`checkEthBalChecker missing builtinToken: ${tokenAddr}`) - ) - continue - } - const { currencyCode, denominations } = builtinToken - balanceCurrencyCode = currencyCode - multiplier = denominations[0].multiplier - unitName = multiplier.length - 1 - } - - const nativeAmount = mul( - ethers.utils.formatUnits(balanceBnHex, unitName).toString(), - multiplier - ) + promises.push( + contract.balances([walletLocalData.publicKey], balanceQueryAddrs) + ) + }) - tokenBal[balanceCurrencyCode] = nativeAmount - } - } else { + const balances = await promiseAny(promises).catch(e => { this.logError( 'checkEthBalChecker', - new Error('checkEthBalChecker missing params') + new Error(`All rpc servers failed eth balance checks: ${e}`) ) + return [] + }) + + for (let i = 0; i < balances.length; i++) { + const tokenAddr = balanceQueryAddrs[i] + const balanceBnHex = balances[i] + + let balanceCurrencyCode + if (tokenAddr === mainnetAssetAddr) { + const { currencyCode } = currencyInfo + balanceCurrencyCode = currencyCode + } else { + const token = allTokensMap[tokenAddr.replace('0x', '')] + if (token == null) { + this.logError( + 'checkEthBalChecker', + new Error(`checkEthBalChecker missing builtinToken: ${tokenAddr}`) + ) + continue + } + const { currencyCode } = token + balanceCurrencyCode = currencyCode + } + + tokenBal[balanceCurrencyCode] = + ethers.BigNumber.from(balanceBnHex).toString() } return { tokenBal, server: 'ethBalChecker' }