import type { ClipboardEvent } from 'react' ; import BigNumber from 'bignumber.js' ; import { KEBAB_REGEX , Network } from '@shared/constants' ; import { StacksNetwork } from '@stacks/network' ; import { BufferReader , deserializePostCondition , PostCondition } from '@stacks/transactions' ; function kebabCase ( str ) { return str . replace ( KEBAB_REGEX , match => '-' + match . toLowerCase ( ) ) ; } export function extractPhraseFromString ( value ) { const clean = value . trim ( ) ; const words = clean . match ( /\S+/g ) ; if ( words ?. length ) { return words . map ( word => ( word . match ( /[^0-9]+/g ) ? word : null ) ) . filter ( Boolean ) . join ( ' ' ) ; } else { return clean ; } } export function extractPhraseFromPasteEvent ( event ) { const pasted = event . clipboardData . getData ( 'Text' ) ; return extractPhraseFromString ( pasted ) ; } export function validateAndCleanRecoveryInput ( value ) { const cleaned = value . trim ( ) ; let cleanedEncrypted = cleaned . replace ( /\s/gm , '' ) ; const isPossibleRecoveryKey = /^[a-zA-Z0-9\+\/]+=?$/ . test ( cleanedEncrypted ) ; if ( isPossibleRecoveryKey && cleanedEncrypted . slice ( - 1 ) !== '=' ) { cleanedEncrypted = `${ cleanedEncrypted }=` ; } if ( cleanedEncrypted . length >= 108 ) { return { isValid : true , value : cleanedEncrypted , } ; } return { isValid : false , value } ; } export const hasLineReturn = ( input ) => input . includes ( '\n' ) ; export function makeTxExplorerLink ( txid , chain , suffix = '' ) { return `https://explorer.stacks.co/txid/${ txid }?chain=${ chain }${ suffix }` ; } export function truncateString ( str , maxLength ) { if ( str . length <= maxLength ) { return str ; } return str . slice ( 0 , maxLength ) + '...' ; } function getLetters ( string , offset = 1 ) { return string . slice ( 0 , offset ) ; } export function getTicker ( value ) { let name = kebabCase ( value ) ; if ( name . includes ( '-' ) ) { const words = name . split ( '-' ) ; if ( words . length >= 3 ) { name = `${ getLetters ( words [ 0 ] ) }${ getLetters ( words [ 1 ] ) }${ getLetters ( words [ 2 ] ) }` ; } else { name = `${ getLetters ( words [ 0 ] ) }${ getLetters ( words [ 1 ] , 2 ) }` ; } } else if ( name . length >= 3 ) { name = `${ getLetters ( name , 3 ) }` ; } return name . toUpperCase ( ) ; } export function postConditionFromString ( postCondition ) { const reader = BufferReader . fromBuffer ( Buffer . from ( postCondition , 'hex' ) ) ; return deserializePostCondition ( reader ) ; } function isUtf8 ( buf ? ) { if ( ! buf ) { return false ; } let i = 0 ; const len = buf . length ; while ( i < len ) { if ( buf [ i ] <= 0x7f ) { i ++ ; continue ; } if ( buf [ i ] >= 0xc2 && buf [ i ] <= 0xdf ) { if ( buf [ i + 1 ] >> 6 === 2 ) { i += 2 ; continue ; } else { return false ; } } if ( ( ( buf [ i ] === 0xe0 && buf [ i + 1 ] >= 0xa0 && buf [ i + 1 ] <= 0xbf ) || ( buf [ i ] === 0xed && buf [ i + 1 ] >= 0x80 && buf [ i + 1 ] <= 0x9f ) ) && buf [ i + 2 ] >> 6 === 2 ) { i += 3 ; continue ; } if ( ( ( buf [ i ] >= 0xe1 && buf [ i ] <= 0xec ) || ( buf [ i ] >= 0xee && buf [ i ] <= 0xef ) ) && buf [ i + 1 ] >> 6 === 2 && buf [ i + 2 ] >> 6 === 2 ) { i += 3 ; continue ; } if ( ( ( buf [ i ] === 0xf0 && buf [ i + 1 ] >= 0x90 && buf [ i + 1 ] <= 0xbf ) || ( buf [ i ] >= 0xf1 && buf [ i ] <= 0xf3 && buf [ i + 1 ] >> 6 === 2 ) || ( buf [ i ] === 0xf4 && buf [ i + 1 ] >= 0x80 && buf [ i + 1 ] <= 0x8f ) ) && buf [ i + 2 ] >> 6 === 2 && buf [ i + 3 ] >> 6 === 2 ) { i += 4 ; continue ; } return false ; } return true ; } export const abbreviateNumber = ( n ) => { if ( n < 1e3 ) return n . toString ( ) ; if ( n >= 1e3 && n < 1e6 ) return + ( n / 1e3 ) . toFixed ( 2 ) + 'K' ; if ( n >= 1e6 && n < 1e9 ) return + ( n / 1e6 ) . toFixed ( 2 ) + 'M' ; if ( n >= 1e9 && n < 1e12 ) return + ( n / 1e9 ) . toFixed ( 2 ) + 'B' ; if ( n >= 1e12 ) return + ( n / 1e12 ) . toFixed ( 2 ) + 'T' ; return n . toString ( ) ; } ; function isHex ( hex ) { const regexp = /^[0-9a-fA-F]+$/ ; return regexp . test ( hex ) ; } function cleanHex ( hexWithMaybePrefix ) { if ( hexWithMaybePrefix !== 'string' ) return hexWithMaybePrefix ; return hexWithMaybePrefix . startsWith ( '0x' ) ? hexWithMaybePrefix . replace ( '0x' , '' ) : hexWithMaybePrefix ; } export function hexToBuff ( hex ) { return Buffer . from ( cleanHex ( hex ) , 'hex' ) ; } export function hexToHumanReadable ( hex ) { if ( ! isHex ( hex ) ) return hex ; const buff = hexToBuff ( hex ) ; if ( isUtf8 ( buff ) ) return buff . toString ( 'utf8' ) ; return `0x${ hex }` ; } function getCoreApiUrl ( network ) { if ( network . coreApiUrl ) return network . coreApiUrl ; if ( ( network as any ) . _coreApiUrl ) return ( network as any ) . _coreApiUrl ; } export function findMatchingNetworkKey ( txNetwork , networks ) { if ( ! networks || ! txNetwork ) return ; const developerDefinedApiUrl = getCoreApiUrl ( txNetwork ) ; const keys = Object . keys ( networks ) ; const exactUrlMatch = keys . find ( ( key ) => { const network = networks [ key ] as Network ; return network . url === developerDefinedApiUrl ; } ) ; if ( exactUrlMatch ) return exactUrlMatch ; const chainIdMatch = keys . find ( ( key ) => { const network = networks [ key ] as Network ; return network . url === developerDefinedApiUrl || network . chainId === txNetwork ?. chainId ; } ) ; if ( chainIdMatch ) return chainIdMatch ; return null ; } export const slugify = ( ... args ) : string => { const value = args . join ( ' ' ) ; return value . normalize ( 'NFD' ) . replace ( /[\u0300-\u036f]/g , '' ) . toLowerCase ( ) . trim ( ) . replace ( /[^a-z0-9 ]/g , '' ) . replace ( /\s+/g , '-' ) ; } ; export function getUrlHostname ( url ) { return new URL ( url ) . hostname ; } export function getUrlPort ( url ) { return new URL ( url ) . port ; } export async function delay ( ms ) { return new Promise ( resolve => setTimeout ( resolve , ms ) ) ; } export function with0x ( value ) { return ! value . startsWith ( '0x' ) ? `0x${ value }` : value ; } export function initBigNumber ( num ) { return BigNumber . isBigNumber ( num ) ? num : new BigNumber ( num ) ; } export function countDecimals ( num ) { const LARGE_NUMBER_OF_DECIMALS = 100 ; BigNumber . config ( { DECIMAL_PLACES : LARGE_NUMBER_OF_DECIMALS } ) ; const amount = initBigNumber ( num ) ; const decimals = amount . toString ( 10 ) . split ( '.' ) [ 1 ] ; return decimals ? decimals . length : 0 ; } export function isNumber ( value ) { return typeof value === 'number' ; } export function isString ( value ) { return typeof value === 'string' ; } export function isEmpty ( value ) { return Object . keys ( value ) . length === 0 ; } type ViewMode = 'full' | 'popup' ; export function getViewMode ( ) { return document . location . pathname . startsWith ( '/index.html' ) ? 'full' : 'popup' ; }