2022-09-16 20:50:12 +00:00
import { Component , OnInit , AfterViewInit , OnDestroy , HostListener , ViewChild , ElementRef } 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 ,
2022-12-01 11:34:11 +09:00
map ,
2023-02-21 12:36:43 +09:00
mergeMap ,
tap
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' ;
2023-07-10 13:57:18 +09:00
import { of , merge , Subscription , Observable , Subject , from , throwError } 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' ;
2023-06-29 11:22:33 -04:00
import { BlockExtended , CpfpInfo , RbfTree , MempoolPosition , DifficultyAdjustment } 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' ;
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 ;
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 ;
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 ;
2023-09-19 00:18:52 +00:00
sigops : number | null ;
adjustedVsize : number | null ;
2021-03-22 18:04:50 +07:00
showCpfpDetails = false ;
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 > ( ) ;
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 ;
graphWidth : number = 1000 ;
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 ;
2023-02-21 12:36:43 +09:00
blockConversion : Price ;
2022-09-17 01:20:08 +00:00
tooltipPosition : { x : number , y : number } ;
2023-03-13 12:48:01 +09:00
isMobile : boolean ;
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 ;
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-02-21 12:36:43 +09:00
private seoService : SeoService ,
private priceService : PriceService ,
2023-08-24 14:17:31 +02:00
private storageService : StorageService
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 === '' ;
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-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 ) {
this . setCpfpInfo ( txPosition . cpfp ) ;
}
2023-04-21 08:40:21 +09:00
}
} else {
this . mempoolPosition = null ;
2022-12-09 10:32:58 -06:00
}
} ) ;
2021-07-06 13:56:32 -03:00
this . subscription = this . route . paramMap
. 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
) ;
2023-08-30 20:26:07 +09:00
this . seoService . setDescription ( $localize ` :@@meta.description.bitcoin.transaction:Get real-time status, addresses, fees, script info, and more for ${ this . stateService . network === 'liquid' || this . stateService . network === 'liquidtestnet' ? 'Liquid' : 'Bitcoin' } ${ seoDescriptionNetwork ( this . stateService . network ) } transaction with txid {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
} )
)
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 {
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
} ) ;
this . cpfpInfo = {
ancestors : tx.ancestors ,
bestDescendant : tx.bestDescendant ,
} ;
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 ) ;
2023-02-21 12:36:43 +09:00
2023-03-04 03:16:59 -06:00
this . priceService . getBlockPrice $ ( tx . status ? . block_time , true ) . pipe (
2023-02-23 09:50:34 +09:00
tap ( ( price ) = > {
this . blockConversion = price ;
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 ) ;
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 } ) ;
this . audioService . playSound ( 'magic' ) ;
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
}
2023-05-03 13:58:08 -06:00
if ( ! found && txFeePerVSize < mempoolBlocks [ mempoolBlocks . length - 1 ] . feeRange [ 0 ] ) {
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 ;
}
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 ) = > {
2023-05-05 15:12:05 -07:00
if ( transactionTimes ? . length ) {
this . transactionTime = transactionTimes [ 0 ] ;
}
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 ;
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 ;
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 ;
}
2023-08-27 00:30:33 +09:00
this . hasEffectiveFeeRate = hasRelatives || ( this . tx . effectiveFeePerVsize && ( Math . abs ( this . tx . effectiveFeePerVsize - this . tx . feePerVsize ) > 0.01 ) ) ;
}
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' ) ;
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 ;
}
2020-04-13 01:26:53 +07:00
resetTransaction() {
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 = [ ] ;
2021-03-22 18:04:50 +07:00
this . showCpfpDetails = false ;
2023-05-03 10:30:45 -06:00
this . txInBlockIndex = null ;
this . mempoolPosition = null ;
2020-04-13 01:26:53 +07:00
document . body . scrollTo ( 0 , 0 ) ;
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 ) ;
}
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-04-05 07:11:13 +09:00
if ( this . graphContainer ? . nativeElement ) {
2023-03-10 12:37:55 +09:00
setTimeout ( ( ) = > {
2023-04-05 07:11:13 +09:00
if ( this . graphContainer ? . nativeElement ) {
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 ( ) ;
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 ( ) ;
2020-04-13 01:26:53 +07:00
this . leaveTransaction ( ) ;
}
2020-02-16 22:15:07 +07:00
}