mirror of
https://github.com/status-im/status-web.git
synced 2026-08-31 06:11:10 +00:00
update token lists (#743)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@status-im/wallet': patch
|
||||
---
|
||||
|
||||
update token lists
|
||||
@@ -23,6 +23,10 @@ const standardTokenLists = [
|
||||
url: 'https://static.optimism.io/optimism.tokenlist.json',
|
||||
},
|
||||
{ name: 'Arbitrum', url: 'https://bridge.arbitrum.io/token-list-42161.json' },
|
||||
{
|
||||
name: 'CoinMarketCap',
|
||||
url: 'https://api.coinmarketcap.com/data-api/v3/uniswap/all.json',
|
||||
},
|
||||
// {
|
||||
// name: 'Base',
|
||||
// },
|
||||
@@ -32,10 +36,18 @@ const standardTokenLists = [
|
||||
// {
|
||||
// name: 'BSC',
|
||||
// },
|
||||
// {
|
||||
// name: 'Aave',
|
||||
// url: 'https://raw.githubusercontent.com/bgd-labs/aave-address-book/main/tokenlist.json',
|
||||
// },
|
||||
{
|
||||
name: 'Aave',
|
||||
url: 'https://raw.githubusercontent.com/bgd-labs/aave-address-book/main/tokenlist.json',
|
||||
},
|
||||
{
|
||||
name: 'CoinGecko',
|
||||
url: 'https://tokens.coingecko.com/uniswap/all.json',
|
||||
},
|
||||
{
|
||||
name: 'Gemini',
|
||||
url: 'https://www.gemini.com/uniswap/manifest.json',
|
||||
},
|
||||
]
|
||||
|
||||
// will assume non-checksummed addresses and always lowercase them
|
||||
@@ -67,27 +79,42 @@ function toChecksumAddress(address) {
|
||||
|
||||
async function fetchStandardTokenList(url) {
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
console.log(`Fetching token list from: ${url}`)
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'User-Agent': 'Status-Wallet-Token-List-Generator/1.0',
|
||||
},
|
||||
})
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
const data = await response.json()
|
||||
console.log(
|
||||
`Successfully fetched ${data.tokens?.length || data.length} tokens from ${url}`,
|
||||
)
|
||||
return data.tokens || data
|
||||
} catch (error) {
|
||||
console.error(`Error fetching from ${url}:`, error)
|
||||
console.error(`Error fetching from ${url}:`, error.message)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchStatusTokenLists(url) {
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
console.log(`Fetching Status token list from: ${url}`)
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'User-Agent': 'Status-Wallet-Token-List-Generator/1.0',
|
||||
},
|
||||
})
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
return await response.text()
|
||||
const content = await response.text()
|
||||
console.log(`Successfully fetched Status token list from ${url}`)
|
||||
return content
|
||||
} catch (error) {
|
||||
console.error(`Error fetching Go file from ${url}:`, error)
|
||||
console.error(`Error fetching Go file from ${url}:`, error.message)
|
||||
return ''
|
||||
}
|
||||
}
|
||||
@@ -149,7 +176,13 @@ function generateTemplate(tokens) {
|
||||
symbol: token.symbol,
|
||||
decimals: token.decimals,
|
||||
logoURI: token.logoURI,
|
||||
...(token.extensions && { extensions: token.extensions }),
|
||||
...(token.extensions &&
|
||||
token.extensions.bridgeInfo &&
|
||||
Object.keys(token.extensions.bridgeInfo).length > 0 && {
|
||||
extensions: {
|
||||
bridgeInfo: token.extensions.bridgeInfo,
|
||||
},
|
||||
}),
|
||||
})),
|
||||
}
|
||||
|
||||
@@ -212,31 +245,202 @@ function logDuplicateSymbols(tokensWithSameSymbol) {
|
||||
console.log()
|
||||
}
|
||||
|
||||
function extendTokensWithBridgeInfo(tokens) {
|
||||
const tokensByAddress = new Map()
|
||||
const tokensByChainAndAddress = new Map()
|
||||
|
||||
// Index tokens by address and by chain+address
|
||||
tokens.forEach(token => {
|
||||
const address = token.address.toLowerCase()
|
||||
tokensByAddress.set(address, token)
|
||||
|
||||
const chainKey = `${token.chainId}-${address}`
|
||||
tokensByChainAndAddress.set(chainKey, token)
|
||||
})
|
||||
|
||||
// First pass: collect all bridge info from existing tokens
|
||||
const bridgeInfoMap = new Map()
|
||||
|
||||
tokens.forEach(token => {
|
||||
if (token.extensions?.bridgeInfo) {
|
||||
const address = token.address.toLowerCase()
|
||||
if (!bridgeInfoMap.has(address)) {
|
||||
bridgeInfoMap.set(address, new Map())
|
||||
}
|
||||
|
||||
Object.entries(token.extensions.bridgeInfo).forEach(
|
||||
([chainId, bridgeData]) => {
|
||||
if (bridgeData.tokenAddress) {
|
||||
bridgeInfoMap.get(address).set(chainId, {
|
||||
tokenAddress: bridgeData.tokenAddress,
|
||||
// originBridgeAddress: bridgeData.originBridgeAddress,
|
||||
// destBridgeAddress: bridgeData.destBridgeAddress,
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Also check for l1Address in extensions
|
||||
if (token.extensions?.l1Address) {
|
||||
const l1Address = token.extensions.l1Address.toLowerCase()
|
||||
|
||||
if (!bridgeInfoMap.has(l1Address)) {
|
||||
bridgeInfoMap.set(l1Address, new Map())
|
||||
}
|
||||
|
||||
// Add bridge info from L2 to L1
|
||||
bridgeInfoMap.get(l1Address).set(token.chainId.toString(), {
|
||||
tokenAddress: token.address,
|
||||
// originBridgeAddress: token.extensions.l2GatewayAddress,
|
||||
// destBridgeAddress: token.extensions.l1GatewayAddress,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Second pass: extend tokens with bridge info
|
||||
tokens.forEach(token => {
|
||||
const address = token.address.toLowerCase()
|
||||
|
||||
if (!token.extensions) {
|
||||
token.extensions = {}
|
||||
}
|
||||
|
||||
if (!token.extensions.bridgeInfo) {
|
||||
token.extensions.bridgeInfo = {}
|
||||
}
|
||||
|
||||
// Add bridge info for this token
|
||||
if (bridgeInfoMap.has(address)) {
|
||||
bridgeInfoMap.get(address).forEach((bridgeData, chainId) => {
|
||||
if (chainId !== token.chainId.toString()) {
|
||||
token.extensions.bridgeInfo[chainId] = bridgeData
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// // For L1 tokens, also add l1Address extension
|
||||
// if (token.chainId === 1 && !token.extensions.l1Address) {
|
||||
// token.extensions.l1Address = token.address
|
||||
// }
|
||||
})
|
||||
|
||||
return tokens
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('Starting ERC20 token list generation...')
|
||||
|
||||
const statusTokens = new Set()
|
||||
for (const url of statusTokenLists) {
|
||||
const content = await fetchStatusTokenLists(url)
|
||||
const tokens = parseGoFile(content)
|
||||
|
||||
// Fetch Status token lists in parallel
|
||||
console.log('Fetching Status token lists in parallel...')
|
||||
const statusFetchPromises = statusTokenLists.map(async url => {
|
||||
try {
|
||||
const content = await fetchStatusTokenLists(url)
|
||||
if (content) {
|
||||
const tokens = parseGoFile(content)
|
||||
return tokens
|
||||
}
|
||||
return []
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error fetching/parsing Status tokens from ${url}:`,
|
||||
error.message,
|
||||
)
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
const statusResults = await Promise.all(statusFetchPromises)
|
||||
|
||||
// Combine all Status tokens
|
||||
statusResults.forEach(tokens => {
|
||||
tokens.forEach(token => statusTokens.add(token.toLowerCase()))
|
||||
}
|
||||
})
|
||||
|
||||
let standardTokens = new Map()
|
||||
for (const list of standardTokenLists) {
|
||||
const tokens = await fetchStandardTokenList(list.url)
|
||||
tokens.forEach(token => {
|
||||
const checksumAddress = toChecksumAddress(token.address)
|
||||
if (!standardTokens.has(checksumAddress)) {
|
||||
token.address = checksumAddress
|
||||
standardTokens.set(checksumAddress, token)
|
||||
}
|
||||
})
|
||||
let successfulFetches = 0
|
||||
|
||||
// Fetch all token lists in parallel to minimize rate limits and improve performance
|
||||
console.log('Fetching standard token lists in parallel...')
|
||||
const fetchPromises = standardTokenLists.map(async (list, index) => {
|
||||
// Add small delay between requests to be respectful to rate limits
|
||||
if (index > 0) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100 * index))
|
||||
}
|
||||
|
||||
try {
|
||||
const tokens = await fetchStandardTokenList(list.url)
|
||||
return { list, tokens, success: tokens.length > 0 }
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch ${list.name}:`, error.message)
|
||||
return { list, tokens: [], success: false }
|
||||
}
|
||||
})
|
||||
|
||||
const results = await Promise.all(fetchPromises)
|
||||
|
||||
// Process results sequentially to avoid memory issues with large datasets
|
||||
for (const { list, tokens, success } of results) {
|
||||
if (success) {
|
||||
successfulFetches++
|
||||
console.log(`Processing ${tokens.length} tokens from ${list.name}...`)
|
||||
tokens.forEach(token => {
|
||||
const checksumAddress = toChecksumAddress(token.address)
|
||||
if (!standardTokens.has(checksumAddress)) {
|
||||
token.address = checksumAddress
|
||||
standardTokens.set(checksumAddress, token)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Successfully fetched ${successfulFetches}/${standardTokenLists.length} token lists`,
|
||||
)
|
||||
|
||||
if (standardTokens.size === 0) {
|
||||
console.error('No tokens fetched from any source. Exiting.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const combinedStandardTokens = Array.from(standardTokens.values())
|
||||
|
||||
// Extend tokens with bridge information
|
||||
const extendedTokens = extendTokensWithBridgeInfo(combinedStandardTokens)
|
||||
|
||||
// todo: set extensions.bridgeInfo on each token and per chain based on all token lists and if they list
|
||||
// note: from arbitrum token list
|
||||
// {
|
||||
// "extensions": {
|
||||
// "bridgeInfo": {
|
||||
// "1": {
|
||||
// "tokenAddress": "0x469eda64aed3a3ad6f868c44564291aa415cb1d9",
|
||||
// "originBridgeAddress": "0x096760F208390250649E3e8763348E783AEF5562",
|
||||
// "destBridgeAddress": "0xcee284f754e854890e311e3280b767f80797180d"
|
||||
// }
|
||||
// },
|
||||
// "l1Address": "0x469eda64aed3a3ad6f868c44564291aa415cb1d9",
|
||||
// "l2GatewayAddress": "0x096760F208390250649E3e8763348E783AEF5562",
|
||||
// "l1GatewayAddress": "0xcee284f754e854890e311e3280b767f80797180d"
|
||||
// }
|
||||
// }
|
||||
// note: from other token lists
|
||||
// "extensions": {
|
||||
// "bridgeInfo": {
|
||||
// "42161": {
|
||||
// "tokenAddress": "0x63806C056Fa458c548Fb416B15E358A9D685710A"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// todo: set extensions.coingecko.id on each token based on coingecko api response
|
||||
|
||||
const {
|
||||
filteredTokens: standardTokensListedByStatus,
|
||||
missingTokens: standardTokensNotListedByStatus,
|
||||
} = compareTokens(combinedStandardTokens, statusTokens)
|
||||
} = compareTokens(extendedTokens, statusTokens)
|
||||
|
||||
const standardTokensUsedByStatus = standardTokensListedByStatus.filter(
|
||||
token => supportedNetworks.includes(token.chainId),
|
||||
@@ -256,16 +460,18 @@ async function main() {
|
||||
|
||||
const tokensWithSameSymbol = []
|
||||
const symbolMap = new Map()
|
||||
standardTokensUsedByStatus.forEach(token => {
|
||||
// standardTokensUsedByStatus.forEach(token => {
|
||||
// console.log(combinedStandardTokens)
|
||||
extendedTokens.forEach(token => {
|
||||
const tokenWithSameSymbol = symbolMap.get(token.symbol)
|
||||
if (tokenWithSameSymbol && tokenWithSameSymbol.chainId === token.chainId) {
|
||||
tokensWithSameSymbol.push(tokenWithSameSymbol)
|
||||
} else {
|
||||
symbolMap.set(token.symbol, [token])
|
||||
symbolMap.set(token.symbol, token)
|
||||
}
|
||||
})
|
||||
|
||||
const templateContent = generateTemplate(standardTokensUsedByStatus)
|
||||
const templateContent = generateTemplate(extendedTokens)
|
||||
await writeToFile(templateContent, outputFilePath)
|
||||
|
||||
logDifferences(differences)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { Avatar, useToast } from '@status-im/components'
|
||||
import { FeesIcon } from '@status-im/icons/12'
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/Uniswap/token-lists/v1.0.0-beta.32/src/tokenlist.schema.json",
|
||||
"name": "Status Portfolio ERC20 Token List",
|
||||
"timestamp": "2025-05-28T08:31:41.152Z",
|
||||
"version": {
|
||||
"major": 0,
|
||||
"minor": 1,
|
||||
"patch": 0
|
||||
},
|
||||
// note: would list l1 first prior reduicing other sources
|
||||
"tokens": [
|
||||
{
|
||||
"chainId": 1, // Ethereum Mainnet
|
||||
"address": "0xBFDE5ac4f5Adb419A931a5bF64B0f3BB5a623d06",
|
||||
"name": "Flux",
|
||||
"symbol": "FLUX",
|
||||
"decimals": 18,
|
||||
"logoURI": "https://assets.coingecko.com/coins/images/50870/thumb/FLUXCoinGecko.png?1729398545"
|
||||
},
|
||||
{
|
||||
"chainId": 1,
|
||||
"address": "0x720CD16b011b987Da3518fbf38c3071d4F0D1495",
|
||||
"name": "Flux",
|
||||
"symbol": "FLUX",
|
||||
"decimals": 18,
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x720CD16b011b987Da3518fbf38c3071d4F0D1495/logo.png",
|
||||
"extensions": {
|
||||
"bridgeInfo": {
|
||||
// note: not confirmed or deleted
|
||||
// "8453": {
|
||||
// "tokenAddress": "0xb008BDCF9CdFf9da684a190941dC3dCa8C2Cdd44"
|
||||
// },
|
||||
"42161": {
|
||||
"tokenAddress": "0x63806C056Fa458c548Fb416B15E358A9D685710A"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 1,
|
||||
"address": "0x469eDA64aEd3A3Ad6f868c44564291aA415cB1d9",
|
||||
"name": "Datamine FLUX",
|
||||
"symbol": "FLUX",
|
||||
"decimals": 18,
|
||||
"logoURI": "https://s2.coinmarketcap.com/static/img/coins/64x64/5876.png",
|
||||
// note: added manually
|
||||
"extensions": {
|
||||
"bridgeInfo": {
|
||||
"42161": {
|
||||
"tokenAddress": "0xF80D589b3Dbe130c270a69F1a69D050f268786Df"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 1,
|
||||
"address": "0x7645DdfEecedA57e41f92679c4aCd83c56A81D14",
|
||||
"name": "Flux Protocol",
|
||||
"symbol": "FLUX",
|
||||
"decimals": 18,
|
||||
"logoURI": "https://s2.coinmarketcap.com/static/img/coins/64x64/9837.png",
|
||||
// note: added manually
|
||||
"extensions": {
|
||||
"bridgeInfo": {
|
||||
"42161": {
|
||||
"tokenAddress": "0x2338a5d62E9A766289934e8d2e83a443e8065b83"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 8453, // Base
|
||||
"address": "0xb008BDCF9CdFf9da684a190941dC3dCa8C2Cdd44",
|
||||
"name": "Flux",
|
||||
"symbol": "FLUX",
|
||||
"decimals": 18,
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x720CD16b011b987Da3518fbf38c3071d4F0D1495/logo.png",
|
||||
"extensions": {
|
||||
"bridgeInfo": {
|
||||
// note: not confirmed or deleted
|
||||
// "1": {
|
||||
// "tokenAddress": "0x720CD16b011b987Da3518fbf38c3071d4F0D1495"
|
||||
// }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 43114, // Avalanche C-Chain
|
||||
"address": "0xc4B06F17ECcB2215a5DBf042C672101Fc20daF55",
|
||||
"name": "Flux",
|
||||
"symbol": "FLUX",
|
||||
"decimals": 8,
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x720CD16b011b987Da3518fbf38c3071d4F0D1495/logo.png"
|
||||
},
|
||||
{
|
||||
"chainId": 42161, // Arbitrum One
|
||||
"address": "0x63806C056Fa458c548Fb416B15E358A9D685710A",
|
||||
"name": "Flux",
|
||||
"symbol": "FLUX",
|
||||
"decimals": 18,
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x720CD16b011b987Da3518fbf38c3071d4F0D1495/logo.png",
|
||||
"extensions": {
|
||||
"bridgeInfo": {
|
||||
"1": {
|
||||
"tokenAddress": "0x720CD16b011b987Da3518fbf38c3071d4F0D1495"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 42161, // Arbitrum One
|
||||
"address": "0xF80D589b3Dbe130c270a69F1a69D050f268786Df",
|
||||
"name": "Flux",
|
||||
"symbol": "FLUX",
|
||||
"decimals": 18,
|
||||
"logoURI": "https://s2.coinmarketcap.com/static/img/coins/64x64/5876.png",
|
||||
"extensions": {
|
||||
"bridgeInfo": {
|
||||
"1": {
|
||||
"tokenAddress": "0x469eda64aed3a3ad6f868c44564291aa415cb1d9"
|
||||
// "originBridgeAddress": "0x096760F208390250649E3e8763348E783AEF5562",
|
||||
// "destBridgeAddress": "0xcee284f754e854890e311e3280b767f80797180d"
|
||||
}
|
||||
},
|
||||
"l1Address": "0x469eda64aed3a3ad6f868c44564291aa415cb1d9"
|
||||
// "l2GatewayAddress": "0x096760F208390250649E3e8763348E783AEF5562",
|
||||
// "l1GatewayAddress": "0xcee284f754e854890e311e3280b767f80797180d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 42161, // Arbitrum One
|
||||
"address": "0x2338a5d62E9A766289934e8d2e83a443e8065b83",
|
||||
"name": "Flux Protocol",
|
||||
"symbol": "FLUX",
|
||||
"decimals": 18,
|
||||
"logoURI": "https://s2.coinmarketcap.com/static/img/coins/64x64/9837.png",
|
||||
"extensions": {
|
||||
"bridgeInfo": {
|
||||
"1": {
|
||||
"tokenAddress": "0x7645ddfeeceda57e41f92679c4acd83c56a81d14"
|
||||
// "originBridgeAddress": "0x09e9222E96E7B4AE2a407B98d48e330053351EEe",
|
||||
// "destBridgeAddress": "0xa3a7b6f88361f48403514059f1f16c8e78d60eec"
|
||||
}
|
||||
},
|
||||
"l1Address": "0x7645ddfeeceda57e41f92679c4acd83c56a81d14"
|
||||
// "l2GatewayAddress": "0x09e9222E96E7B4AE2a407B98d48e330053351EEe",
|
||||
// "l1GatewayAddress": "0xa3a7b6f88361f48403514059f1f16c8e78d60eec"
|
||||
}
|
||||
},
|
||||
// note: manually transformed from coingecko api responses
|
||||
// note: would log as invalid
|
||||
{
|
||||
"name": "Flux",
|
||||
"symbol": "flux",
|
||||
"extensions": {
|
||||
"coingecko": {
|
||||
"id": "zelcash"
|
||||
},
|
||||
"bridgeInfo": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
// note: manually added
|
||||
"chainId": 1,
|
||||
// note: manually added
|
||||
"address": "0x469eDA64aEd3A3Ad6f868c44564291aA415cB1d9",
|
||||
"symbol": "flux",
|
||||
"name": "Datamine FLUX",
|
||||
"extensions": {
|
||||
"coingecko": {
|
||||
"id": "flux"
|
||||
},
|
||||
"bridgeInfo": {
|
||||
// note: duplicate
|
||||
// "1": {
|
||||
// "tokenAddress": "0x469eda64aed3a3ad6f868c44564291aa415cb1d9"
|
||||
// },
|
||||
"42161": {
|
||||
"tokenAddress": "0xf80d589b3dbe130c270a69f1a69d050f268786df"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 1, // Ethereum Mainnet
|
||||
"address": "0xBFDE5ac4f5Adb419A931a5bF64B0f3BB5a623d06",
|
||||
"symbol": "flux",
|
||||
"name": "Flux",
|
||||
"extensions": {
|
||||
"coingecko": {
|
||||
"id": "flux-2"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+116970
-1274
File diff suppressed because it is too large
Load Diff
@@ -302,7 +302,7 @@ async function all({
|
||||
acc[network].push(token)
|
||||
return acc
|
||||
},
|
||||
{} as Record<NetworkType, typeof erc20TokenList.tokens>,
|
||||
{} as Record<NetworkType, ERC20Token[]>,
|
||||
)
|
||||
|
||||
const partialERC20Assets: Map<
|
||||
@@ -313,7 +313,9 @@ async function all({
|
||||
>
|
||||
> = new Map()
|
||||
|
||||
for (const [network, tokens] of Object.entries(ERC20TokensByNetwork)) {
|
||||
for (const [network, tokens] of Object.entries(ERC20TokensByNetwork) as Array<
|
||||
[string, ERC20Token[]]
|
||||
>) {
|
||||
for (let i = 0; i < tokens.length; i += 100) {
|
||||
const batch = tokens.slice(i, i + 100)
|
||||
const batchBalances = await getERC20TokensBalance(
|
||||
|
||||
Reference in New Issue
Block a user