2024-04-13 08:05:45 +00:00
import { Component , OnInit , OnDestroy , HostListener , Inject , ChangeDetectorRef , ChangeDetectionStrategy , NgZone } from '@angular/core' ;
2024-04-08 04:27:49 +00:00
import { ElectrsApiService } from '../../services/electrs-api.service' ;
import { ActivatedRoute , ParamMap , Router } from '@angular/router' ;
import {
switchMap ,
filter ,
catchError ,
retryWhen ,
delay ,
mergeMap ,
tap ,
map
} from 'rxjs/operators' ;
import { Transaction } from '../../interfaces/electrs.interface' ;
2024-04-13 08:05:45 +00:00
import { of , merge , Subscription , Observable , Subject , throwError , combineLatest } from 'rxjs' ;
2024-04-08 04:27:49 +00:00
import { StateService } from '../../services/state.service' ;
import { CacheService } from '../../services/cache.service' ;
import { WebsocketService } from '../../services/websocket.service' ;
import { AudioService } from '../../services/audio.service' ;
import { ApiService } from '../../services/api.service' ;
import { SeoService } from '../../services/seo.service' ;
import { StorageService } from '../../services/storage.service' ;
import { seoDescriptionNetwork } from '../../shared/common.utils' ;
2024-04-13 08:05:45 +00:00
import { Filter } from '../../shared/filters.utils' ;
2024-04-08 04:27:49 +00:00
import { BlockExtended , CpfpInfo , RbfTree , MempoolPosition , DifficultyAdjustment , Acceleration } from '../../interfaces/node-api.interface' ;
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe' ;
2024-04-13 08:05:45 +00:00
import { PriceService } from '../../services/price.service' ;
2024-04-08 04:27:49 +00:00
import { ServicesApiServices } from '../../services/services-api.service' ;
import { EnterpriseService } from '../../services/enterprise.service' ;
import { ZONE_SERVICE } from '../../injection-tokens' ;
2024-04-13 08:05:45 +00:00
import { TrackerStage } from './tracker-bar.component' ;
2024-04-08 04:27:49 +00:00
interface Pool {
id : number ;
name : string ;
slug : string ;
}
interface AuditStatus {
seen? : boolean ;
expected? : boolean ;
added? : boolean ;
prioritized? : boolean ;
delayed? : number ;
accelerated? : boolean ;
conflict? : boolean ;
coinbase? : boolean ;
}
@Component ( {
selector : 'app-tracker' ,
templateUrl : './tracker.component.html' ,
styleUrls : [ './tracker.component.scss' ] ,
2024-04-13 08:05:45 +00:00
changeDetection : ChangeDetectionStrategy.OnPush
2024-04-08 04:27:49 +00:00
} )
2024-04-13 08:05:45 +00:00
export class TrackerComponent implements OnInit , OnDestroy {
2024-04-08 04:27:49 +00:00
network = '' ;
tx : Transaction ;
txId : string ;
txInBlockIndex : number ;
mempoolPosition : MempoolPosition ;
isLoadingTx = true ;
error : any = undefined ;
loadingCachedTx = false ;
waitingForTransaction = false ;
latestBlock : BlockExtended ;
transactionTime = - 1 ;
subscription : Subscription ;
fetchCpfpSubscription : Subscription ;
fetchRbfSubscription : Subscription ;
fetchCachedTxSubscription : Subscription ;
fetchAccelerationSubscription : Subscription ;
txReplacedSubscription : Subscription ;
mempoolPositionSubscription : Subscription ;
mempoolBlocksSubscription : Subscription ;
blocksSubscription : Subscription ;
miningSubscription : Subscription ;
currencyChangeSubscription : Subscription ;
rbfTransaction : undefined | Transaction ;
replaced : boolean = false ;
rbfReplaces : string [ ] ;
rbfInfo : RbfTree ;
cpfpInfo : CpfpInfo | null ;
hasCpfp : boolean = false ;
accelerationInfo : Acceleration | null = null ;
sigops : number | null ;
adjustedVsize : number | null ;
pool : Pool | null ;
auditStatus : AuditStatus | null ;
2024-04-13 08:05:45 +00:00
isAcceleration : boolean = false ;
2024-04-08 04:27:49 +00:00
filters : Filter [ ] = [ ] ;
showCpfpDetails = false ;
fetchCpfp $ = new Subject < string > ( ) ;
fetchRbfHistory $ = new Subject < string > ( ) ;
fetchCachedTx $ = new Subject < string > ( ) ;
fetchAcceleration $ = new Subject < string > ( ) ;
fetchMiningInfo $ = new Subject < { hash : string , height : number , txid : string } > ( ) ;
isCached : boolean = false ;
now = Date . now ( ) ;
da$ : Observable < DifficultyAdjustment > ;
isMobile : boolean ;
2024-04-13 08:05:45 +00:00
paymentType : 'bitcoin' | 'cashapp' = 'bitcoin' ;
trackerStage : TrackerStage = 'waiting' ;
blockchainHeight : number = 100 ;
blockchainWidth : number = 600 ;
2024-04-08 04:27:49 +00:00
hasEffectiveFeeRate : boolean ;
accelerateCtaType : 'alert' | 'button' = 'button' ;
acceleratorAvailable : boolean = this . stateService . env . OFFICIAL_MEMPOOL_SPACE && this . stateService . env . ACCELERATOR && this . stateService . network === '' ;
2024-04-13 08:05:45 +00:00
accelerationEligible : boolean = false ;
2024-04-08 04:27:49 +00:00
showAccelerationSummary = false ;
scrollIntoAccelPreview = false ;
auditEnabled : boolean = this . stateService . env . AUDIT && this . stateService . env . BASE_MODULE === 'mempool' && this . stateService . env . MINING_DASHBOARD === true ;
constructor (
private route : ActivatedRoute ,
private electrsApiService : ElectrsApiService ,
public stateService : StateService ,
private cacheService : CacheService ,
private websocketService : WebsocketService ,
private audioService : AudioService ,
private apiService : ApiService ,
private servicesApiService : ServicesApiServices ,
private seoService : SeoService ,
private priceService : PriceService ,
private enterpriseService : EnterpriseService ,
private cd : ChangeDetectorRef ,
2024-04-13 08:05:45 +00:00
private zone : NgZone ,
2024-04-08 04:27:49 +00:00
@Inject ( ZONE_SERVICE ) private zoneService : any ,
) { }
ngOnInit() {
2024-04-13 08:05:45 +00:00
this . onResize ( ) ;
window [ 'setStage' ] = ( ( stage : TrackerStage ) = > {
this . zone . run ( ( ) = > {
this . trackerStage = stage ;
this . cd . markForCheck ( ) ;
} ) ;
} ) . bind ( this ) ;
2024-04-08 04:27:49 +00:00
this . acceleratorAvailable = this . stateService . env . OFFICIAL_MEMPOOL_SPACE && this . stateService . env . ACCELERATOR && this . stateService . network === '' ;
2024-04-13 08:05:45 +00:00
if ( this . acceleratorAvailable && this . stateService . ref === 'https://cash.app/' ) {
this . paymentType = 'cashapp' ;
}
2024-04-13 23:07:19 +09:00
const urlParams = new URLSearchParams ( window . location . search ) ;
if ( urlParams . get ( 'cash_request_id' ) ) {
this . showAccelerationSummary = true ;
}
2024-04-13 08:05:45 +00:00
2024-04-08 04:27:49 +00:00
this . enterpriseService . page ( ) ;
this . websocketService . want ( [ 'blocks' , 'mempool-blocks' ] ) ;
this . stateService . networkChanged $ . subscribe (
( network ) = > {
this . network = network ;
this . acceleratorAvailable = this . stateService . env . OFFICIAL_MEMPOOL_SPACE && this . stateService . env . ACCELERATOR && this . stateService . network === '' ;
}
) ;
this . da $ = this . stateService . difficultyAdjustment $ . pipe (
tap ( ( ) = > {
this . now = Date . now ( ) ;
} )
) ;
this . blocksSubscription = this . stateService . blocks $ . subscribe ( ( blocks ) = > {
this . latestBlock = blocks [ 0 ] ;
} ) ;
this . fetchCpfpSubscription = this . fetchCpfp $
. pipe (
switchMap ( ( txId ) = >
this . apiService
. getCpfpinfo $ ( txId )
. pipe ( retryWhen ( ( errors ) = > errors . pipe (
mergeMap ( ( error ) = > {
if ( ! this . tx ? . status || this . tx . status . confirmed ) {
return throwError ( error ) ;
} else {
return of ( null ) ;
}
} ) ,
delay ( 2000 )
) ) ,
catchError ( ( ) = > {
return of ( null ) ;
} )
)
) ,
catchError ( ( ) = > {
return of ( null ) ;
} )
)
. subscribe ( ( cpfpInfo ) = > {
this . setCpfpInfo ( cpfpInfo ) ;
} ) ;
this . fetchRbfSubscription = this . fetchRbfHistory $
. pipe (
switchMap ( ( txId ) = >
this . apiService
. getRbfHistory $ ( txId )
) ,
catchError ( ( ) = > {
return of ( null ) ;
} )
) . subscribe ( ( rbfResponse ) = > {
this . rbfInfo = rbfResponse ? . replacements ;
this . rbfReplaces = rbfResponse ? . replaces || null ;
} ) ;
this . fetchCachedTxSubscription = this . fetchCachedTx $
. pipe (
tap ( ( ) = > {
this . loadingCachedTx = true ;
} ) ,
switchMap ( ( txId ) = >
this . apiService
. getRbfCachedTx $ ( txId )
) ,
catchError ( ( ) = > {
return of ( null ) ;
} )
) . subscribe ( ( tx ) = > {
this . loadingCachedTx = false ;
if ( ! tx ) {
this . seoService . logSoft404 ( ) ;
return ;
}
this . seoService . clearSoft404 ( ) ;
if ( ! this . tx ) {
this . tx = tx ;
this . isCached = true ;
if ( tx . fee === undefined ) {
this . tx . fee = 0 ;
}
this . tx . feePerVsize = tx . fee / ( tx . weight / 4 ) ;
this . isLoadingTx = false ;
this . error = undefined ;
this . waitingForTransaction = false ;
this . transactionTime = tx . firstSeen || 0 ;
this . fetchRbfHistory $ . next ( this . tx . txid ) ;
}
} ) ;
this . fetchAccelerationSubscription = this . fetchAcceleration $ . pipe (
filter ( ( ) = > this . stateService . env . ACCELERATOR === true ) ,
tap ( ( ) = > {
this . accelerationInfo = null ;
} ) ,
switchMap ( ( blockHash : string ) = > {
return this . servicesApiService . getAccelerationHistory $ ( { blockHash } ) ;
} ) ,
catchError ( ( ) = > {
return of ( null ) ;
} )
) . subscribe ( ( accelerationHistory ) = > {
for ( const acceleration of accelerationHistory ) {
if ( acceleration . txid === this . txId && ( acceleration . status === 'completed' || acceleration . status === 'completed_provisional' ) ) {
2024-04-14 03:21:24 +00:00
const boostCost = acceleration . boostCost || acceleration . bidBoost ;
2024-04-13 08:05:45 +00:00
acceleration . acceleratedFeeRate = Math . max ( acceleration . effectiveFee , acceleration . effectiveFee + boostCost ) / acceleration . effectiveVsize ;
acceleration . boost = boostCost ;
2024-04-08 04:27:49 +00:00
this . accelerationInfo = acceleration ;
2024-04-13 08:05:45 +00:00
this . setIsAccelerated ( ) ;
2024-04-08 04:27:49 +00:00
}
}
} ) ;
this . miningSubscription = this . fetchMiningInfo $ . pipe (
filter ( ( target ) = > target . txid === this . txId ) ,
tap ( ( ) = > {
this . pool = null ;
this . auditStatus = null ;
} ) ,
switchMap ( ( { hash , height , txid } ) = > {
const foundBlock = this . cacheService . getCachedBlock ( height ) || null ;
const auditAvailable = this . isAuditAvailable ( height ) ;
const isCoinbase = this . tx . vin . some ( v = > v . is_coinbase ) ;
const fetchAudit = auditAvailable && ! isCoinbase ;
return combineLatest ( [
foundBlock ? of ( foundBlock . extras . pool ) : this . apiService . getBlock $ ( hash ) . pipe (
map ( block = > {
return block . extras . pool ;
} ) ,
catchError ( ( ) = > {
return of ( null ) ;
} )
) ,
fetchAudit ? this . apiService . getBlockAudit $ ( hash ) . pipe (
map ( audit = > {
const isAdded = audit . addedTxs . includes ( txid ) ;
const isPrioritized = audit . prioritizedTxs . includes ( txid ) ;
const isAccelerated = audit . acceleratedTxs . includes ( txid ) ;
const isConflict = audit . fullrbfTxs . includes ( txid ) ;
const isExpected = audit . template . some ( tx = > tx . txid === txid ) ;
return {
seen : isExpected || isPrioritized || isAccelerated ,
expected : isExpected ,
added : isAdded ,
prioritized : isPrioritized ,
conflict : isConflict ,
accelerated : isAccelerated ,
} ;
} ) ,
catchError ( ( ) = > {
return of ( null ) ;
} )
) : of ( isCoinbase ? { coinbase : true } : null )
] ) ;
} ) ,
catchError ( ( ) = > {
return of ( null ) ;
} )
) . subscribe ( ( [ pool , auditStatus ] ) = > {
this . pool = pool ;
this . auditStatus = auditStatus ;
} ) ;
this . mempoolPositionSubscription = this . stateService . mempoolTxPosition $ . subscribe ( txPosition = > {
this . now = Date . now ( ) ;
if ( txPosition && txPosition . txid === this . txId && txPosition . position ) {
this . mempoolPosition = txPosition . position ;
if ( this . tx && ! this . tx . status . confirmed ) {
this . stateService . markBlock $ . next ( {
txid : txPosition.txid ,
mempoolPosition : this.mempoolPosition
} ) ;
this . txInBlockIndex = this . mempoolPosition . block ;
if ( txPosition . cpfp !== undefined ) {
this . setCpfpInfo ( txPosition . cpfp ) ;
}
2024-04-13 08:05:45 +00:00
2024-04-13 13:14:26 +00:00
if ( txPosition . position ? . accelerated ) {
this . tx . acceleration = true ;
}
2024-04-13 08:05:45 +00:00
if ( txPosition . position ? . block === 0 ) {
this . trackerStage = 'next' ;
} else if ( txPosition . position ? . block < 3 ) {
this . trackerStage = 'soon' ;
} else {
this . trackerStage = 'pending' ;
}
if ( txPosition . position ? . block > 0 && this . tx . weight < 4000 ) {
this . accelerationEligible = true ;
}
2024-04-08 04:27:49 +00:00
}
} else {
this . mempoolPosition = null ;
}
} ) ;
this . subscription = this . zoneService . wrapObservable ( this . route . paramMap
. pipe (
switchMap ( ( params : ParamMap ) = > {
const urlMatch = ( params . get ( 'id' ) || '' ) . split ( ':' ) ;
if ( urlMatch . length === 2 && urlMatch [ 1 ] . length === 64 ) {
const vin = parseInt ( urlMatch [ 0 ] , 10 ) ;
this . txId = urlMatch [ 1 ] ;
} else {
this . txId = urlMatch [ 0 ] ;
}
this . seoService . setTitle (
$localize ` :@@bisq.transaction.browser-title:Transaction: ${ this . txId } :INTERPOLATION: `
) ;
const network = this . stateService . network === 'liquid' || this . stateService . network === 'liquidtestnet' ? 'Liquid' : 'Bitcoin' ;
const seoDescription = seoDescriptionNetwork ( this . stateService . network ) ;
this . seoService . setDescription ( $localize ` :@@meta.description.bitcoin.transaction:Get real-time status, addresses, fees, script info, and more for ${ network } ${ seoDescription } transaction with txid ${ this . txId } . ` ) ;
this . resetTransaction ( ) ;
return merge (
of ( true ) ,
this . stateService . connectionState $ . pipe (
filter (
( state ) = > state === 2 && this . tx && ! this . tx . status ? . confirmed
)
)
) ;
} ) ,
switchMap ( ( ) = > {
let transactionObservable$ : Observable < Transaction > ;
const cached = this . cacheService . getTxFromCache ( this . txId ) ;
if ( cached && cached . fee !== - 1 ) {
transactionObservable $ = of ( cached ) ;
} else {
transactionObservable $ = this . electrsApiService
. getTransaction $ ( this . txId )
. pipe (
catchError ( this . handleLoadElectrsTransactionError . bind ( this ) )
) ;
}
return merge (
transactionObservable $ ,
this . stateService . mempoolTransactions $
) ;
} ) ,
) )
. subscribe ( ( tx : Transaction ) = > {
if ( ! tx ) {
this . fetchCachedTx $ . next ( this . txId ) ;
this . seoService . logSoft404 ( ) ;
return ;
}
this . seoService . clearSoft404 ( ) ;
this . tx = tx ;
this . isCached = false ;
if ( tx . fee === undefined ) {
this . tx . fee = 0 ;
}
if ( this . tx . sigops != null ) {
this . sigops = this . tx . sigops ;
this . adjustedVsize = Math . max ( this . tx . weight / 4 , this . sigops * 5 ) ;
}
this . tx . feePerVsize = tx . fee / ( tx . weight / 4 ) ;
this . isLoadingTx = false ;
this . error = undefined ;
this . loadingCachedTx = false ;
this . waitingForTransaction = false ;
this . websocketService . startTrackTransaction ( tx . txid ) ;
if ( ! tx . status ? . confirmed ) {
2024-04-13 08:05:45 +00:00
this . trackerStage = 'pending' ;
2024-04-08 04:27:49 +00:00
if ( tx . firstSeen ) {
this . transactionTime = tx . firstSeen ;
} else {
this . getTransactionTime ( ) ;
}
} else {
2024-04-13 08:05:45 +00:00
this . trackerStage = 'confirmed' ;
2024-04-08 04:27:49 +00:00
this . fetchAcceleration $ . next ( tx . status . block_hash ) ;
this . fetchMiningInfo $ . next ( { hash : tx.status.block_hash , height : tx.status.block_height , txid : tx.txid } ) ;
this . transactionTime = 0 ;
}
if ( this . tx ? . status ? . confirmed ) {
this . stateService . markBlock $ . next ( {
blockHeight : tx.status.block_height ,
} ) ;
this . fetchCpfp $ . next ( this . tx . txid ) ;
} else {
if ( tx . cpfpChecked ) {
this . stateService . markBlock $ . next ( {
txid : tx.txid ,
txFeePerVSize : tx.effectiveFeePerVsize ,
mempoolPosition : this.mempoolPosition ,
} ) ;
this . setCpfpInfo ( {
ancestors : tx.ancestors ,
bestDescendant : tx.bestDescendant ,
} ) ;
const hasRelatives = ! ! ( tx . ancestors ? . length || tx . bestDescendant ) ;
this . hasEffectiveFeeRate = hasRelatives || ( tx . effectiveFeePerVsize && ( Math . abs ( tx . effectiveFeePerVsize - tx . feePerVsize ) > 0.01 ) ) ;
} else {
this . fetchCpfp $ . next ( this . tx . txid ) ;
}
}
this . fetchRbfHistory $ . next ( this . tx . txid ) ;
this . currencyChangeSubscription ? . unsubscribe ( ) ;
this . currencyChangeSubscription = this . stateService . fiatCurrency $ . pipe (
switchMap ( ( currency ) = > {
return tx . status . block_time ? this . priceService . getBlockPrice $ ( tx . status . block_time , true , currency ) . pipe (
tap ( ( price ) = > tx [ 'price' ] = price ) ,
) : of ( undefined ) ;
} )
) . subscribe ( ) ;
this . cd . detectChanges ( ) ;
} ,
( error ) = > {
this . error = error ;
this . seoService . logSoft404 ( ) ;
this . isLoadingTx = false ;
}
) ;
this . stateService . txConfirmed $ . subscribe ( ( [ txConfirmed , block ] ) = > {
if ( txConfirmed && this . tx && ! this . tx . status . confirmed && txConfirmed === this . tx . txid ) {
this . tx . status = {
confirmed : true ,
block_height : block.height ,
block_hash : block.id ,
block_time : block.timestamp ,
} ;
2024-04-13 08:05:45 +00:00
this . trackerStage = 'confirmed' ;
2024-04-08 04:27:49 +00:00
this . stateService . markBlock $ . next ( { blockHeight : block.height } ) ;
if ( this . tx . acceleration || ( this . accelerationInfo && [ 'accelerating' , 'completed_provisional' , 'completed' ] . includes ( this . accelerationInfo . status ) ) ) {
this . audioService . playSound ( 'wind-chimes-harp-ascend' ) ;
} else {
this . audioService . playSound ( 'magic' ) ;
}
this . fetchAcceleration $ . next ( block . id ) ;
this . fetchMiningInfo $ . next ( { hash : block.id , height : block.height , txid : this.tx.txid } ) ;
}
} ) ;
this . txReplacedSubscription = this . stateService . txReplaced $ . subscribe ( ( rbfTransaction ) = > {
if ( ! this . tx ) {
this . error = new Error ( ) ;
this . loadingCachedTx = false ;
this . waitingForTransaction = false ;
}
this . rbfTransaction = rbfTransaction ;
this . replaced = true ;
this . stateService . markBlock $ . next ( { } ) ;
if ( rbfTransaction && ! this . tx ) {
this . fetchCachedTx $ . next ( this . txId ) ;
}
} ) ;
this . mempoolBlocksSubscription = this . stateService . mempoolBlocks $ . subscribe ( ( mempoolBlocks ) = > {
this . now = Date . now ( ) ;
if ( ! this . tx || this . mempoolPosition ) {
return ;
}
const txFeePerVSize =
this . tx . effectiveFeePerVsize || this . tx . fee / ( this . tx . weight / 4 ) ;
let found = false ;
this . txInBlockIndex = 0 ;
for ( const block of mempoolBlocks ) {
for ( let i = 0 ; i < block . feeRange . length - 1 && ! found ; i ++ ) {
if (
txFeePerVSize <= block . feeRange [ i + 1 ] &&
txFeePerVSize >= block . feeRange [ i ]
) {
this . txInBlockIndex = mempoolBlocks . indexOf ( block ) ;
found = true ;
}
}
}
if ( ! found && mempoolBlocks . length && txFeePerVSize < mempoolBlocks [ mempoolBlocks . length - 1 ] . feeRange [ 0 ] ) {
this . txInBlockIndex = 7 ;
}
} ) ;
}
handleLoadElectrsTransactionError ( error : any ) : Observable < any > {
if ( error . status === 404 && /^[a-fA-F0-9]{64}$/ . test ( this . txId ) ) {
this . websocketService . startMultiTrackTransaction ( this . txId ) ;
this . waitingForTransaction = true ;
}
this . error = error ;
this . seoService . logSoft404 ( ) ;
this . isLoadingTx = false ;
return of ( false ) ;
}
getTransactionTime() {
this . apiService
. getTransactionTimes $ ( [ this . tx . txid ] )
. subscribe ( ( transactionTimes ) = > {
if ( transactionTimes ? . length ) {
this . transactionTime = transactionTimes [ 0 ] ;
}
} ) ;
}
setCpfpInfo ( cpfpInfo : CpfpInfo ) : void {
if ( ! cpfpInfo || ! this . tx ) {
this . cpfpInfo = null ;
this . hasCpfp = false ;
this . hasEffectiveFeeRate = false ;
return ;
}
// merge ancestors/descendants
const relatives = [ . . . ( cpfpInfo . ancestors || [ ] ) , . . . ( cpfpInfo . descendants || [ ] ) ] ;
if ( cpfpInfo . bestDescendant && ! cpfpInfo . descendants ? . length ) {
relatives . push ( cpfpInfo . bestDescendant ) ;
}
const hasRelatives = ! ! relatives . length ;
if ( ! cpfpInfo . effectiveFeePerVsize && hasRelatives ) {
const totalWeight =
this . tx . weight +
relatives . reduce ( ( prev , val ) = > prev + val . weight , 0 ) ;
const totalFees =
this . tx . fee +
relatives . reduce ( ( prev , val ) = > prev + val . fee , 0 ) ;
this . tx . effectiveFeePerVsize = totalFees / ( totalWeight / 4 ) ;
} else {
this . tx . effectiveFeePerVsize = cpfpInfo . effectiveFeePerVsize ;
}
if ( cpfpInfo . acceleration ) {
this . tx . acceleration = cpfpInfo . acceleration ;
}
this . cpfpInfo = cpfpInfo ;
if ( this . cpfpInfo . adjustedVsize && this . cpfpInfo . sigops != null ) {
this . sigops = this . cpfpInfo . sigops ;
this . adjustedVsize = this . cpfpInfo . adjustedVsize ;
}
this . hasCpfp = ! ! ( this . cpfpInfo && ( this . cpfpInfo . bestDescendant || this . cpfpInfo . descendants ? . length || this . cpfpInfo . ancestors ? . length ) ) ;
this . hasEffectiveFeeRate = hasRelatives || ( this . tx . effectiveFeePerVsize && ( Math . abs ( this . tx . effectiveFeePerVsize - this . tx . feePerVsize ) > 0.01 ) ) ;
}
isAuditAvailable ( blockHeight : number ) : boolean {
if ( ! this . auditEnabled ) {
return false ;
}
switch ( this . stateService . network ) {
case 'testnet' :
if ( blockHeight < this . stateService . env . TESTNET_BLOCK_AUDIT_START_HEIGHT ) {
return false ;
}
break ;
case 'signet' :
if ( blockHeight < this . stateService . env . SIGNET_BLOCK_AUDIT_START_HEIGHT ) {
return false ;
}
break ;
default :
if ( blockHeight < this . stateService . env . MAINNET_BLOCK_AUDIT_START_HEIGHT ) {
return false ;
}
}
return true ;
}
2024-04-13 08:05:45 +00:00
setIsAccelerated ( initialState : boolean = false ) {
this . isAcceleration = ( this . tx . acceleration || ( this . accelerationInfo && this . pool && this . accelerationInfo . pools . some ( pool = > ( pool === this . pool . id || pool ? . [ 'pool_unique_id' ] === this . pool . id ) ) ) ) ;
}
dismissAccelAlert ( ) : void {
this . accelerateCtaType = 'button' ;
}
onAccelerateClicked() {
if ( ! this . txId ) {
return ;
}
this . enterpriseService . goal ( 8 ) ;
this . showAccelerationSummary = true && this . acceleratorAvailable ;
this . scrollIntoAccelPreview = ! this . scrollIntoAccelPreview ;
return false ;
}
2024-04-08 04:27:49 +00:00
resetTransaction() {
this . error = undefined ;
this . tx = null ;
this . waitingForTransaction = false ;
this . isLoadingTx = true ;
this . rbfTransaction = undefined ;
this . replaced = false ;
this . transactionTime = - 1 ;
this . cpfpInfo = null ;
this . adjustedVsize = null ;
this . sigops = null ;
this . hasEffectiveFeeRate = false ;
this . rbfInfo = null ;
this . rbfReplaces = [ ] ;
this . filters = [ ] ;
this . showCpfpDetails = false ;
this . accelerationInfo = null ;
this . txInBlockIndex = null ;
this . mempoolPosition = null ;
this . pool = null ;
this . auditStatus = null ;
2024-04-13 08:05:45 +00:00
this . accelerationEligible = false ;
this . trackerStage = 'waiting' ;
2024-04-08 04:27:49 +00:00
document . body . scrollTo ( 0 , 0 ) ;
this . leaveTransaction ( ) ;
}
leaveTransaction() {
this . websocketService . stopTrackingTransaction ( ) ;
this . stateService . markBlock $ . next ( { } ) ;
}
roundToOneDecimal ( cpfpTx : any ) : number {
return + ( cpfpTx . fee / ( cpfpTx . weight / 4 ) ) . toFixed ( 1 ) ;
}
@HostListener ( 'window:resize' , [ '$event' ] )
2024-04-13 08:05:45 +00:00
onResize ( ) : void {
2024-04-08 04:27:49 +00:00
this . isMobile = window . innerWidth < 850 ;
2024-04-13 08:05:45 +00:00
this . blockchainWidth = Math . min ( 600 , window . innerWidth ) ;
this . blockchainHeight = this . blockchainWidth / 5 ;
2024-04-08 04:27:49 +00:00
}
ngOnDestroy() {
this . subscription . unsubscribe ( ) ;
this . fetchCpfpSubscription . unsubscribe ( ) ;
this . fetchRbfSubscription . unsubscribe ( ) ;
this . fetchCachedTxSubscription . unsubscribe ( ) ;
this . fetchAccelerationSubscription . unsubscribe ( ) ;
this . txReplacedSubscription . unsubscribe ( ) ;
this . mempoolBlocksSubscription . unsubscribe ( ) ;
this . mempoolPositionSubscription . unsubscribe ( ) ;
this . mempoolBlocksSubscription . unsubscribe ( ) ;
this . blocksSubscription . unsubscribe ( ) ;
this . miningSubscription ? . unsubscribe ( ) ;
this . currencyChangeSubscription ? . unsubscribe ( ) ;
this . leaveTransaction ( ) ;
}
}