UNPKG

blockchain-api

Version:

API utilities for interacting with the Exatechl2 blockchain

239 lines (237 loc) 10.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getTokenInfo = getTokenInfo; const rpc_1 = require("../../core/rpc"); const explorerApi_1 = require("../../core/explorerApi"); const formatters_1 = require("../../utils/formatters"); const logger_1 = require("../../utils/logger"); /** * @param {string} tokenAddress * @returns {Promise<TokenInfo>} */ async function getTokenInfo(tokenAddress) { 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 nameSignature = '0x06fdde03'; const symbolSignature = '0x95d89b41'; const decimalsSignature = '0x313ce567'; const totalSupplySignature = '0x18160ddd'; const [nameResult, symbolResult, decimalsResult, totalSupplyResult] = await Promise.all([ (0, rpc_1.callRpc)('eth_call', [{ to: normalizedAddress, data: nameSignature }, 'latest']).catch(() => '0x'), (0, rpc_1.callRpc)('eth_call', [{ to: normalizedAddress, data: symbolSignature }, 'latest']).catch(() => '0x'), (0, rpc_1.callRpc)('eth_call', [{ to: normalizedAddress, data: decimalsSignature }, 'latest']).catch(() => '0x0000000000000000000000000000000000000000000000000000000000000012'), (0, rpc_1.callRpc)('eth_call', [{ to: normalizedAddress, data: totalSupplySignature }, 'latest']).catch(() => '0x0') ]); let decimals = 18; if (decimalsResult && decimalsResult !== '0x') { decimals = (0, formatters_1.parseHexToDecimal)(decimalsResult); } let name = 'Unknown Token'; let symbol = 'UNKNOWN'; if (nameResult && nameResult !== '0x') { try { const nameHex = nameResult.slice(2); const nameLength = (0, formatters_1.parseHexToDecimal)('0x' + nameHex.slice(64, 128)); const nameData = nameHex.slice(128, 128 + nameLength * 2); name = Buffer.from(nameData, 'hex').toString('utf8'); } catch (e) { console.error('Error parsing token name:', e); } } if (symbolResult && symbolResult !== '0x') { try { const symbolHex = symbolResult.slice(2); const symbolLength = (0, formatters_1.parseHexToDecimal)('0x' + symbolHex.slice(64, 128)); const symbolData = symbolHex.slice(128, 128 + symbolLength * 2); symbol = Buffer.from(symbolData, 'hex').toString('utf8'); } catch (e) { console.error('Error parsing token symbol:', e); } } let totalSupply = '0'; let formattedTotalSupply = '0'; if (totalSupplyResult && totalSupplyResult !== '0x') { try { const rawSupply = (0, formatters_1.parseHexToDecimal)(totalSupplyResult); totalSupply = rawSupply.toString(); formattedTotalSupply = (0, formatters_1.formatTokenAmount)(totalSupply, decimals, ''); } catch (e) { console.error('Error parsing total supply:', e); } } let creator = null; let creationTx = null; let creationTimestamp = null; try { const tokenFromExplorer = await (0, explorerApi_1.callExplorerApi)(`tokens/${normalizedAddress}`); if (tokenFromExplorer && tokenFromExplorer.data) { const tokenData = tokenFromExplorer.data; creator = tokenData.creator_address || null; creationTx = tokenData.creation_transaction || null; if (tokenData.creation_timestamp) { creationTimestamp = (0, formatters_1.standardizeTimestamp)(tokenData.creation_timestamp); } else { creationTimestamp = null; } if (tokenData.name && name === 'Unknown Token') { name = tokenData.name; } if (tokenData.symbol && symbol === 'UNKNOWN') { symbol = tokenData.symbol; } logger_1.logger.debug(`Got token info from Explorer DB for ${normalizedAddress}`); } } catch (e) { console.warn(`Could not fetch token info from Explorer DB: ${e.message}`); } const transferEventSignature = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'; const latestBlockHex = await (0, rpc_1.callRpc)('eth_blockNumber'); 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 addresses = new Set(); let transfersCount = logs.length; logs.forEach(log => { if (log.topics && log.topics.length >= 3) { const fromAddress = '0x' + log.topics[1].slice(26); const toAddress = '0x' + log.topics[2].slice(26); if (fromAddress !== '0x0000000000000000000000000000000000000000') { addresses.add(fromAddress); } if (toAddress !== '0x0000000000000000000000000000000000000000') { addresses.add(toAddress); } } }); const holdersCount = addresses.size; const excludedAddresses = [ '0x0000000000000000000000000000000000000000', normalizedAddress.toLowerCase(), ]; let lockedSupply = BigInt(0); const balanceOfSignature = '0x70a08231'; try { const balancePromises = excludedAddresses.map(async (address) => { if (address === '0x0000000000000000000000000000000000000000') { } const normalizedExcludedAddress = address.startsWith('0x') ? address.toLowerCase() : `0x${address}`.toLowerCase(); const paddedAddress = '000000000000000000000000' + normalizedExcludedAddress.slice(2); try { const balanceHex = await (0, rpc_1.callRpc)('eth_call', [{ to: normalizedAddress, data: balanceOfSignature + paddedAddress }, 'latest']); return BigInt(balanceHex); } catch (e) { console.warn(`Error getting balance for excluded address ${normalizedExcludedAddress}:`, e); return BigInt(0); } }); const excludedBalances = await Promise.all(balancePromises); lockedSupply = excludedBalances.reduce((sum, balance) => sum + balance, BigInt(0)); logger_1.logger.debug(`Calculated locked supply: ${lockedSupply.toString()}`); } catch (e) { console.warn('Error calculating locked supply:', e); logger_1.logger.debug('Falling back to estimated circulating supply'); } let totalSupplyBigInt; try { if (totalSupply.includes('e+')) { const parts = totalSupply.split('e+'); const base = parts[0]; const exponent = parseInt(parts[1]); let fullNumberStr = base.replace('.', ''); const decimalIndex = base.indexOf('.'); const zerosToAdd = exponent - (base.length - decimalIndex - 1); fullNumberStr += '0'.repeat(Math.max(0, zerosToAdd)); totalSupplyBigInt = BigInt(fullNumberStr); } else { totalSupplyBigInt = BigInt(totalSupply); } } catch (error) { console.error('Error converting totalSupply to BigInt:', error); totalSupplyBigInt = BigInt(totalSupplyResult); } let circulatingSupply; let circulatingSupplyPct; if (lockedSupply > BigInt(0) && lockedSupply < totalSupplyBigInt) { const circulatingSupplyBigInt = totalSupplyBigInt - lockedSupply; circulatingSupply = Number(circulatingSupplyBigInt); circulatingSupplyPct = Number(circulatingSupplyBigInt * BigInt(100) / totalSupplyBigInt); logger_1.logger.debug(`Calculated circulating supply: ${circulatingSupplyBigInt.toString()} (${circulatingSupplyPct.toFixed(2)}% of total)`); } else { circulatingSupply = Math.round(Number(totalSupply) * 0.7); circulatingSupplyPct = 70; logger_1.logger.debug(`Using estimated circulating supply: ${circulatingSupply} (70% of total)`); } const formattedCirculatingSupply = (0, formatters_1.formatTokenAmount)(circulatingSupply, decimals, ''); return { address: normalizedAddress, name, symbol, decimals, totalSupply, formattedTotalSupply: `${formattedTotalSupply} ${symbol}`, circulatingSupply, formattedCirculatingSupply: `${formattedCirculatingSupply} ${symbol}`, contractType: 'ERC-20', creator, creationTx, creationTimestamp, price: null, marketCap: null, volume24h: null, holdersCount, transfersCount }; } catch (error) { console.error('Error fetching token info:', error); throw error; } } /** * * @param {string} tokenAddress * @param {number} limit * @param {number} [page] * @returns {Promise<TokenTransfer[]>} * * @deprecated */