2023-10-28 00:33:29 +00:00
import { Component , OnInit , AfterViewInit , OnDestroy , HostListener , ViewChild , ElementRef , Inject , ChangeDetectorRef } from '@angular/core' ;
2020-02-16 22:15:07 +07:00
import { ElectrsApiService } from '../../services/electrs-api.service' ;
2022-09-29 15:41:14 +00:00
import { ActivatedRoute , ParamMap , Router } from '@angular/router' ;
2021-07-06 13:56:32 -03:00
import {
switchMap ,
filter ,
catchError ,
retryWhen ,
delay ,
2023-02-21 12:36:43 +09:00
mergeMap ,
2024-03-07 23:21:27 +00:00
tap ,
2024-04-08 14:08:11 +00:00
map ,
retry
2021-07-06 13:56:32 -03:00
} from 'rxjs/operators' ;
2022-02-04 12:51:45 +09:00
import { Transaction } from '../../interfaces/electrs.interface' ;
2024-03-07 23:21:27 +00:00
import { of , merge , Subscription , Observable , Subject , from , throwError , combineLatest } from 'rxjs' ;
2020-02-16 22:15:07 +07:00
import { StateService } from '../../services/state.service' ;
2022-12-27 05:36:58 -06:00
import { CacheService } from '../../services/cache.service' ;
2020-02-16 22:15:07 +07:00
import { WebsocketService } from '../../services/websocket.service' ;
2022-09-21 17:23:45 +02:00
import { AudioService } from '../../services/audio.service' ;
import { ApiService } from '../../services/api.service' ;
import { SeoService } from '../../services/seo.service' ;
2023-08-24 14:17:31 +02:00
import { StorageService } from '../../services/storage.service' ;
2023-08-30 20:26:07 +09:00
import { seoDescriptionNetwork } from '../../shared/common.utils' ;
2024-03-22 09:52:27 +00:00
import { getTransactionFlags } from '../../shared/transaction.utils' ;
import { Filter , toFilters , TransactionFlags } from '../../shared/filters.utils' ;
2023-12-05 13:40:17 +00:00
import { BlockExtended , CpfpInfo , RbfTree , MempoolPosition , DifficultyAdjustment , Acceleration } from '../../interfaces/node-api.interface' ;
2021-08-17 20:20:25 +03:00
import { LiquidUnblinding } from './liquid-ublinding' ;
2022-10-11 20:54:17 +00:00
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe' ;
2023-02-28 10:59:39 +09:00
import { Price , PriceService } from '../../services/price.service' ;
2023-03-14 13:02:50 +09:00
import { isFeatureActive } from '../../bitcoin.utils' ;
2023-12-12 17:24:58 +01:00
import { ServicesApiServices } from '../../services/services-api.service' ;
2024-02-14 21:47:42 +00:00
import { EnterpriseService } from '../../services/enterprise.service' ;
2023-10-28 00:33:29 +00:00
import { ZONE_SERVICE } from '../../injection-tokens' ;
2024-05-26 20:38:28 +00:00
import { MiningService , MiningStats } from '../../services/mining.service' ;
2020-02-16 22:15:07 +07:00
2024-03-07 23:21:27 +00:00
interface Pool {
id : number ;
name : string ;
slug : string ;
}
interface AuditStatus {
2024-03-08 15:21:37 +00:00
seen? : boolean ;
expected? : boolean ;
added? : boolean ;
2024-04-02 02:02:17 +00:00
prioritized? : boolean ;
2024-03-07 23:21:27 +00:00
delayed? : number ;
2024-03-08 15:21:37 +00:00
accelerated? : boolean ;
conflict? : boolean ;
coinbase? : boolean ;
2024-03-07 23:21:27 +00:00
}
2020-02-16 22:15:07 +07:00
@Component ( {
selector : 'app-transaction' ,
templateUrl : './transaction.component.html' ,
2021-07-06 13:56:32 -03:00
styleUrls : [ './transaction.component.scss' ] ,
2020-02-16 22:15:07 +07:00
} )
2022-09-16 20:50:12 +00:00
export class TransactionComponent implements OnInit , AfterViewInit , OnDestroy {
2020-05-09 20:37:50 +07:00
network = '' ;
2020-02-16 22:15:07 +07:00
tx : Transaction ;
txId : string ;
2020-03-23 04:07:31 +07:00
txInBlockIndex : number ;
2023-04-21 08:40:21 +09:00
mempoolPosition : MempoolPosition ;
2020-02-16 22:15:07 +07:00
isLoadingTx = true ;
error : any = undefined ;
2021-07-06 13:56:32 -03:00
errorUnblinded : any = undefined ;
2023-07-11 15:49:38 +09:00
loadingCachedTx = false ;
2020-04-13 01:26:53 +07:00
waitingForTransaction = false ;
2022-02-04 12:51:45 +09:00
latestBlock : BlockExtended ;
2020-02-28 01:09:07 +07:00
transactionTime = - 1 ;
2020-04-12 03:03:51 +07:00
subscription : Subscription ;
2021-04-27 02:13:48 +04:00
fetchCpfpSubscription : Subscription ;
2022-12-09 10:32:58 -06:00
fetchRbfSubscription : Subscription ;
fetchCachedTxSubscription : Subscription ;
2023-12-05 13:40:17 +00:00
fetchAccelerationSubscription : Subscription ;
2022-03-08 14:49:25 +01:00
txReplacedSubscription : Subscription ;
2022-12-14 08:49:35 -06:00
txRbfInfoSubscription : Subscription ;
2023-04-21 08:40:21 +09:00
mempoolPositionSubscription : Subscription ;
2022-09-29 15:41:14 +00:00
queryParamsSubscription : Subscription ;
2022-10-11 20:54:17 +00:00
urlFragmentSubscription : Subscription ;
2023-05-03 13:58:08 -06:00
mempoolBlocksSubscription : Subscription ;
2023-07-10 13:57:18 +09:00
blocksSubscription : Subscription ;
2024-03-07 23:21:27 +00:00
miningSubscription : Subscription ;
2024-04-17 16:41:30 +02:00
auditSubscription : Subscription ;
2024-03-09 16:32:21 +01:00
currencyChangeSubscription : Subscription ;
2022-10-11 20:54:17 +00:00
fragmentParams : URLSearchParams ;
2020-06-08 18:55:53 +07:00
rbfTransaction : undefined | Transaction ;
2022-12-09 10:32:58 -06:00
replaced : boolean = false ;
rbfReplaces : string [ ] ;
2022-12-17 09:39:06 -06:00
rbfInfo : RbfTree ;
2021-03-22 18:04:50 +07:00
cpfpInfo : CpfpInfo | null ;
2024-04-05 06:27:12 +00:00
hasCpfp : boolean = false ;
2023-12-05 13:40:17 +00:00
accelerationInfo : Acceleration | null = null ;
2023-09-19 00:18:52 +00:00
sigops : number | null ;
adjustedVsize : number | null ;
2024-03-07 23:21:27 +00:00
pool : Pool | null ;
auditStatus : AuditStatus | null ;
2024-04-08 13:45:05 +00:00
isAcceleration : boolean = false ;
2024-03-22 09:52:27 +00:00
filters : Filter [ ] = [ ] ;
2021-03-22 18:04:50 +07:00
showCpfpDetails = false ;
2024-05-26 20:38:28 +00:00
miningStats : MiningStats ;
2021-04-27 02:13:48 +04:00
fetchCpfp $ = new Subject < string > ( ) ;
2022-12-09 10:32:58 -06:00
fetchRbfHistory $ = new Subject < string > ( ) ;
fetchCachedTx $ = new Subject < string > ( ) ;
2024-04-04 08:22:55 +00:00
fetchAcceleration $ = new Subject < number > ( ) ;
2024-03-07 23:21:27 +00:00
fetchMiningInfo $ = new Subject < { hash : string , height : number , txid : string } > ( ) ;
2023-03-06 00:02:21 -06:00
isCached : boolean = false ;
2023-05-03 13:55:26 -06:00
now = Date . now ( ) ;
2023-06-29 11:22:33 -04:00
da$ : Observable < DifficultyAdjustment > ;
2021-08-17 20:20:25 +03:00
liquidUnblinding = new LiquidUnblinding ( ) ;
2022-10-04 21:00:46 +00:00
inputIndex : number ;
2021-10-19 23:24:12 +04:00
outputIndex : number ;
2022-09-16 20:50:12 +00:00
graphExpanded : boolean = false ;
2023-10-28 00:33:29 +00:00
graphWidth : number = 1068 ;
2022-09-17 01:20:08 +00:00
graphHeight : number = 360 ;
2022-09-23 19:03:21 +00:00
inOutLimit : number = 150 ;
2022-09-16 20:50:12 +00:00
maxInOut : number = 0 ;
2022-10-11 17:01:23 +00:00
flowPrefSubscription : Subscription ;
hideFlow : boolean = this . stateService . hideFlow . value ;
overrideFlowPreference : boolean = null ;
flowEnabled : boolean ;
2022-09-17 01:20:08 +00:00
tooltipPosition : { x : number , y : number } ;
2023-03-13 12:48:01 +09:00
isMobile : boolean ;
2024-04-08 08:00:00 +00:00
firstLoad = true ;
2023-03-13 12:48:01 +09:00
featuresEnabled : boolean ;
segwitEnabled : boolean ;
rbfEnabled : boolean ;
taprootEnabled : boolean ;
2023-05-31 12:11:56 -04:00
hasEffectiveFeeRate : boolean ;
2023-11-18 18:36:17 +09:00
accelerateCtaType : 'alert' | 'button' = 'button' ;
2023-08-24 14:17:31 +02:00
acceleratorAvailable : boolean = this . stateService . env . OFFICIAL_MEMPOOL_SPACE && this . stateService . env . ACCELERATOR && this . stateService . network === '' ;
showAccelerationSummary = false ;
2023-08-26 09:52:55 +02:00
scrollIntoAccelPreview = false ;
2024-03-07 23:21:27 +00:00
auditEnabled : boolean = this . stateService . env . AUDIT && this . stateService . env . BASE_MODULE === 'mempool' && this . stateService . env . MINING_DASHBOARD === true ;
2022-09-16 20:50:12 +00:00
@ViewChild ( 'graphContainer' )
graphContainer : ElementRef ;
2020-02-16 22:15:07 +07:00
constructor (
private route : ActivatedRoute ,
2022-09-29 15:41:14 +00:00
private router : Router ,
2022-10-11 20:54:17 +00:00
private relativeUrlPipe : RelativeUrlPipe ,
2020-02-16 22:15:07 +07:00
private electrsApiService : ElectrsApiService ,
2023-05-03 16:32:00 +02:00
public stateService : StateService ,
2022-12-27 05:36:58 -06:00
private cacheService : CacheService ,
2020-02-16 22:15:07 +07:00
private websocketService : WebsocketService ,
2020-02-26 04:29:57 +07:00
private audioService : AudioService ,
2020-02-28 01:09:07 +07:00
private apiService : ApiService ,
2023-12-12 17:24:58 +01:00
private servicesApiService : ServicesApiServices ,
2023-02-21 12:36:43 +09:00
private seoService : SeoService ,
private priceService : PriceService ,
2024-02-14 21:47:42 +00:00
private storageService : StorageService ,
private enterpriseService : EnterpriseService ,
2024-05-26 20:38:28 +00:00
private miningService : MiningService ,
2023-10-28 00:33:29 +00:00
private cd : ChangeDetectorRef ,
@Inject ( ZONE_SERVICE ) private zoneService : any ,
2021-07-06 13:56:32 -03:00
) { }
2020-02-16 22:15:07 +07:00
ngOnInit() {
2023-08-17 14:28:33 +02:00
this . acceleratorAvailable = this . stateService . env . OFFICIAL_MEMPOOL_SPACE && this . stateService . env . ACCELERATOR && this . stateService . network === '' ;
2024-02-14 21:47:42 +00:00
this . enterpriseService . page ( ) ;
2020-09-26 22:46:26 +07:00
this . websocketService . want ( [ 'blocks' , 'mempool-blocks' ] ) ;
2021-07-06 13:56:32 -03:00
this . stateService . networkChanged $ . subscribe (
2023-08-24 14:17:31 +02:00
( network ) = > {
this . network = network ;
this . acceleratorAvailable = this . stateService . env . OFFICIAL_MEMPOOL_SPACE && this . stateService . env . ACCELERATOR && this . stateService . network === '' ;
}
2021-07-06 13:56:32 -03:00
) ;
2020-05-09 20:37:50 +07:00
2023-11-18 18:36:17 +09:00
this . accelerateCtaType = ( this . storageService . getValue ( 'accel-cta-type' ) as 'alert' | 'button' ) ? ? 'button' ;
2023-08-24 14:17:31 +02:00
2022-10-11 17:01:23 +00:00
this . setFlowEnabled ( ) ;
this . flowPrefSubscription = this . stateService . hideFlow . subscribe ( ( hide ) = > {
this . hideFlow = ! ! hide ;
this . setFlowEnabled ( ) ;
} ) ;
2023-06-29 11:22:33 -04:00
this . da $ = this . stateService . difficultyAdjustment $ . pipe (
tap ( ( ) = > {
this . now = Date . now ( ) ;
} )
) ;
2021-07-24 19:26:29 -03:00
2022-10-11 20:54:17 +00:00
this . urlFragmentSubscription = this . route . fragment . subscribe ( ( fragment ) = > {
this . fragmentParams = new URLSearchParams ( fragment || '' ) ;
const vin = parseInt ( this . fragmentParams . get ( 'vin' ) , 10 ) ;
const vout = parseInt ( this . fragmentParams . get ( 'vout' ) , 10 ) ;
this . inputIndex = ( ! isNaN ( vin ) && vin >= 0 ) ? vin : null ;
this . outputIndex = ( ! isNaN ( vout ) && vout >= 0 ) ? vout : null ;
} ) ;
2023-07-10 13:57:18 +09:00
this . blocksSubscription = this . stateService . blocks $ . subscribe ( ( blocks ) = > {
this . latestBlock = blocks [ 0 ] ;
} ) ;
2021-05-21 17:06:53 +04:00
this . fetchCpfpSubscription = this . fetchCpfp $
. pipe (
2021-07-06 13:56:32 -03:00
switchMap ( ( txId ) = >
this . apiService
. getCpfpinfo $ ( txId )
2022-12-01 11:34:11 +09:00
. pipe ( retryWhen ( ( errors ) = > errors . pipe (
mergeMap ( ( error ) = > {
if ( ! this . tx ? . status || this . tx . status . confirmed ) {
return throwError ( error ) ;
} else {
return of ( null ) ;
}
} ) ,
delay ( 2000 )
2023-01-16 12:04:24 -06:00
) ) ,
catchError ( ( ) = > {
return of ( null ) ;
} )
)
2022-12-01 11:34:11 +09:00
) ,
catchError ( ( ) = > {
return of ( null ) ;
} )
2021-05-21 17:06:53 +04:00
)
. subscribe ( ( cpfpInfo ) = > {
2023-08-27 00:30:33 +09:00
this . setCpfpInfo ( cpfpInfo ) ;
2021-05-21 17:06:53 +04:00
} ) ;
2022-12-09 10:32:58 -06:00
this . fetchRbfSubscription = this . fetchRbfHistory $
. pipe (
switchMap ( ( txId ) = >
this . apiService
. getRbfHistory $ ( txId )
) ,
catchError ( ( ) = > {
2022-12-13 17:11:37 -06:00
return of ( null ) ;
2022-12-09 10:32:58 -06:00
} )
2022-12-13 17:11:37 -06:00
) . subscribe ( ( rbfResponse ) = > {
2022-12-17 09:39:06 -06:00
this . rbfInfo = rbfResponse ? . replacements ;
2022-12-13 17:11:37 -06:00
this . rbfReplaces = rbfResponse ? . replaces || null ;
2022-12-09 10:32:58 -06:00
} ) ;
this . fetchCachedTxSubscription = this . fetchCachedTx $
. pipe (
2023-07-11 15:49:38 +09:00
tap ( ( ) = > {
this . loadingCachedTx = true ;
} ) ,
2022-12-09 10:32:58 -06:00
switchMap ( ( txId ) = >
this . apiService
. getRbfCachedTx $ ( txId )
) ,
catchError ( ( ) = > {
return of ( null ) ;
} )
) . subscribe ( ( tx ) = > {
2023-07-11 15:49:38 +09:00
this . loadingCachedTx = false ;
2022-12-09 10:32:58 -06:00
if ( ! tx ) {
2023-03-09 02:34:21 -06:00
this . seoService . logSoft404 ( ) ;
2022-12-09 10:32:58 -06:00
return ;
}
2023-03-09 02:34:21 -06:00
this . seoService . clearSoft404 ( ) ;
2022-12-09 10:32:58 -06:00
2022-12-14 08:49:35 -06:00
if ( ! this . tx ) {
this . tx = tx ;
this . setFeatures ( ) ;
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 . graphExpanded = false ;
2023-05-05 15:12:05 -07:00
this . transactionTime = tx . firstSeen || 0 ;
2022-12-14 08:49:35 -06:00
this . setupGraph ( ) ;
2022-12-09 10:32:58 -06:00
this . fetchRbfHistory $ . next ( this . tx . txid ) ;
2023-03-04 03:16:59 -06:00
this . txRbfInfoSubscription = this . stateService . txRbfInfo $ . subscribe ( ( rbfInfo ) = > {
if ( this . tx ) {
this . rbfInfo = rbfInfo ;
}
} ) ;
2022-12-09 10:32:58 -06:00
}
} ) ;
2023-12-05 13:40:17 +00:00
this . fetchAccelerationSubscription = this . fetchAcceleration $ . pipe (
2024-01-02 11:25:50 +07:00
filter ( ( ) = > this . stateService . env . ACCELERATOR === true ) ,
2023-12-05 13:40:17 +00:00
tap ( ( ) = > {
this . accelerationInfo = null ;
2024-04-08 13:45:05 +00:00
this . setIsAccelerated ( ) ;
2023-12-05 13:40:17 +00:00
} ) ,
2024-04-04 08:22:55 +00:00
switchMap ( ( blockHeight : number ) = > {
return this . servicesApiService . getAccelerationHistory $ ( { blockHeight } ) ;
2023-12-05 13:40:17 +00:00
} ) ,
catchError ( ( ) = > {
return of ( null ) ;
} )
) . subscribe ( ( accelerationHistory ) = > {
for ( const acceleration of accelerationHistory ) {
2024-03-12 17:04:37 +09:00
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-04 09:42:49 +00:00
acceleration . acceleratedFeeRate = Math . max ( acceleration . effectiveFee , acceleration . effectiveFee + boostCost ) / acceleration . effectiveVsize ;
2024-04-08 09:13:24 +00:00
acceleration . boost = boostCost ;
2024-04-04 09:42:49 +00:00
2023-12-05 13:40:17 +00:00
this . accelerationInfo = acceleration ;
2024-04-08 13:45:05 +00:00
this . setIsAccelerated ( ) ;
2023-12-05 13:40:17 +00:00
}
}
} ) ;
2024-03-07 23:21:27 +00:00
this . miningSubscription = this . fetchMiningInfo $ . pipe (
filter ( ( target ) = > target . txid === this . txId ) ,
tap ( ( ) = > {
this . pool = null ;
2024-04-17 16:41:30 +02:00
} ) ,
switchMap ( ( { hash , height } ) = > {
const foundBlock = this . cacheService . getCachedBlock ( height ) || null ;
return foundBlock ? of ( foundBlock . extras . pool ) : this . apiService . getBlock $ ( hash ) . pipe (
map ( block = > block . extras . pool ) ,
retry ( { count : 3 , delay : 2000 } ) ,
catchError ( ( ) = > of ( null ) )
) ;
} ) ,
catchError ( ( e ) = > {
return of ( null ) ;
} )
) . subscribe ( pool = > {
this . pool = pool ;
} ) ;
this . auditSubscription = this . fetchMiningInfo $ . pipe (
filter ( ( target ) = > target . txid === this . txId ) ,
tap ( ( ) = > {
2024-03-07 23:21:27 +00:00
this . auditStatus = null ;
} ) ,
switchMap ( ( { hash , height , txid } ) = > {
const auditAvailable = this . isAuditAvailable ( height ) ;
2024-03-08 15:21:37 +00:00
const isCoinbase = this . tx . vin . some ( v = > v . is_coinbase ) ;
const fetchAudit = auditAvailable && ! isCoinbase ;
2024-04-17 16:41:30 +02:00
return 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 ,
} ;
} ) ,
retry ( { count : 3 , delay : 2000 } ) ,
catchError ( ( ) = > {
return of ( null ) ;
} )
) : of ( isCoinbase ? { coinbase : true } : null ) ;
2024-03-07 23:21:27 +00:00
} ) ,
2024-04-08 14:08:11 +00:00
catchError ( ( e ) = > {
2024-03-07 23:21:27 +00:00
return of ( null ) ;
} )
2024-04-17 16:41:30 +02:00
) . subscribe ( auditStatus = > {
2024-03-07 23:21:27 +00:00
this . auditStatus = auditStatus ;
2024-04-08 13:45:05 +00:00
this . setIsAccelerated ( ) ;
2024-03-07 23:21:27 +00:00
} ) ;
2023-04-21 08:40:21 +09:00
this . mempoolPositionSubscription = this . stateService . mempoolTxPosition $ . subscribe ( txPosition = > {
2023-06-29 11:22:33 -04:00
this . now = Date . now ( ) ;
2023-04-21 08:40:21 +09:00
if ( txPosition && txPosition . txid === this . txId && txPosition . position ) {
this . mempoolPosition = txPosition . position ;
if ( this . tx && ! this . tx . status . confirmed ) {
this . stateService . markBlock $ . next ( {
2023-06-19 15:19:34 -04:00
txid : txPosition.txid ,
2023-04-21 08:40:21 +09:00
mempoolPosition : this.mempoolPosition
} ) ;
2023-05-03 10:30:45 -06:00
this . txInBlockIndex = this . mempoolPosition . block ;
2023-08-27 00:30:33 +09:00
if ( txPosition . cpfp !== undefined ) {
2024-05-28 21:33:09 +00:00
if ( txPosition . position . acceleratedBy ) {
txPosition . cpfp . acceleratedBy = txPosition . position . acceleratedBy ;
}
2023-08-27 00:30:33 +09:00
this . setCpfpInfo ( txPosition . cpfp ) ;
2024-05-28 21:33:09 +00:00
} else if ( ( this . tx ? . acceleration && txPosition . position . acceleratedBy ) ) {
this . tx . acceleratedBy = txPosition . position . acceleratedBy ;
2023-08-27 00:30:33 +09:00
}
2023-04-21 08:40:21 +09:00
}
} else {
this . mempoolPosition = null ;
2022-12-09 10:32:58 -06:00
}
} ) ;
2023-10-28 00:33:29 +00:00
this . subscription = this . zoneService . wrapObservable ( this . route . paramMap
2021-07-06 13:56:32 -03:00
. pipe (
2021-08-17 20:20:25 +03:00
switchMap ( ( params : ParamMap ) = > {
2021-10-19 23:24:12 +04:00
const urlMatch = ( params . get ( 'id' ) || '' ) . split ( ':' ) ;
2022-10-04 23:30:14 +00:00
if ( urlMatch . length === 2 && urlMatch [ 1 ] . length === 64 ) {
2022-10-11 20:54:17 +00:00
const vin = parseInt ( urlMatch [ 0 ] , 10 ) ;
2022-10-04 23:30:14 +00:00
this . txId = urlMatch [ 1 ] ;
2022-10-11 20:54:17 +00:00
// rewrite legacy vin syntax
if ( ! isNaN ( vin ) ) {
this . fragmentParams . set ( 'vin' , vin . toString ( ) ) ;
this . fragmentParams . delete ( 'vout' ) ;
}
this . router . navigate ( [ this . relativeUrlPipe . transform ( '/tx' ) , this . txId ] , {
queryParamsHandling : 'merge' ,
fragment : this.fragmentParams.toString ( ) ,
} ) ;
2022-10-04 23:30:14 +00:00
} else {
this . txId = urlMatch [ 0 ] ;
2022-10-11 20:54:17 +00:00
const vout = parseInt ( urlMatch [ 1 ] , 10 ) ;
if ( urlMatch . length > 1 && ! isNaN ( vout ) ) {
// rewrite legacy vout syntax
this . fragmentParams . set ( 'vout' , vout . toString ( ) ) ;
this . fragmentParams . delete ( 'vin' ) ;
this . router . navigate ( [ this . relativeUrlPipe . transform ( '/tx' ) , this . txId ] , {
queryParamsHandling : 'merge' ,
fragment : this.fragmentParams.toString ( ) ,
} ) ;
}
2022-10-04 23:30:14 +00:00
}
2021-07-06 13:56:32 -03:00
this . seoService . setTitle (
$localize ` :@@bisq.transaction.browser-title:Transaction: ${ this . txId } :INTERPOLATION: `
2020-04-13 01:26:53 +07:00
) ;
2024-01-20 20:07:25 +07:00
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 } . ` ) ;
2021-07-06 13:56:32 -03:00
this . resetTransaction ( ) ;
return merge (
of ( true ) ,
this . stateService . connectionState $ . pipe (
filter (
2023-03-04 03:16:59 -06:00
( state ) = > state === 2 && this . tx && ! this . tx . status ? . confirmed
2021-07-06 13:56:32 -03:00
)
)
) ;
} ) ,
switchMap ( ( ) = > {
let transactionObservable$ : Observable < Transaction > ;
2022-12-27 05:36:58 -06:00
const cached = this . cacheService . getTxFromCache ( this . txId ) ;
2022-11-07 20:05:33 -06:00
if ( cached && cached . fee !== - 1 ) {
transactionObservable $ = of ( cached ) ;
2021-07-06 13:56:32 -03:00
} else {
transactionObservable $ = this . electrsApiService
. getTransaction $ ( this . txId )
. pipe (
catchError ( this . handleLoadElectrsTransactionError . bind ( this ) )
) ;
}
return merge (
transactionObservable $ ,
this . stateService . mempoolTransactions $
) ;
2021-08-18 14:05:40 +03:00
} ) ,
switchMap ( ( tx ) = > {
2021-12-27 22:54:45 +04:00
if ( this . network === 'liquid' || this . network === 'liquidtestnet' ) {
2021-08-18 14:05:40 +03:00
return from ( this . liquidUnblinding . checkUnblindedTx ( tx ) )
. pipe (
catchError ( ( error ) = > {
this . errorUnblinded = error ;
return of ( tx ) ;
} )
2021-11-12 20:24:15 +04:00
) ;
2021-08-18 14:05:40 +03:00
}
return of ( tx ) ;
2021-07-06 13:56:32 -03:00
} )
2023-10-28 00:33:29 +00:00
) )
2021-08-18 14:05:40 +03:00
. subscribe ( ( tx : Transaction ) = > {
2021-07-06 13:56:32 -03:00
if ( ! tx ) {
2023-03-04 03:16:59 -06:00
this . fetchCachedTx $ . next ( this . txId ) ;
2023-08-05 18:23:12 +09:00
this . seoService . logSoft404 ( ) ;
2021-07-06 13:56:32 -03:00
return ;
}
2023-08-05 18:23:12 +09:00
this . seoService . clearSoft404 ( ) ;
2021-08-18 03:34:17 +03:00
2021-07-06 13:56:32 -03:00
this . tx = tx ;
2023-03-13 12:48:01 +09:00
this . setFeatures ( ) ;
2023-03-06 00:02:21 -06:00
this . isCached = false ;
2021-07-06 13:56:32 -03:00
if ( tx . fee === undefined ) {
this . tx . fee = 0 ;
}
2023-09-19 00:18:52 +00:00
if ( this . tx . sigops != null ) {
this . sigops = this . tx . sigops ;
this . adjustedVsize = Math . max ( this . tx . weight / 4 , this . sigops * 5 ) ;
}
2021-07-06 13:56:32 -03:00
this . tx . feePerVsize = tx . fee / ( tx . weight / 4 ) ;
this . isLoadingTx = false ;
this . error = undefined ;
2023-07-11 15:49:38 +09:00
this . loadingCachedTx = false ;
2021-07-06 13:56:32 -03:00
this . waitingForTransaction = false ;
2022-03-06 18:27:13 +01:00
this . websocketService . startTrackTransaction ( tx . txid ) ;
2022-11-22 16:30:04 +09:00
this . graphExpanded = false ;
2022-09-16 20:50:12 +00:00
this . setupGraph ( ) ;
2021-03-18 23:47:40 +07:00
2023-03-04 03:16:59 -06:00
if ( ! tx . status ? . confirmed ) {
if ( tx . firstSeen ) {
this . transactionTime = tx . firstSeen ;
} else {
2023-05-05 15:12:05 -07:00
this . getTransactionTime ( ) ;
2023-03-04 03:16:59 -06:00
}
2022-03-06 18:27:13 +01:00
} else {
2024-04-04 08:22:55 +00:00
this . fetchAcceleration $ . next ( tx . status . block_height ) ;
2024-03-07 23:21:27 +00:00
this . fetchMiningInfo $ . next ( { hash : tx.status.block_hash , height : tx.status.block_height , txid : tx.txid } ) ;
2023-05-05 15:12:05 -07:00
this . transactionTime = 0 ;
2021-07-06 13:56:32 -03:00
}
2023-03-04 03:16:59 -06:00
if ( this . tx ? . status ? . confirmed ) {
2021-07-06 13:56:32 -03:00
this . stateService . markBlock $ . next ( {
blockHeight : tx.status.block_height ,
} ) ;
2022-11-27 13:46:54 +09:00
this . fetchCpfp $ . next ( this . tx . txid ) ;
2021-07-06 13:56:32 -03:00
} else {
if ( tx . cpfpChecked ) {
this . stateService . markBlock $ . next ( {
2023-06-19 15:19:34 -04:00
txid : tx.txid ,
2021-07-06 13:56:32 -03:00
txFeePerVSize : tx.effectiveFeePerVsize ,
2023-04-21 08:40:21 +09:00
mempoolPosition : this.mempoolPosition ,
2021-07-06 13:56:32 -03:00
} ) ;
2024-04-05 06:27:12 +00:00
this . setCpfpInfo ( {
2021-07-06 13:56:32 -03:00
ancestors : tx.ancestors ,
bestDescendant : tx.bestDescendant ,
2024-04-05 06:27:12 +00:00
} ) ;
2023-07-16 12:53:55 +09:00
const hasRelatives = ! ! ( tx . ancestors ? . length || tx . bestDescendant ) ;
2023-05-31 12:11:56 -04:00
this . hasEffectiveFeeRate = hasRelatives || ( tx . effectiveFeePerVsize && ( Math . abs ( tx . effectiveFeePerVsize - tx . feePerVsize ) > 0.01 ) ) ;
2021-07-06 13:56:32 -03:00
} else {
this . fetchCpfp $ . next ( this . tx . txid ) ;
}
}
2023-03-04 03:16:59 -06:00
this . fetchRbfHistory $ . next ( this . tx . txid ) ;
2024-03-09 16:32:21 +01:00
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 ) ;
2023-02-21 12:36:43 +09:00
} )
) . subscribe ( ) ;
2023-08-30 20:26:07 +09:00
2022-10-11 20:54:17 +00:00
setTimeout ( ( ) = > { this . applyFragment ( ) ; } , 0 ) ;
2023-10-28 00:33:29 +00:00
this . cd . detectChanges ( ) ;
2021-07-06 13:56:32 -03:00
} ,
( error ) = > {
this . error = error ;
2023-03-09 02:34:21 -06:00
this . seoService . logSoft404 ( ) ;
2021-07-06 13:56:32 -03:00
this . isLoadingTx = false ;
2021-03-18 23:47:40 +07:00
}
2021-07-06 13:56:32 -03:00
) ;
2023-07-08 01:07:06 -04:00
this . stateService . txConfirmed $ . subscribe ( ( [ txConfirmed , block ] ) = > {
2023-05-30 16:36:49 -04:00
if ( txConfirmed && this . tx && ! this . tx . status . confirmed && txConfirmed === this . tx . txid ) {
2021-07-06 13:56:32 -03:00
this . tx . status = {
confirmed : true ,
block_height : block.height ,
block_hash : block.id ,
block_time : block.timestamp ,
} ;
this . stateService . markBlock $ . next ( { blockHeight : block.height } ) ;
2024-03-12 17:04:37 +09:00
if ( this . tx . acceleration || ( this . accelerationInfo && [ 'accelerating' , 'completed_provisional' , 'completed' ] . includes ( this . accelerationInfo . status ) ) ) {
2023-12-15 23:09:24 +07:00
this . audioService . playSound ( 'wind-chimes-harp-ascend' ) ;
} else {
this . audioService . playSound ( 'magic' ) ;
}
2024-04-04 08:22:55 +00:00
this . fetchAcceleration $ . next ( block . height ) ;
2024-03-07 23:21:27 +00:00
this . fetchMiningInfo $ . next ( { hash : block.id , height : block.height , txid : this.tx.txid } ) ;
2020-03-22 17:44:36 +07:00
}
2020-02-16 22:15:07 +07:00
} ) ;
2022-03-08 14:49:25 +01:00
this . txReplacedSubscription = this . stateService . txReplaced $ . subscribe ( ( rbfTransaction ) = > {
2022-03-08 18:54:49 +01:00
if ( ! this . tx ) {
2022-03-08 14:49:25 +01:00
this . error = new Error ( ) ;
2023-07-11 15:49:38 +09:00
this . loadingCachedTx = false ;
2022-03-08 14:49:25 +01:00
this . waitingForTransaction = false ;
}
this . rbfTransaction = rbfTransaction ;
2022-12-09 10:32:58 -06:00
this . replaced = true ;
2023-09-29 00:02:01 +01:00
this . stateService . markBlock $ . next ( { } ) ;
2022-12-09 10:32:58 -06:00
if ( rbfTransaction && ! this . tx ) {
this . fetchCachedTx $ . next ( this . txId ) ;
}
2022-03-08 14:49:25 +01:00
} ) ;
2022-09-29 15:41:14 +00:00
2022-12-14 08:49:35 -06:00
this . txRbfInfoSubscription = this . stateService . txRbfInfo $ . subscribe ( ( rbfInfo ) = > {
if ( this . tx ) {
this . rbfInfo = rbfInfo ;
}
} ) ;
2022-09-29 15:41:14 +00:00
this . queryParamsSubscription = this . route . queryParams . subscribe ( ( params ) = > {
if ( params . showFlow === 'false' ) {
2022-10-11 17:01:23 +00:00
this . overrideFlowPreference = false ;
} else if ( params . showFlow === 'true' ) {
this . overrideFlowPreference = true ;
2022-09-29 15:41:14 +00:00
} else {
2022-10-11 17:01:23 +00:00
this . overrideFlowPreference = null ;
2022-09-29 15:41:14 +00:00
}
2022-10-11 17:01:23 +00:00
this . setFlowEnabled ( ) ;
this . setGraphSize ( ) ;
2022-09-29 15:41:14 +00:00
} ) ;
2022-09-16 20:50:12 +00:00
2023-05-03 13:58:08 -06:00
this . mempoolBlocksSubscription = this . stateService . mempoolBlocks $ . subscribe ( ( mempoolBlocks ) = > {
2023-06-29 11:22:33 -04:00
this . now = Date . now ( ) ;
2020-04-13 01:26:53 +07:00
2023-05-03 10:30:45 -06:00
if ( ! this . tx || this . mempoolPosition ) {
2021-07-06 13:56:32 -03:00
return ;
}
2020-03-23 04:07:31 +07:00
2021-07-06 13:56:32 -03:00
const txFeePerVSize =
this . tx . effectiveFeePerVsize || this . tx . fee / ( this . tx . weight / 4 ) ;
2020-03-23 04:07:31 +07:00
2023-05-03 10:02:03 -06:00
let found = false ;
2023-05-03 13:58:08 -06:00
this . txInBlockIndex = 0 ;
2021-07-06 13:56:32 -03:00
for ( const block of mempoolBlocks ) {
2023-05-03 10:02:03 -06:00
for ( let i = 0 ; i < block . feeRange . length - 1 && ! found ; i ++ ) {
2021-07-06 13:56:32 -03:00
if (
txFeePerVSize <= block . feeRange [ i + 1 ] &&
txFeePerVSize >= block . feeRange [ i ]
) {
this . txInBlockIndex = mempoolBlocks . indexOf ( block ) ;
2023-05-03 10:02:03 -06:00
found = true ;
2020-03-23 04:07:31 +07:00
}
}
2021-07-06 13:56:32 -03:00
}
2024-02-01 12:13:34 +01:00
if ( ! found && mempoolBlocks . length && txFeePerVSize < mempoolBlocks [ mempoolBlocks . length - 1 ] . feeRange [ 0 ] ) {
2023-05-03 13:58:08 -06:00
this . txInBlockIndex = 7 ;
}
2021-07-06 13:56:32 -03:00
} ) ;
2020-03-23 04:07:31 +07:00
}
2023-05-03 13:58:08 -06:00
ngAfterViewInit ( ) : void {
this . setGraphSize ( ) ;
}
2023-08-24 14:17:31 +02:00
dismissAccelAlert ( ) : void {
this . storageService . setValue ( 'accel-cta-type' , 'button' ) ;
this . accelerateCtaType = 'button' ;
}
2023-08-26 09:52:55 +02:00
onAccelerateClicked() {
2023-08-24 14:17:31 +02:00
if ( ! this . txId ) {
return ;
}
2024-04-14 13:54:25 +09:00
document . location . hash = '#accelerate' ;
2024-02-14 21:47:42 +00:00
this . enterpriseService . goal ( 8 ) ;
2023-08-24 14:17:31 +02:00
this . showAccelerationSummary = true && this . acceleratorAvailable ;
2023-08-26 09:52:55 +02:00
this . scrollIntoAccelPreview = ! this . scrollIntoAccelPreview ;
return false ;
2023-08-24 14:17:31 +02:00
}
2023-05-03 13:58:08 -06:00
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 ;
2023-08-05 18:23:12 +09:00
this . seoService . logSoft404 ( ) ;
2023-05-03 13:58:08 -06:00
this . isLoadingTx = false ;
return of ( false ) ;
}
2020-02-28 01:09:07 +07:00
getTransactionTime() {
2021-07-06 13:56:32 -03:00
this . apiService
. getTransactionTimes $ ( [ this . tx . txid ] )
2020-02-28 01:09:07 +07:00
. subscribe ( ( transactionTimes ) = > {
2024-04-11 06:03:14 +00:00
if ( transactionTimes ? . length && transactionTimes [ 0 ] ) {
2023-05-05 15:12:05 -07:00
this . transactionTime = transactionTimes [ 0 ] ;
2024-04-11 06:03:14 +00:00
} else {
setTimeout ( ( ) = > {
this . getTransactionTime ( ) ;
} , 2000 ) ;
2023-05-05 15:12:05 -07:00
}
2020-02-28 01:09:07 +07:00
} ) ;
}
2023-08-27 00:30:33 +09:00
setCpfpInfo ( cpfpInfo : CpfpInfo ) : void {
if ( ! cpfpInfo || ! this . tx ) {
this . cpfpInfo = null ;
2024-04-05 06:27:12 +00:00
this . hasCpfp = false ;
2023-08-27 00:30:33 +09:00
this . hasEffectiveFeeRate = false ;
return ;
}
2024-04-10 11:12:44 +00:00
const firstCpfp = this . cpfpInfo == null ;
2023-08-27 00:30:33 +09:00
// 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 {
2024-04-08 11:36:22 +00:00
this . tx . effectiveFeePerVsize = cpfpInfo . effectiveFeePerVsize || this . tx . effectiveFeePerVsize || this . tx . feePerVsize || ( this . tx . fee / ( this . tx . weight / 4 ) ) ;
2023-08-27 00:30:33 +09:00
}
if ( cpfpInfo . acceleration ) {
this . tx . acceleration = cpfpInfo . acceleration ;
2024-05-26 20:38:28 +00:00
this . tx . acceleratedBy = cpfpInfo . acceleratedBy ;
2024-04-10 11:12:44 +00:00
this . setIsAccelerated ( firstCpfp ) ;
2023-08-27 00:30:33 +09:00
}
this . cpfpInfo = cpfpInfo ;
2023-09-19 00:18:52 +00:00
if ( this . cpfpInfo . adjustedVsize && this . cpfpInfo . sigops != null ) {
this . sigops = this . cpfpInfo . sigops ;
this . adjustedVsize = this . cpfpInfo . adjustedVsize ;
}
2024-04-05 06:27:12 +00:00
this . hasCpfp = ! ! ( this . cpfpInfo && ( this . cpfpInfo . bestDescendant || this . cpfpInfo . descendants ? . length || this . cpfpInfo . ancestors ? . length ) ) ;
2023-08-27 00:30:33 +09:00
this . hasEffectiveFeeRate = hasRelatives || ( this . tx . effectiveFeePerVsize && ( Math . abs ( this . tx . effectiveFeePerVsize - this . tx . feePerVsize ) > 0.01 ) ) ;
}
2024-04-10 11:12:44 +00:00
setIsAccelerated ( initialState : boolean = false ) {
2024-04-08 13:45:05 +00:00
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 ) ) ) ) ;
2024-04-10 11:12:44 +00:00
if ( this . isAcceleration && initialState ) {
this . showAccelerationSummary = false ;
}
2024-05-26 20:38:28 +00:00
if ( this . isAcceleration ) {
// this immediately returns cached stats if we fetched them recently
this . miningService . getMiningStats ( '1w' ) . subscribe ( stats = > {
this . miningStats = stats ;
} ) ;
}
2024-04-08 13:45:05 +00:00
}
2023-03-13 12:48:01 +09:00
setFeatures ( ) : void {
if ( this . tx ) {
2023-03-14 13:02:50 +09:00
this . segwitEnabled = ! this . tx . status . confirmed || isFeatureActive ( this . stateService . network , this . tx . status . block_height , 'segwit' ) ;
this . taprootEnabled = ! this . tx . status . confirmed || isFeatureActive ( this . stateService . network , this . tx . status . block_height , 'taproot' ) ;
this . rbfEnabled = ! this . tx . status . confirmed || isFeatureActive ( this . stateService . network , this . tx . status . block_height , 'rbf' ) ;
2024-03-22 09:52:27 +00:00
this . tx . flags = getTransactionFlags ( this . tx ) ;
this . filters = this . tx . flags ? toFilters ( this . tx . flags ) . filter ( f = > f . txPage ) : [ ] ;
2023-03-13 12:48:01 +09:00
} else {
this . segwitEnabled = false ;
this . taprootEnabled = false ;
this . rbfEnabled = false ;
}
this . featuresEnabled = this . segwitEnabled || this . taprootEnabled || this . rbfEnabled ;
}
2024-03-07 23:21:27 +00:00
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 ;
}
2020-04-13 01:26:53 +07:00
resetTransaction() {
2024-04-16 16:01:52 +09:00
this . firstLoad = false ;
2020-04-13 01:26:53 +07:00
this . error = undefined ;
this . tx = null ;
2023-03-13 12:48:01 +09:00
this . setFeatures ( ) ;
2020-04-13 01:26:53 +07:00
this . waitingForTransaction = false ;
this . isLoadingTx = true ;
2020-06-08 18:55:53 +07:00
this . rbfTransaction = undefined ;
2022-12-09 10:32:58 -06:00
this . replaced = false ;
2020-04-13 01:26:53 +07:00
this . transactionTime = - 1 ;
2021-03-22 18:04:50 +07:00
this . cpfpInfo = null ;
2023-09-19 00:18:52 +00:00
this . adjustedVsize = null ;
this . sigops = null ;
2023-05-31 12:11:56 -04:00
this . hasEffectiveFeeRate = false ;
2022-12-17 09:39:06 -06:00
this . rbfInfo = null ;
2022-12-09 10:32:58 -06:00
this . rbfReplaces = [ ] ;
2024-03-22 09:52:27 +00:00
this . filters = [ ] ;
2021-03-22 18:04:50 +07:00
this . showCpfpDetails = false ;
2023-12-05 13:40:17 +00:00
this . accelerationInfo = null ;
2023-05-03 10:30:45 -06:00
this . txInBlockIndex = null ;
this . mempoolPosition = null ;
2024-03-07 23:21:27 +00:00
this . pool = null ;
this . auditStatus = null ;
2020-04-13 01:26:53 +07:00
document . body . scrollTo ( 0 , 0 ) ;
2024-04-08 13:45:05 +00:00
this . isAcceleration = false ;
2020-04-12 03:03:51 +07:00
this . leaveTransaction ( ) ;
}
leaveTransaction() {
this . websocketService . stopTrackingTransaction ( ) ;
2020-03-22 17:44:36 +07:00
this . stateService . markBlock $ . next ( { } ) ;
2020-02-19 23:50:23 +07:00
}
2020-04-13 01:26:53 +07:00
2021-03-22 18:04:50 +07:00
roundToOneDecimal ( cpfpTx : any ) : number {
return + ( cpfpTx . fee / ( cpfpTx . weight / 4 ) ) . toFixed ( 1 ) ;
}
2024-05-26 20:38:28 +00:00
getUnacceleratedFeeRate ( tx : Transaction , accelerated : boolean ) : number {
if ( accelerated ) {
let ancestorVsize = tx . weight / 4 ;
let ancestorFee = tx . fee ;
for ( const ancestor of tx . ancestors || [ ] ) {
ancestorVsize += ( ancestor . weight / 4 ) ;
ancestorFee += ancestor . fee ;
}
return Math . min ( tx . fee / ( tx . weight / 4 ) , ( ancestorFee / ancestorVsize ) ) ;
} else {
return tx . effectiveFeePerVsize ;
}
}
2022-09-16 20:50:12 +00:00
setupGraph() {
2022-09-23 19:03:21 +00:00
this . maxInOut = Math . min ( this . inOutLimit , Math . max ( this . tx ? . vin ? . length || 1 , this . tx ? . vout ? . length + 1 || 1 ) ) ;
2022-11-22 16:30:04 +09:00
this . graphHeight = this . graphExpanded ? this . maxInOut * 15 : Math.min ( 360 , this . maxInOut * 80 ) ;
2022-09-16 20:50:12 +00:00
}
2022-09-29 15:41:14 +00:00
toggleGraph() {
2022-10-11 17:01:23 +00:00
const showFlow = ! this . flowEnabled ;
this . stateService . hideFlow . next ( ! showFlow ) ;
2022-09-29 15:41:14 +00:00
this . router . navigate ( [ ] , {
relativeTo : this.route ,
2022-10-11 17:01:23 +00:00
queryParams : { showFlow : showFlow } ,
2022-09-29 15:41:14 +00:00
queryParamsHandling : 'merge' ,
fragment : 'flow'
} ) ;
}
2022-10-11 17:01:23 +00:00
setFlowEnabled() {
this . flowEnabled = ( this . overrideFlowPreference != null ? this . overrideFlowPreference : ! this . hideFlow ) ;
}
2022-09-16 20:50:12 +00:00
expandGraph() {
this . graphExpanded = true ;
2022-11-22 16:30:04 +09:00
this . graphHeight = this . maxInOut * 15 ;
2022-09-16 20:50:12 +00:00
}
collapseGraph() {
this . graphExpanded = false ;
2022-11-22 16:30:04 +09:00
this . graphHeight = Math . min ( 360 , this . maxInOut * 80 ) ;
2022-09-16 20:50:12 +00:00
}
2022-10-11 20:54:17 +00:00
// simulate normal anchor fragment behavior
applyFragment ( ) : void {
const anchor = Array . from ( this . fragmentParams . entries ( ) ) . find ( ( [ frag , value ] ) = > value === '' ) ;
2023-09-03 12:20:30 +03:00
if ( anchor ? . length ) {
if ( anchor [ 0 ] === 'accelerate' ) {
setTimeout ( this . onAccelerateClicked . bind ( this ) , 100 ) ;
} else {
const anchorElement = document . getElementById ( anchor [ 0 ] ) ;
if ( anchorElement ) {
anchorElement . scrollIntoView ( ) ;
}
2022-10-11 20:54:17 +00:00
}
}
2022-10-04 21:00:46 +00:00
}
2022-09-16 20:50:12 +00:00
@HostListener ( 'window:resize' , [ '$event' ] )
setGraphSize ( ) : void {
2023-03-13 12:48:01 +09:00
this . isMobile = window . innerWidth < 850 ;
2023-10-28 00:33:29 +00:00
if ( this . graphContainer ? . nativeElement && this . stateService . isBrowser ) {
2023-03-10 12:37:55 +09:00
setTimeout ( ( ) = > {
2023-10-28 00:33:29 +00:00
if ( this . graphContainer ? . nativeElement ? . clientWidth ) {
2023-04-05 07:11:13 +09:00
this . graphWidth = this . graphContainer . nativeElement . clientWidth ;
} else {
setTimeout ( ( ) = > { this . setGraphSize ( ) ; } , 1 ) ;
}
2023-03-10 12:37:55 +09:00
} , 1 ) ;
2022-09-16 20:50:12 +00:00
}
}
2020-04-13 01:26:53 +07:00
ngOnDestroy() {
this . subscription . unsubscribe ( ) ;
2021-04-27 02:13:48 +04:00
this . fetchCpfpSubscription . unsubscribe ( ) ;
2022-12-09 10:32:58 -06:00
this . fetchRbfSubscription . unsubscribe ( ) ;
this . fetchCachedTxSubscription . unsubscribe ( ) ;
2023-12-05 13:40:17 +00:00
this . fetchAccelerationSubscription . unsubscribe ( ) ;
2022-03-08 14:49:25 +01:00
this . txReplacedSubscription . unsubscribe ( ) ;
2022-12-14 08:49:35 -06:00
this . txRbfInfoSubscription . unsubscribe ( ) ;
2022-09-29 15:41:14 +00:00
this . queryParamsSubscription . unsubscribe ( ) ;
2022-10-11 17:01:23 +00:00
this . flowPrefSubscription . unsubscribe ( ) ;
2022-10-11 20:54:17 +00:00
this . urlFragmentSubscription . unsubscribe ( ) ;
2023-05-03 13:58:08 -06:00
this . mempoolBlocksSubscription . unsubscribe ( ) ;
2023-04-21 08:40:21 +09:00
this . mempoolPositionSubscription . unsubscribe ( ) ;
2023-05-03 10:30:45 -06:00
this . mempoolBlocksSubscription . unsubscribe ( ) ;
2023-07-10 13:57:18 +09:00
this . blocksSubscription . unsubscribe ( ) ;
2024-03-07 23:21:27 +00:00
this . miningSubscription ? . unsubscribe ( ) ;
2024-04-17 16:41:30 +02:00
this . auditSubscription ? . unsubscribe ( ) ;
2024-03-09 16:32:21 +01:00
this . currencyChangeSubscription ? . unsubscribe ( ) ;
2020-04-13 01:26:53 +07:00
this . leaveTransaction ( ) ;
}
2020-02-16 22:15:07 +07:00
}