import { BigNumber } from 'bignumber.js' ; import { TransactionTypes } from '@stacks/connect' ; import { AuthType , ChainID , StacksTransaction } from '@stacks/transactions' ; import { AddressTransactionWithTransfers , CoinbaseTransaction , MempoolTransaction , Transaction , TransactionEventFungibleAsset , } from '@stacks/stacks-blockchain-api-types' ; import { getContractName , truncateMiddle } from '@stacks/ui-utils' ; import { AssetWithMeta } from '@app/common/asset-types' ; import { DEFAULT_FEE_RATE } from '@shared/constants' ; import { displayDate , isoDateToLocalDateSafe , todaysIsoDate } from '@app/common/date-utils' ; import { stacksValue } from '@app/common/stacks-utils' ; import { FeeEstimation } from '@shared/models/fees-types' ; type Tx = MempoolTransaction | Transaction ; export interface TransactionFormValues { amount : number | string ; fee : number | string ; recipient : string ; memo : string ; } export interface StxTransfer { amount : string ; sender ? : string ; recipient ? : string ; } export interface FtTransfer { asset_identifier : string ; amount : string ; sender ? : string ; recipient ? : string ; } export const stacksTransactionToHex = ( transaction ) => `0x${ transaction . serialize ( ) . toString ( 'hex' ) }` ; function txHasTime ( tx ) { return ! ! ( ( 'burn_block_time_iso' in tx && tx . burn_block_time_iso ) || ( 'parent_burn_block_time_iso' in tx && tx . parent_burn_block_time_iso ) ) ; } const maxValueForLowFeeEstimation = 500000 ; const maxValueForMiddleFeeEstimation = 750000 ; const maxValueForHighFeeEstimation = 2000000 ; export function getFeeEstimationsWithMaxValues ( feeEstimations ) { const feeEstimationsWithMaxValues = [ ] ; if ( new BigNumber ( feeEstimations [ 0 ] . fee ) . isGreaterThan ( maxValueForLowFeeEstimation ) ) { feeEstimationsWithMaxValues . push ( { fee : maxValueForLowFeeEstimation , fee_rate : 0 } ) ; } else { feeEstimationsWithMaxValues . push ( feeEstimations [ 0 ] ) ; } if ( new BigNumber ( feeEstimations [ 1 ] . fee ) . isGreaterThan ( maxValueForMiddleFeeEstimation ) ) { feeEstimationsWithMaxValues . push ( { fee : maxValueForMiddleFeeEstimation , fee_rate : 0 } ) ; } else { feeEstimationsWithMaxValues . push ( feeEstimations [ 1 ] ) ; } if ( new BigNumber ( feeEstimations [ 2 ] . fee ) . isGreaterThan ( maxValueForHighFeeEstimation ) ) { feeEstimationsWithMaxValues . push ( { fee : maxValueForHighFeeEstimation , fee_rate : 0 } ) ; } else { feeEstimationsWithMaxValues . push ( feeEstimations [ 2 ] ) ; } return feeEstimationsWithMaxValues ; } function calculateFeeFromFeeRate ( txBytes , feeRate ) { return new BigNumber ( txBytes ) . multipliedBy ( feeRate ) ; } const marginFromDefaultFeeDecimalPercent = 0.1 ; export function getDefaultSimulatedFeeEstimations ( estimatedByteLength ) { const fee = calculateFeeFromFeeRate ( estimatedByteLength , DEFAULT_FEE_RATE ) ; return [ { fee : fee . multipliedBy ( 1 - marginFromDefaultFeeDecimalPercent ) . toNumber ( ) , fee_rate : 0 } , { fee : fee . toNumber ( ) , fee_rate : 0 } , { fee : fee . multipliedBy ( 1 + marginFromDefaultFeeDecimalPercent ) . toNumber ( ) , fee_rate : 0 } , ] ; } export function isAddressTransactionWithTransfers ( transaction ) { return 'tx' in transaction ; } function groupTxsByDateMap ( txs ) { return txs . reduce ( ( txsByDate , atx ) => { const tx = isAddressTransactionWithTransfers ( atx ) ? atx . tx : atx ; const time = ( 'burn_block_time_iso' in tx && tx . burn_block_time_iso ) || ( 'parent_burn_block_time_iso' in tx && tx . parent_burn_block_time_iso ) ; const date = time ? isoDateToLocalDateSafe ( time ) : undefined ; if ( date && txHasTime ( tx ) ) { if ( ! txsByDate . has ( date ) ) { txsByDate . set ( date , [ ] ) ; } txsByDate . set ( date , [ ... ( txsByDate . get ( date ) || [ ] ) , atx ] ) ; } if ( ! txHasTime ( tx ) ) { const today = todaysIsoDate ( ) ; txsByDate . set ( today , [ ... ( txsByDate . get ( today ) || [ ] ) , atx ] ) ; } return txsByDate ; } , new Map < string , ( AddressTransactionWithTransfers | MempoolTransaction ) [ ] > ( ) ) ; } function formatTxDateMapAsList ( txMap ) { return [ ... txMap . keys ( ) ] . map ( date => ( { date , displayDate : displayDate ( date ) , txs : txMap . get ( date ) ?? [ ] , } ) ) ; } function countTxIds ( txs ) { return txs . reduce ( ( acc , e ) => { const txId = isAddressTransactionWithTransfers ( e ) ? e . tx . tx_id : e . tx_id ; return acc . set ( txId , ( acc . get ( txId ) || 0 ) + 1 ) ; } , new Map ( ) ) ; } function filterDuplicateTx ( txs ) { const countOfTxIds = countTxIds ( txs ) ; return txs . filter ( atx => { const tx = isAddressTransactionWithTransfers ( atx ) ? atx . tx : atx ; const isDropped = tx . tx_status . includes ( 'dropped' ) ; if ( countOfTxIds . get ( tx . tx_id ) === 1 && ! isDropped ) return true ; return tx . tx_status === 'success' || tx . tx_status . includes ( 'abort' ) ; } ) ; } export function createTxDateFormatList ( txs ) { return formatTxDateMapAsList ( groupTxsByDateMap ( filterDuplicateTx ( txs ) ) ) ; } export const getTxCaption = ( transaction ) => { switch ( transaction . tx_type ) { case 'smart_contract' : return truncateMiddle ( transaction . smart_contract . contract_id , 4 ) ; case 'contract_call' : return transaction . contract_call . contract_id . split ( '.' ) [ 1 ] ; case 'token_transfer' : case 'coinbase' : case 'poison_microblock' : return truncateMiddle ( transaction . tx_id , 4 ) ; default : return null ; } } ; const getAssetTransfer = ( tx ) : TransactionEventFungibleAsset | null => { if ( tx . tx_type !== 'contract_call' ) return null ; if ( tx . tx_status !== 'success' ) return null ; const transfer = tx . events . find ( event => event . event_type === 'fungible_token_asset' ) ; if ( transfer ?. event_type !== 'fungible_token_asset' ) return null ; return transfer ; } ; export const getTxValue = ( tx , isOriginator ) : number | string | null => { if ( tx . tx_type === 'token_transfer' ) { return `${ isOriginator ? '-' : '' }${ stacksValue ( { value : tx . token_transfer . amount , withTicker : false , } ) }` ; } const transfer = getAssetTransfer ( tx ) ; if ( transfer ) return new BigNumber ( transfer . asset . amount ) . toFormat ( ) ; return null ; } ; export const getTxTitle = ( tx ) => { switch ( tx . tx_type ) { case 'token_transfer' : return 'Stacks Token' ; case 'contract_call' : return tx . contract_call . function_name ; case 'smart_contract' : return getContractName ( tx . smart_contract . contract_id ) ; case 'coinbase' : return `Coinbase ${ ( tx as CoinbaseTransaction ) . block_height }` ; case 'poison_microblock' : return 'Poison Microblock' ; } } ; export const calculateTokenTransferAmount = ( asset , amount ) => { if ( ! asset || ! asset . meta ) return ; return new BigNumber ( amount ) . shiftedBy ( - asset . meta . decimals ) ; } ; export function isTransactionTypeSupported ( txType ) { return ( txType === TransactionTypes . STXTransfer || txType === TransactionTypes . ContractCall || txType === TransactionTypes . ContractDeploy ) ; } export function isTxSponsored ( tx ) { return tx . auth . authType === AuthType . Sponsored ; } interface WhenChainIdMap < T > { [ ChainID . Mainnet ] : T ; [ ChainID . Testnet ] : T ; } export function whenChainId ( chainId ) { return < T > ( chainIdMap ) : T => chainIdMap [ chainId ] ; }