@shogun-sdk/money-legos
Version:
Shogun Money Legos: clients and types for quotes, memes, prices, balances, fees, validations, etc.
268 lines (224 loc) • 9.04 kB
text/typescript
export const formatNetworkName = (network: string): string => {
return network.charAt(0).toUpperCase() + network.slice(1);
};
export const toUpperCase = (stringInput: string) => {
return stringInput.toUpperCase();
};
export const formatNumber = (number: number, decPlaces: number = 1): string => {
const units = ['K', 'M', 'B', 'T']; // Thousand, Million, Billion, Trillion
const isNegative = number < 0;
const absNumber = Math.abs(number);
// Handle numbers less than 1, showing 4 decimal places
if (absNumber < 1) {
return `${isNegative ? '-' : ''}${absNumber.toFixed(4)}`;
}
// Handle numbers between 1 and 999, displaying with two decimal places
if (absNumber < 1000) {
return `${isNegative ? '-' : ''}${formatNumberWithThreeDecimalPlaces(absNumber)}`;
}
// Handle numbers >= 1000 and < 1 million (abbreviating as 'K')
if (absNumber >= 1000 && absNumber < 1e6) {
const rounded = Math.floor(absNumber / 100) / 10; // Round to nearest 100
return `${isNegative ? '-' : ''}${rounded}${units[0]}`;
}
// Handle numbers >= 1 million and < 1 billion (abbreviating as 'M')
if (absNumber >= 1e6 && absNumber < 1e9) {
const rounded = Math.floor(absNumber / 1e4) / 100; // Round to nearest 10,000
return `${isNegative ? '-' : ''}${rounded}${units[1]}`;
}
// Handle numbers >= 1 billion and < 1 trillion (abbreviating as 'B')
if (absNumber >= 1e9 && absNumber < 1e12) {
const rounded = Math.floor(absNumber / 1e7) / 100; // Round to nearest 10,000,000
return `${isNegative ? '-' : ''}${rounded}${units[2]}`;
}
// Handle numbers >= 1 trillion (abbreviating as 'T')
if (absNumber >= 1e12) {
const rounded = Math.floor(absNumber / 1e10) / 100; // Round to nearest 10,000,000,000
return `${isNegative ? '-' : ''}${rounded}${units[3]}`;
}
// Default to showing number with decimals (if larger than trillion)
return `${isNegative ? '-' : ''}${absNumber.toFixed(decPlaces)}`;
};
/**
* Capitalizes specific chain names or returns the original name
* @param name - The chain name to process (e.g. 'Bsc' becomes 'BSC')
* @returns The processed chain name
*/
const formatBlockchainName = (chainName: string): string => {
if (!chainName) {
return '';
}
const normalizedChainName = chainName.toLowerCase();
if (['bsc'].includes(normalizedChainName)) {
return chainName.toUpperCase();
}
return chainName;
};
export const capitalizeFirstLetter = (str: string) => {
return formatBlockchainName(str.charAt(0).toUpperCase() + str.slice(1));
};
export const formatEthUiBalance = (balance: string, symbol?: string): string => {
return `${Number(balance) === 0 ? '0' : Number(balance).toFixed(4)} ${symbol ? symbol : 'ETH'}`;
};
export const formatPriceInEth = (price: string): string => {
// Handle zero or empty input
if (!price || Number(price) === 0) {
return '0';
}
const priceFormatted = Number(price).toFixed(30);
const [integerPart, decimalPart] = priceFormatted.split('.'); // Get the integer and decimal parts
const leadingZerosCount = decimalPart?.match(/^0*/)?.[0]?.length || 0; // Count leading zeros
let formattedPrice = integerPart;
const significantPart = decimalPart?.substring(leadingZerosCount); // Get the significant part after leading zeros
// Build the formatted price string
if (leadingZerosCount <= 2) {
formattedPrice += `.${`0`.repeat(leadingZerosCount)}${significantPart?.substring(0, 4)}`;
} else {
formattedPrice += `.0${subscriptDigit(leadingZerosCount)}${significantPart?.substring(0, 4)}`;
}
return formattedPrice ?? '0';
};
export const formatMaxBuyNativeAmount = (amount: number): string => {
const amountFormatted = amount.toFixed(30);
const [integerPart, decimalPart] = amountFormatted.split('.'); // Get the integer and decimal parts
const leadingZerosCount = decimalPart?.match(/^0*/)?.[0]?.length || 0; // Count leading zeros
let formattedPrice = integerPart;
const significantPart = decimalPart?.substring(leadingZerosCount); // Get the significant part after leading zeros
formattedPrice += `.${`0`.repeat(leadingZerosCount)}${significantPart?.substring(0, 2)}`;
return formattedPrice ?? '0';
};
export function formatNumberWithThreeDecimalPlaces(num: number) {
if (Math.abs(num) < 0.0001) {
return '0';
}
return num.toFixed(4).replace(/\.?0*$/, '');
}
export const subscriptDigit = (digit: number): string => {
const subscriptMap: { [key: number]: string } = {
0: '₀',
1: '₁',
2: '₂',
3: '₃',
4: '₄',
5: '₅',
6: '₆',
7: '₇',
8: '₈',
9: '₉',
10: '₁₀',
11: '₁₁',
12: '₁₂',
13: '₁₃',
14: '₁₄',
15: '₁₅',
16: '₁₆',
17: '₁₇',
18: '₁₈',
19: '₁₉',
};
return subscriptMap[digit] || digit.toString();
};
export const formatSupplyPercentageOwned = (tokenSupplyOwned: number): string => {
if (tokenSupplyOwned === 0) {
return '0%';
}
if (tokenSupplyOwned < 0.01) {
return '< 0.01 %';
}
return `${tokenSupplyOwned.toFixed(2)}%`;
};
export const convertNumbThousand = (x?: number): string => {
if (!x) {
return '0';
}
return x.toLocaleString('en-US');
};
export function lowercase(input: string): string {
return String(input).toLowerCase();
}
export const formatPnl = (pnl: string) => {
if (pnl === 'N/A') return `PNL: N/A`;
let pnlFormatted = formatNumberWithThreeDecimalPlaces(Number(pnl));
if (pnlFormatted === '0') pnlFormatted = '0.00';
return `PNL: ${Number(pnlFormatted) >= 0 ? '🟢' : '🔴'} <b>${pnlFormatted}%</b>`;
};
export const formatChangeXH = (change: number, h: string) => {
let changeFormatted = change.toFixed(3);
if (changeFormatted === '0') changeFormatted = '0.00';
return `${h}h: ${Number(changeFormatted) >= 0 ? '🟢' : '🔴'} <b>${changeFormatted}%</b>`;
};
export const formatAvgEntry = (avgEntry: string) => {
if (avgEntry === 'N/A') return avgEntry;
const avgEntryFormatted = formatPriceInEth(Number(avgEntry).toFixed(30));
return `$${avgEntryFormatted}`;
};
export const formatTimeAgo = (timestamp: number): string => {
const now = Date.now();
const diff = now - timestamp * 1000;
const minutes = Math.floor(diff / 60000);
const hours = Math.floor(diff / 3600000);
const days = Math.floor(diff / 86400000);
const weeks = Math.floor(days / 7);
const months = Math.floor(days / 30);
const years = Math.floor(days / 365);
if (years > 0) return `${years} ${years === 1 ? 'year' : 'years'} ago`;
if (months > 0) return `${months} ${months === 1 ? 'month' : 'months'} ago`;
if (weeks > 0) return `${weeks} ${weeks === 1 ? 'week' : 'weeks'} ago`;
if (days > 0) return `${days} ${days === 1 ? 'day' : 'days'} ago`;
if (hours > 0) return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`;
if (minutes > 0) return `${minutes} ${minutes === 1 ? 'minute' : 'minutes'} ago`;
return 'less than a minute ago';
};
/**
* Normalizes a decimal number string by limiting the number of decimal places.
* This helper prevents numeric overflow/underflow errors when dealing with high-precision decimals.
*
* @param value - The decimal number as a string (e.g., "0.15947712418300652")
* @returns A string with the normalized decimal number
*
* Examples:
* normalizeDecimals("0.15947712418300652") => "0.15947712"
* normalizeDecimals("1.23", 4) => "1.23"
* normalizeDecimals("123") => "123"
* normalizeDecimals("0.1234", 2) => "0.12"
*
*/
export function normalizeDecimals(value: string, decimals: number = 18): string {
if (!value.includes('.')) return value;
const [whole, fraction] = value.split('.');
return `${whole}.${fraction?.slice(0, decimals) ?? ''}`;
}
export function bigIntReplacer<T>(_key: string, value: T): T {
return typeof value === 'bigint' ? (value.toString() as T) : value;
}
export function getFormattedObject(object: object): string {
return JSON.stringify(object, bigIntReplacer, 2);
}
export function formatUSD(amount: string | number): string {
// Convert to number and handle potential parsing errors
const numAmount = typeof amount === 'string' ? parseFloat(amount.replace(/[^0-9.-]/g, '')) : amount;
// Check for invalid input
if (isNaN(numAmount)) {
return '0';
}
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
maximumFractionDigits: 2,
minimumFractionDigits: 0,
}).format(numAmount);
}
export function getUserIdLog(userId: number | string): string {
return `[${userId}]`;
}
export function getFormattedCurrentMethod(userId: number | string, methodName: string, instance?: object): string {
return `${getUserIdLog(userId)} >> ${instance?.constructor?.name ? instance?.constructor?.name + '.' : ''}${methodName}`;
}
// replace last 9 digits with 0s
export function formatTokenAmount(amount: string | number): string {
const strAmount = amount.toString();
if (strAmount.length <= 9) {
return strAmount;
}
return strAmount.slice(0, -9) + '0'.repeat(9);
}