UNPKG

@phiresky/eth-scan

Version:

An efficient Ether and token balance scanner

121 lines (109 loc) 3.61 kB
import { decode, toNumber } from '@findeth/abi'; import { BATCH_SIZE, CONTRACT_ADDRESS } from './constants'; import { call, ProviderLike } from './providers'; import { BalanceMap, EthScanOptions, Result } from './types'; import { batch } from './utils'; const isResult = (result: unknown): result is Result => { return Array.isArray(result) && result.length === 2; }; /** * Get a balance map from an array of addresses and an array of balances. * * @param {string[]} addresses * @param {bigint[]} results * @return {BalanceMap} */ export const toBalanceMap = (addresses: string[], results: Array<bigint | Result>): BalanceMap => { const m: BalanceMap = {}; for (const [index, next] of results.entries()) { const value = isResult(next) ? toNumber(next[1].slice(0, 32)) : next; m[addresses[index]] = value; } return m; }; /** * Get a nested balance map from an array of addresses, token addresses, and results. * * @param {string[]} addresses * @param {bigint[]} tokenAddresses * @param {BalanceMap<BalanceMap>} results */ export const toNestedBalanceMap = ( addresses: string[], tokenAddresses: string[], results: Array<Array<bigint | Result>> ): BalanceMap<BalanceMap> => { const m: BalanceMap<BalanceMap> = {}; for (const [index, next] of results.entries()) { m[addresses[index]] = toBalanceMap(tokenAddresses, next); } return m; }; /** * Low level API function to send a contract call that returns a single Result array. It will automatically retry any * failed calls. * * @param provider * @param batchAddresses The addresses to batch by * @param addresses The address(es) to use when retrying failed calls * @param contractAddresses The contract address(es) to use when retrying failed calls * @param encodeData * @param encodeSingle * @param options */ export const callSingle = async ( provider: ProviderLike, batchAddresses: string[], addresses: string | string[], contractAddresses: string | string[], encodeData: (addresses: string[]) => string, encodeSingle: (address: string) => string, options?: EthScanOptions ): Promise<Result[]> => { const contractAddress = options?.contractAddress ?? CONTRACT_ADDRESS; const batchSize = options?.batchSize ?? BATCH_SIZE; const results = await batch( async (batchedAddresses: string[]) => { const data = encodeData(batchedAddresses); const buffer = await call(provider, contractAddress, data); return decode(['(bool,bytes)[]'], buffer)[0] as Result[]; }, batchSize, batchAddresses ); return retryCalls(provider, addresses, contractAddresses, results, encodeSingle); }; /** * Retry calls to the contract directly, if a contract call in the eth-scan contract failed. * * @param provider * @param addresses * @param contracts * @param results * @param encodeData */ export const retryCalls = async ( provider: ProviderLike, addresses: string | string[], contracts: string | string[], results: Result[], encodeData: (address: string) => string ): Promise<Result[]> => { return Promise.all( results.map(async (result, index) => { if (result[0]) { return result; } const address = typeof addresses === 'string' ? addresses : addresses[index]; const contractAddress = typeof contracts === 'string' ? contracts : contracts[index]; const data = encodeData(address); try { const newResult = await call(provider, contractAddress, data); return [true, newResult] as [boolean, Uint8Array]; } catch { // noop } return result; }) ); };