blockchain-api
Version:
API utilities for interacting with the Exatechl2 blockchain
111 lines (109 loc) • 5.47 kB
JavaScript
;
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 [];
}
}