UNPKG

brahma-trade-widget

Version:

A React component for trade automation within the Brahma ecosystem.

325 lines (275 loc) 8.97 kB
import { parseUnits as parseUnitsViem, formatUnits as formatUnitsViem, isAddress, Hex, Address, isHex, } from "viem" import { v4, V4Options } from "uuid" import { TAsset } from "@/types" import { USER_REJECTED_REQUEST_CODE } from "@/constants" export const formatUnits = (value: string | bigint, unitName = 18): string => { try { if (!value) return "0" if (typeof value === "bigint") return formatUnitsViem(value, unitName) return formatUnitsViem(BigInt(value), unitName) } catch (err) { console.error("[ERROR ON FORMAT UNITS]:", err) return "0" } } export const parseUnits = (value: string, unitName = 18): bigint => { return parseUnitsViem(value, unitName) } export const truncateString = (str: string, start = 6, end = 4) => { if (!str) return "" if (str.length <= start + end) { return str } const truncated = `${str.substring(0, start)}...${str.substring( str.length - end, )}` return truncated } export const formatNumberWithCommas = ( _input: string | number, ): string | number => { if (!_input) return _input const result = _input .toString() .replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ",") return result } export const sliceDecimalString = ( input: string, decimals = 6, addThousandsFormatting = false, ): string => { if (!input) return "0" const parts = input.split(".") if (parts.length === 1) { // Input string does not contain a decimal point return input } const integerPart = parts[0] let decimalPart = parts[1] decimalPart = decimalPart.slice(0, decimals) while (decimalPart.endsWith("0")) { // Remove trailing zeros decimalPart = decimalPart.slice(0, -1) } if (decimalPart.length === 0) { return addThousandsFormatting ? (formatNumberWithCommas(integerPart) as string) : integerPart } const result = `${integerPart}.${decimalPart}` if (addThousandsFormatting) { return formatNumberWithCommas(result) as string } return result } // This was defined by product team, order: verified, balanceOf, alphabetical export function sortAssets(assets: TAsset[]): TAsset[] { return assets.sort((a, b) => { if (a.verified && !b.verified) { return -1 } else if (!a.verified && b.verified) { return 1 } else if (a.balanceOf && b.balanceOf) { const balanceA = parseFloat(a.balanceOf.formatted) const balanceB = parseFloat(b.balanceOf.formatted) return balanceB - balanceA } else if (a.balanceOf && !b.balanceOf) { return -1 } else if (!a.balanceOf && b.balanceOf) { return 1 } else { return a.name.localeCompare(b.name) } }) } export const minifyCurrencyValue = (value: string | number) => { // remove $ and commas if the value is a string const numericValue = typeof value === "string" ? parseFloat(value.replace(/\$|,/g, "")) : value if (isNaN(numericValue)) { return "Invalid value" } // just format if 1e3 and minify if above 1k if (numericValue < 1e3) { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 2, maximumFractionDigits: 2, }).format(numericValue) } else if (numericValue < 1e6) { return "$" + (numericValue / 1e3).toFixed(1) + "k" } else if (numericValue < 1e9) { return "$" + (numericValue / 1e6).toFixed(1) + "M" } else if (numericValue < 1e12) { return "$" + (numericValue / 1e9).toFixed(1) + "B" } else { return "$" + (numericValue / 1e12).toFixed(1) + "T" } } export const openInNewTab = (href: string) => { window.open(href, "_blank", "noreferrer noopener") } export const openInSameTab = (href: string) => { window.location.href = href } export const copyToClipboard = async (text?: string) => { try { const toCopy = text || "" await navigator.clipboard.writeText(toCopy) } catch (err) { console.error("Failed to copy: ", err) } } export const generateUUID = (options?: V4Options) => { return v4(options) } export const formatRejectMetamaskErrorMessage = (err: any) => { return err?.code === USER_REJECTED_REQUEST_CODE || err?.code === "ACTION_REJECTED" ? "Transaction not signed. If this was an error, please attempt to sign again." : err?.mesage } export const isNotZero = (value: string) => { const trimmedValue = value.trim() // Check if the string is empty or contains only zeros or decimal points const regex = /^0*\.?0*$/ return !regex.test(trimmedValue) } export const isZero = (value: string) => !isNotZero(value) function detectJsonObjects(input: string): object[] { const jsonObjects: object[] = [] const jsonRegex = /\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\}/g // Remove comments from JSON (single-line and multi-line) const cleanedInput = input.replace(/\/\/.*|\/\*[\s\S]*?\*\//g, "") let match: RegExpExecArray | null while ((match = jsonRegex.exec(cleanedInput)) !== null) { try { const jsonObject = JSON.parse(match[0]) jsonObjects.push(jsonObject) } catch (error) { // Ignore invalid JSON matches } } return jsonObjects } export const extractObjectFromAiResponse = ( aiMessage: string, keys: string[], ): Record<string, any> | null => { try { const jsonMatchesData = detectJsonObjects(aiMessage).map((obj) => ({ json: obj, keys: Object.keys(obj), })) // Find the object whose keys exactly match the provided keys (order-independent) for (const jsonMatch of jsonMatchesData) { const objJson = jsonMatch?.json || "" const objKeys = jsonMatch?.keys || [] if ( objKeys.length === keys.length && objKeys.every((key) => keys.includes(key)) ) { return objJson } } return null // Return null if no matching object is found } catch (e) { console.error("Error parsing AI message:", e) return null } } export const removeMatchingJSON = (message: string, jsonKey: string[]) => { // Split the message into parts based on JSON objects const parts = message.split(/(\{[^}]+\})/g) // Filter out the parts that are JSON objects and match the keys to remove const filteredParts = parts.filter((part) => { try { const obj = JSON.parse(part) return !jsonKey.every((key) => key in obj) } catch (e) { return true // Keep non-JSON parts } }) // Join the filtered parts back into a single message return filteredParts.join("") } export const formatAmountWithOptions = ( amount: number | string, options?: Intl.NumberFormatOptions, ) => { const stringifiedAmount = amount.toString() const amountInNumber = parseFloat(stringifiedAmount) if (isNaN(amountInNumber)) return stringifiedAmount return amountInNumber.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2, // style: 'currency', // currency: 'USD', notation: "compact", compactDisplay: "short", signDisplay: "never", ...(options ? options : {}), }) } export const isValidEthereumAddress = (address: string): boolean => { return isAddress(address) } export const removeSectionAndJson = ( message: string, stringToRemove: string, ) => { const index = message.indexOf(stringToRemove) if (index !== -1) { return message.substring(0, index) } return message } export const formatHours = (hours: number) => { const days = Math.floor(hours / 24) const remainingHours = hours % 24 return { day: days > 0 ? `${days}` : "", hour: `${remainingHours}`, seconds: `${hours * 60 * 60}`, } } export function convertToFullString(value: string | number): string { const numberValue = typeof value === "string" ? Number(value) : value return numberValue.toLocaleString("fullwide", { useGrouping: false, }) } // Utility function to check if a string is a valid hex const isValidHex = (value: string) => isHex(value, { strict: true }) export const isHexMatch = ( hexOrAddress1: Hex | Address | undefined, hexOrAddress2: Hex | Address | undefined, ): boolean => { if (!hexOrAddress1 || !hexOrAddress2) return false // Trim whitespace and ensure lowercase for case-insensitive comparison const normalize = (value: string) => value.trim().toLowerCase() // Ensure both inputs are valid hex strings if (!isValidHex(hexOrAddress1) || !isValidHex(hexOrAddress2)) return false // Normalize inputs const normalized1 = normalize(hexOrAddress1) const normalized2 = normalize(hexOrAddress2) // If both are already 42-character Ethereum addresses, compare directly if (normalized1.length === 42 && normalized2.length === 42) return normalized1 === normalized2 // Extract last 40 characters (20-byte address from hex) and normalize const extractAddress = (hex: string) => hex.length >= 42 ? `0x${hex.slice(-40)}` : hex return ( normalize(extractAddress(normalized1)) === normalize(extractAddress(normalized2)) ) }