UNPKG

blockchain-api

Version:

API utilities for interacting with the Exatechl2 blockchain

442 lines (366 loc) 12 kB
import { logger } from './logger'; /** * * @param {string|number} timestamp * @param {boolean} debug * @returns {number} */ export function standardizeTimestamp(timestamp: string | number, debug = false): number { if (timestamp === undefined || timestamp === null) { if (debug) logger.debug('Timestamp undefined or null, using current time'); return Date.now(); } const timestampNum = typeof timestamp === 'string' ? parseInt(timestamp, 10) : timestamp; if (debug) logger.debug(`Standardizing timestamp: original=${timestamp}, parsed=${timestampNum}`); if (timestampNum < 10000000000) { const converted = timestampNum * 1000; if (debug) logger.debug(`Converting seconds to milliseconds: ${timestampNum}${converted} (${new Date(converted).toISOString()})`); return converted; } if (debug) logger.debug(`Timestamp already in milliseconds: ${timestampNum} (${new Date(timestampNum).toISOString()})`); return timestampNum; } /** * * @param {string|number|Date} timestamp * @returns {Date} * @throws {Error} */ export function parseBlockchainTimestamp(timestamp: string | number | Date): Date { if (!timestamp) { throw new Error('Invalid timestamp: empty value'); } let date: Date; if (timestamp instanceof Date) { date = timestamp; } else if (typeof timestamp === 'string') { if (timestamp.startsWith('0x')) { const seconds = parseInt(timestamp, 16); date = new Date(seconds * 1000); } else if (/^\d+$/.test(timestamp)) { const num = parseInt(timestamp, 10); if (num < 10000000000) { date = new Date(num * 1000); } else { date = new Date(num); } } else if (timestamp.includes('T') && timestamp.includes('-')) { date = new Date(timestamp); } else if (timestamp.includes('/') || timestamp.includes('-')) { date = new Date(timestamp); } else { date = new Date(timestamp); } } else { if (timestamp < 10000000000) { date = new Date(timestamp * 1000); } else { date = new Date(timestamp); } } if (isNaN(date.getTime())) { throw new Error(`Invalid timestamp: could not parse date from ${JSON.stringify(timestamp)}`); } return date; } /** * * @param {string|number|Date} timestamp * @param {boolean} fallbackToNow * @returns {number} */ export function parseTimestampToMillis(timestamp: string | number | Date, fallbackToNow = true): number { try { const date = parseBlockchainTimestamp(timestamp); return date.getTime(); } catch (error) { logger.error('Error parsing timestamp:', error, timestamp); return fallbackToNow ? Date.now() : 0; } } /** * * @param {string|number|Date} timestamp * @returns {number} */ export function calculateAgeInMillis(timestamp: string | number | Date): number { const timestampMs = parseTimestampToMillis(timestamp); return Date.now() - timestampMs; } /** * * @param {string|number|Date} timestamp * @returns {string} */ export function formatRelativeTime(timestamp: string | number | Date): string { if (!timestamp) { return 'Unknown time'; } let date: Date; try { date = parseBlockchainTimestamp(timestamp); } catch (error) { logger.error('Error parsing timestamp:', error, timestamp); return 'Invalid date'; } const now = new Date(); const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); if (isNaN(diffInSeconds)) { return 'Invalid date'; } let relativeTime = ''; if (diffInSeconds < 5) { relativeTime = 'Just now'; } else if (diffInSeconds < 60) { relativeTime = `${diffInSeconds} ${diffInSeconds === 1 ? 'sec' : 'secs'} ago`; } else { const diffInMinutes = Math.floor(diffInSeconds / 60); if (diffInMinutes < 60) { relativeTime = `${diffInMinutes} ${diffInMinutes === 1 ? 'min' : 'mins'} ago`; } else { const diffInHours = Math.floor(diffInMinutes / 60); if (diffInHours < 24) { relativeTime = `${diffInHours} ${diffInHours === 1 ? 'hour' : 'hours'} ago`; } else { const diffInDays = Math.floor(diffInHours / 24); if (diffInDays < 30) { relativeTime = `${diffInDays} ${diffInDays === 1 ? 'day' : 'days'} ago`; } else { const diffInMonths = Math.floor(diffInDays / 30); if (diffInMonths < 12) { relativeTime = `${diffInMonths} ${diffInMonths === 1 ? 'month' : 'months'} ago`; } else { const diffInYears = Math.floor(diffInMonths / 12); relativeTime = `${diffInYears} ${diffInYears === 1 ? 'year' : 'years'} ago`; } } } } } const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const year = date.getUTCFullYear(); const month = months[date.getUTCMonth()]; const day = date.getUTCDate().toString().padStart(2, '0'); let hours = date.getUTCHours(); const ampm = hours >= 12 ? 'PM' : 'AM'; hours = hours % 12; hours = hours ? hours : 12; const minutes = date.getUTCMinutes().toString().padStart(2, '0'); const seconds = date.getUTCSeconds().toString().padStart(2, '0'); const utcTime = `${month}-${day}-${year} ${hours.toString().padStart(2, '0')}:${minutes}:${seconds} ${ampm} +UTC`; return `${relativeTime} (${utcTime})`; } /** * @param {string|number|Date} timestamp * @returns {string} */ export function formatTableTime(timestamp: string | number | Date): string { if (!timestamp) { return 'Unknown time'; } try { const date = parseBlockchainTimestamp(timestamp); const now = new Date(); const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); if (isNaN(diffInSeconds)) { return 'Invalid date'; } if (diffInSeconds < 5) { return 'Just now'; } if (diffInSeconds < 60) { return `${diffInSeconds} ${diffInSeconds === 1 ? 'sec' : 'secs'} ago`; } const diffInMinutes = Math.floor(diffInSeconds / 60); if (diffInMinutes < 60) { return `${diffInMinutes} ${diffInMinutes === 1 ? 'min' : 'mins'} ago`; } const diffInHours = Math.floor(diffInMinutes / 60); if (diffInHours < 24) { return `${diffInHours} ${diffInHours === 1 ? 'hour' : 'hours'} ago`; } const diffInDays = Math.floor(diffInHours / 24); if (diffInDays < 30) { return `${diffInDays} ${diffInDays === 1 ? 'day' : 'days'} ago`; } const diffInMonths = Math.floor(diffInDays / 30); if (diffInMonths < 12) { return `${diffInMonths} ${diffInMonths === 1 ? 'month' : 'months'} ago`; } const diffInYears = Math.floor(diffInMonths / 12); return `${diffInYears} ${diffInYears === 1 ? 'year' : 'years'} ago`; } catch (error) { logger.error('Error parsing timestamp:', error, timestamp); return 'Invalid date'; } } /** * * @param {number} value * @param {number} decimals * @returns {string} */ export function formatNumber(value: number, decimals = 2): string { if (!value && value !== 0) return 'N/A'; if (value === 0) return '0'; if (Math.abs(value) < 1000) { return value.toFixed(decimals).replace(/\.0+$/, ''); } const suffixes = ['', 'K', 'M', 'B', 'T']; const suffixIndex = Math.floor(Math.log10(Math.abs(value)) / 3); if (suffixIndex >= suffixes.length) { return value.toExponential(decimals); } const scaledValue = value / Math.pow(10, suffixIndex * 3); return `${scaledValue.toFixed(decimals).replace(/\.0+$/, '')}${suffixes[suffixIndex]}`; } /** * * @param {string|number} wei * @param {number} decimals * @returns {string} */ export function formatWeiToEth(wei: string | number, decimals = 6): string { if (!wei) return '0 tEXT'; try { const weiValue = typeof wei === 'string' && wei.startsWith('0x') ? parseInt(wei, 16) : Number(wei); const ethValue = weiValue / 1e18; if (ethValue === Math.floor(ethValue)) { return `${Math.floor(ethValue)} tEXT`; } const formatted = ethValue.toFixed(decimals); const trimmed = formatted.replace(/\.?0+$/, ''); return `${trimmed} tEXT`; } catch (e) { logger.error('Error formatting wei to tEXT:', e); return '0 tEXT'; } } /** * * @param {string} str * @param {number} frontChars * @param {number} endChars * @returns {string} */ export function truncateString(str: string, frontChars = 6, endChars = 4): string { if (!str) return ''; if (str.length <= frontChars + endChars) return str; return `${str.substring(0, frontChars)}...${str.substring(str.length - endChars)}`; } /** * * @param {string} address * @returns {string} */ export function formatAddress(address: string): string { if (!address) return ''; return truncateString(address, 6, 4); } /** * * @param {string|number} rawAmount * @param {number} decimals * @param {string} symbol * @returns {string} */ export function formatTokenAmount(rawAmount: string | number, decimals: number = 18, symbol: string = ''): string { if (!rawAmount) return `0${symbol ? ' ' + symbol : ''}`; let numericAmount: number; if (typeof rawAmount === 'string') { if (rawAmount.startsWith('0x')) { numericAmount = parseInt(rawAmount, 16) / Math.pow(10, decimals); } else { numericAmount = Number(rawAmount) / Math.pow(10, decimals); } } else { numericAmount = rawAmount / Math.pow(10, decimals); } return `${numericAmount.toLocaleString(undefined, { maximumFractionDigits: numericAmount > 1000 ? 0 : 2, minimumFractionDigits: 0 })}${symbol ? ' ' + symbol : ''}`; } /** * * @param {string|number} hexValue * @param {number} defaultValue * @returns {number} */ export function parseHexToDecimal(hexValue: string | number | undefined, defaultValue = 0): number { try { if (hexValue === undefined || hexValue === null) { return defaultValue; } if (typeof hexValue === 'number') { return hexValue; } if (typeof hexValue === 'string') { if (hexValue.startsWith('0x')) { return parseInt(hexValue, 16); } return parseInt(hexValue, 10); } return defaultValue; } catch (error) { logger.error('Error parsing hex value:', error, hexValue); return defaultValue; } } /** * * @param {string|number} value * @param {string} unit * @param {number} decimals * @returns {string} */ export function formatGasValue(value: string | number | undefined, unit = 'gwei', decimals = 2): string { try { if (value === undefined || value === null) { return 'N/A'; } const weiValue = parseHexToDecimal(value); if (weiValue === 0) { return `0 ${unit.toUpperCase()}`; } let convertedValue = weiValue; let unitLabel = unit.toUpperCase(); if (unit.toLowerCase() === 'gwei') { convertedValue = weiValue / 1e9; unitLabel = 'Gwei'; } else if (unit.toLowerCase() === 'text') { convertedValue = weiValue / 1e18; unitLabel = 'TEXT'; } else { unitLabel = 'Wei'; } return `${convertedValue.toFixed(decimals).replace(/\.0+$/, '')} ${unitLabel}`; } catch (error) { logger.error('Error formatting gas value:', error, value); return 'N/A'; } } /** * * @param {string|number} gasUsed * @param {string|number} gasLimit * @returns {number} */ export function calculateGasPercentage(gasUsed: string | number | undefined, gasLimit: string | number | undefined): number { const gasUsedDecimal = parseHexToDecimal(gasUsed); const gasLimitDecimal = parseHexToDecimal(gasLimit); if (gasLimitDecimal === 0) { return 0; } return Math.round((gasUsedDecimal / gasLimitDecimal) * 100); } /** * * @param {number} value * @returns {string} */ export function formatDecimalToHex(value: number): string { return '0x' + value.toString(16); }