blockchain-api
Version:
API utilities for interacting with the Exatechl2 blockchain
422 lines (418 loc) • 13.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.standardizeTimestamp = standardizeTimestamp;
exports.parseBlockchainTimestamp = parseBlockchainTimestamp;
exports.parseTimestampToMillis = parseTimestampToMillis;
exports.calculateAgeInMillis = calculateAgeInMillis;
exports.formatRelativeTime = formatRelativeTime;
exports.formatTableTime = formatTableTime;
exports.formatNumber = formatNumber;
exports.formatWeiToEth = formatWeiToEth;
exports.truncateString = truncateString;
exports.formatAddress = formatAddress;
exports.formatTokenAmount = formatTokenAmount;
exports.parseHexToDecimal = parseHexToDecimal;
exports.formatGasValue = formatGasValue;
exports.calculateGasPercentage = calculateGasPercentage;
exports.formatDecimalToHex = formatDecimalToHex;
const logger_1 = require("./logger");
/**
*
* @param {string|number} timestamp
* @param {boolean} debug
* @returns {number}
*/
function standardizeTimestamp(timestamp, debug = false) {
if (timestamp === undefined || timestamp === null) {
if (debug)
logger_1.logger.debug('Timestamp undefined or null, using current time');
return Date.now();
}
const timestampNum = typeof timestamp === 'string' ? parseInt(timestamp, 10) : timestamp;
if (debug)
logger_1.logger.debug(`Standardizing timestamp: original=${timestamp}, parsed=${timestampNum}`);
if (timestampNum < 10000000000) {
const converted = timestampNum * 1000;
if (debug)
logger_1.logger.debug(`Converting seconds to milliseconds: ${timestampNum} → ${converted} (${new Date(converted).toISOString()})`);
return converted;
}
if (debug)
logger_1.logger.debug(`Timestamp already in milliseconds: ${timestampNum} (${new Date(timestampNum).toISOString()})`);
return timestampNum;
}
/**
*
* @param {string|number|Date} timestamp
* @returns {Date}
* @throws {Error}
*/
function parseBlockchainTimestamp(timestamp) {
if (!timestamp) {
throw new Error('Invalid timestamp: empty value');
}
let 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}
*/
function parseTimestampToMillis(timestamp, fallbackToNow = true) {
try {
const date = parseBlockchainTimestamp(timestamp);
return date.getTime();
}
catch (error) {
logger_1.logger.error('Error parsing timestamp:', error, timestamp);
return fallbackToNow ? Date.now() : 0;
}
}
/**
*
* @param {string|number|Date} timestamp
* @returns {number}
*/
function calculateAgeInMillis(timestamp) {
const timestampMs = parseTimestampToMillis(timestamp);
return Date.now() - timestampMs;
}
/**
*
* @param {string|number|Date} timestamp
* @returns {string}
*/
function formatRelativeTime(timestamp) {
if (!timestamp) {
return 'Unknown time';
}
let date;
try {
date = parseBlockchainTimestamp(timestamp);
}
catch (error) {
logger_1.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}
*/
function formatTableTime(timestamp) {
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_1.logger.error('Error parsing timestamp:', error, timestamp);
return 'Invalid date';
}
}
/**
*
* @param {number} value
* @param {number} decimals
* @returns {string}
*/
function formatNumber(value, decimals = 2) {
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}
*/
function formatWeiToEth(wei, decimals = 6) {
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_1.logger.error('Error formatting wei to tEXT:', e);
return '0 tEXT';
}
}
/**
*
* @param {string} str
* @param {number} frontChars
* @param {number} endChars
* @returns {string}
*/
function truncateString(str, frontChars = 6, endChars = 4) {
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}
*/
function formatAddress(address) {
if (!address)
return '';
return truncateString(address, 6, 4);
}
/**
*
* @param {string|number} rawAmount
* @param {number} decimals
* @param {string} symbol
* @returns {string}
*/
function formatTokenAmount(rawAmount, decimals = 18, symbol = '') {
if (!rawAmount)
return `0${symbol ? ' ' + symbol : ''}`;
let numericAmount;
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}
*/
function parseHexToDecimal(hexValue, defaultValue = 0) {
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_1.logger.error('Error parsing hex value:', error, hexValue);
return defaultValue;
}
}
/**
*
* @param {string|number} value
* @param {string} unit
* @param {number} decimals
* @returns {string}
*/
function formatGasValue(value, unit = 'gwei', decimals = 2) {
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_1.logger.error('Error formatting gas value:', error, value);
return 'N/A';
}
}
/**
*
* @param {string|number} gasUsed
* @param {string|number} gasLimit
* @returns {number}
*/
function calculateGasPercentage(gasUsed, gasLimit) {
const gasUsedDecimal = parseHexToDecimal(gasUsed);
const gasLimitDecimal = parseHexToDecimal(gasLimit);
if (gasLimitDecimal === 0) {
return 0;
}
return Math.round((gasUsedDecimal / gasLimitDecimal) * 100);
}
/**
*
* @param {number} value
* @returns {string}
*/
function formatDecimalToHex(value) {
return '0x' + value.toString(16);
}