UNPKG

blockchain-api

Version:

API utilities for interacting with the Exatechl2 blockchain

111 lines (109 loc) 5.47 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getTokenHolders = getTokenHolders; const rpc_1 = require("../../core/rpc"); const blocks_1 = require("../blocks/blocks"); const formatters_1 = require("../../utils/formatters"); const tokens_1 = require("./tokens"); /** * @param {string} tokenAddress * @param {number} limit * @returns {Promise<TokenHolder[]>} */ async function getTokenHolders(tokenAddress, limit = 20) { try { const normalizedAddress = tokenAddress.startsWith('0x') ? tokenAddress : `0x${tokenAddress}`; if (normalizedAddress.length !== 42) { throw new Error(`Invalid token address format: ${normalizedAddress}`); } const code = await (0, rpc_1.callRpc)('eth_getCode', [normalizedAddress, 'latest']); const isContract = code && code !== '0x' && code !== '0x0'; if (!isContract) { throw new Error(`Address ${normalizedAddress} is not a contract`); } const { decimals } = await (0, tokens_1.getTokenInfo)(normalizedAddress); const transferEventSignature = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'; const balanceOfSignature = '0x70a08231'; const latestBlockHex = await (0, blocks_1.getLatestBlockNumber)(); const blockRange = 10000; const fromBlock = Math.max(0, (0, formatters_1.parseHexToDecimal)(latestBlockHex) - blockRange); const fromBlockHex = (0, formatters_1.formatDecimalToHex)(fromBlock); const logs = await (0, rpc_1.callRpc)('eth_getLogs', [{ address: normalizedAddress, fromBlock: fromBlockHex, toBlock: 'latest', topics: [transferEventSignature] }]); const balanceMap = new Map(); const lastUpdateMap = new Map(); const totalSupplyBigInt = BigInt(await (0, rpc_1.callRpc)('eth_call', [{ to: normalizedAddress, data: '0x18160ddd' }, 'latest']).catch(() => '0x0')); for (const log of logs) { if (log.topics && log.topics.length >= 3) { const fromAddress = '0x' + log.topics[1].slice(26).toLowerCase(); const toAddress = '0x' + log.topics[2].slice(26).toLowerCase(); const value = BigInt(log.data); const blockNumber = (0, formatters_1.parseHexToDecimal)(log.blockNumber); const block = await (0, blocks_1.getBlockByNumber)(blockNumber, false); let timestamp = Math.floor(Date.now() / 1000); if (block && block.timestamp) { try { const date = (0, formatters_1.parseBlockchainTimestamp)(block.timestamp); timestamp = Math.floor(date.getTime() / 1000); } catch (error) { console.error('Error parsing block timestamp:', error); } } const currentFromBalance = balanceMap.get(fromAddress) || BigInt(0); balanceMap.set(fromAddress, currentFromBalance - value); lastUpdateMap.set(fromAddress, timestamp); const currentToBalance = balanceMap.get(toAddress) || BigInt(0); balanceMap.set(toAddress, currentToBalance + value); lastUpdateMap.set(toAddress, timestamp); } } const holders = []; for (const [address, estimatedBalance] of balanceMap.entries()) { if (estimatedBalance <= BigInt(0) && address !== '0x0000000000000000000000000000000000000000') continue; try { const paddedAddress = '000000000000000000000000' + address.slice(2); const balanceHex = await (0, rpc_1.callRpc)('eth_call', [{ to: normalizedAddress, data: balanceOfSignature + paddedAddress }, 'latest']); const balanceBigInt = BigInt(balanceHex); if (balanceBigInt <= BigInt(0)) continue; const percentage = totalSupplyBigInt > BigInt(0) ? Number((balanceBigInt * BigInt(10000)) / totalSupplyBigInt) / 100 : 0; const balanceInTokens = Number(balanceBigInt) / Math.pow(10, decimals); const formattedBalance = (0, formatters_1.formatTokenAmount)(balanceBigInt.toString(), decimals); const lastUpdated = lastUpdateMap.get(address) || Math.floor(Date.now() / 1000); const isZeroAddress = address === '0x0000000000000000000000000000000000000000'; holders.push({ address, balance: formattedBalance, rawBalance: balanceBigInt.toString(), percentage: percentage.toFixed(2) + '%', lastUpdated, label: isZeroAddress ? 'Burned Tokens' : undefined }); } catch (e) { console.warn(`Error getting balance for ${address}:`, e); } } return holders .sort((a, b) => BigInt(b.rawBalance) > BigInt(a.rawBalance) ? 1 : -1) .slice(0, limit); } catch (error) { console.error('Error getting token holders:', error); return []; } }