mempool/frontend/src/app/components/transaction/transaction.component.ts

679 lines
23 KiB
TypeScript
Raw Normal View History

2022-09-16 20:50:12 +00:00
import { Component, OnInit, AfterViewInit, OnDestroy, HostListener, ViewChild, ElementRef } from '@angular/core';
import { ElectrsApiService } from '../../services/electrs-api.service';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import {
switchMap,
filter,
catchError,
retryWhen,
delay,
map,
mergeMap,
tap
} from 'rxjs/operators';
import { Transaction } from '../../interfaces/electrs.interface';
import { of, merge, Subscription, Observable, Subject, from, throwError } from 'rxjs';
import { StateService } from '../../services/state.service';
import { CacheService } from '../../services/cache.service';
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';
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';
import { LiquidUnblinding } from './liquid-ublinding';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
import { Price, PriceService } from '../../services/price.service';
import { isFeatureActive } from '../../bitcoin.utils';
@Component({
selector: 'app-transaction',
templateUrl: './transaction.component.html',
styleUrls: ['./transaction.component.scss'],
})
2022-09-16 20:50:12 +00:00
export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
network = '';
tx: Transaction;
txId: string;
txInBlockIndex: number;
mempoolPosition: MempoolPosition;
isLoadingTx = true;
error: any = undefined;
errorUnblinded: any = undefined;
loadingCachedTx = false;
waitingForTransaction = false;
latestBlock: BlockExtended;
transactionTime = -1;
subscription: Subscription;
fetchCpfpSubscription: Subscription;
fetchRbfSubscription: Subscription;
fetchCachedTxSubscription: Subscription;
2022-03-08 14:49:25 +01:00
txReplacedSubscription: Subscription;
2022-12-14 08:49:35 -06:00
txRbfInfoSubscription: Subscription;
mempoolPositionSubscription: Subscription;
queryParamsSubscription: Subscription;
urlFragmentSubscription: Subscription;
mempoolBlocksSubscription: Subscription;
blocksSubscription: Subscription;
fragmentParams: URLSearchParams;
rbfTransaction: undefined | Transaction;
replaced: boolean = false;
rbfReplaces: string[];
2022-12-17 09:39:06 -06:00
rbfInfo: RbfTree;
cpfpInfo: CpfpInfo | null;
2023-09-19 00:18:52 +00:00
sigops: number | null;
adjustedVsize: number | null;
showCpfpDetails = false;
fetchCpfp$ = new Subject<string>();
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>;
liquidUnblinding = new LiquidUnblinding();
inputIndex: number;
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;
inOutLimit: number = 150;
2022-09-16 20:50:12 +00:00
maxInOut: number = 0;
flowPrefSubscription: Subscription;
hideFlow: boolean = this.stateService.hideFlow.value;
overrideFlowPreference: boolean = null;
flowEnabled: boolean;
blockConversion: Price;
2022-09-17 01:20:08 +00:00
tooltipPosition: { x: number, y: number };
isMobile: boolean;
featuresEnabled: boolean;
segwitEnabled: boolean;
rbfEnabled: boolean;
taprootEnabled: boolean;
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;
constructor(
private route: ActivatedRoute,
private router: Router,
private relativeUrlPipe: RelativeUrlPipe,
private electrsApiService: ElectrsApiService,
public stateService: StateService,
private cacheService: CacheService,
private websocketService: WebsocketService,
private audioService: AudioService,
private apiService: ApiService,
private seoService: SeoService,
private priceService: PriceService,
2023-08-24 14:17:31 +02:00
private storageService: StorageService
) {}
ngOnInit() {
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']);
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 === '';
}
);
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
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();
})
);
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;
});
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)
2023-01-16 12:04:24 -06:00
)),
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) => {
2022-12-17 09:39:06 -06:00
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();
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();
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;
}
});
}
});
this.mempoolPositionSubscription = this.stateService.mempoolTxPosition$.subscribe(txPosition => {
2023-06-29 11:22:33 -04:00
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);
}
}
} else {
this.mempoolPosition = null;
}
});
this.subscription = 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];
// 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(),
});
} else {
this.txId = urlMatch[0];
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(),
});
}
}
this.seoService.setTitle(
$localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:`
);
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}.`);
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
)
)
);
}),
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$
);
}),
switchMap((tx) => {
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
return from(this.liquidUnblinding.checkUnblindedTx(tx))
.pipe(
catchError((error) => {
this.errorUnblinded = error;
return of(tx);
})
2021-11-12 20:24:15 +04:00
);
}
return of(tx);
})
)
.subscribe((tx: Transaction) => {
if (!tx) {
2023-03-04 03:16:59 -06:00
this.fetchCachedTx$.next(this.txId);
this.seoService.logSoft404();
return;
}
this.seoService.clearSoft404();
2021-08-18 03:34:17 +03:00
this.tx = tx;
this.setFeatures();
2023-03-06 00:02:21 -06:00
this.isCached = false;
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);
}
this.tx.feePerVsize = tx.fee / (tx.weight / 4);
this.isLoadingTx = false;
this.error = undefined;
this.loadingCachedTx = false;
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();
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;
}
2023-03-04 03:16:59 -06:00
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.cpfpInfo = {
ancestors: tx.ancestors,
bestDescendant: tx.bestDescendant,
};
2023-07-16 12:53:55 +09:00
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);
}
}
2023-03-04 03:16:59 -06:00
this.fetchRbfHistory$.next(this.tx.txid);
2023-03-04 03:16:59 -06:00
this.priceService.getBlockPrice$(tx.status?.block_time, true).pipe(
tap((price) => {
this.blockConversion = price;
})
).subscribe();
setTimeout(() => { this.applyFragment(); }, 0);
},
(error) => {
this.error = error;
this.seoService.logSoft404();
this.isLoadingTx = false;
}
);
2023-07-08 01:07:06 -04:00
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,
};
this.stateService.markBlock$.next({ blockHeight: block.height });
this.audioService.playSound('magic');
}
});
2022-03-08 14:49:25 +01:00
this.txReplacedSubscription = this.stateService.txReplaced$.subscribe((rbfTransaction) => {
if (!this.tx) {
2022-03-08 14:49:25 +01:00
this.error = new Error();
this.loadingCachedTx = false;
2022-03-08 14:49:25 +01:00
this.waitingForTransaction = false;
}
this.rbfTransaction = rbfTransaction;
this.replaced = true;
this.stateService.markBlock$.next({});
if (rbfTransaction && !this.tx) {
this.fetchCachedTx$.next(this.txId);
}
2022-03-08 14:49:25 +01:00
});
2022-12-14 08:49:35 -06:00
this.txRbfInfoSubscription = this.stateService.txRbfInfo$.subscribe((rbfInfo) => {
if (this.tx) {
this.rbfInfo = rbfInfo;
}
});
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
if (params.showFlow === 'false') {
this.overrideFlowPreference = false;
} else if (params.showFlow === 'true') {
this.overrideFlowPreference = true;
} else {
this.overrideFlowPreference = null;
}
this.setFlowEnabled();
this.setGraphSize();
});
2022-09-16 20:50:12 +00:00
this.mempoolBlocksSubscription = this.stateService.mempoolBlocks$.subscribe((mempoolBlocks) => {
2023-06-29 11:22:33 -04:00
this.now = Date.now();
if (!this.tx || this.mempoolPosition) {
return;
}
const txFeePerVSize =
this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4);
2023-05-03 10:02:03 -06:00
let found = false;
this.txInBlockIndex = 0;
for (const block of mempoolBlocks) {
2023-05-03 10:02:03 -06:00
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);
2023-05-03 10:02:03 -06:00
found = true;
}
}
}
if (!found && txFeePerVSize < mempoolBlocks[mempoolBlocks.length - 1].feeRange[0]) {
this.txInBlockIndex = 7;
}
});
}
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
}
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) => {
2023-05-05 15:12:05 -07:00
if (transactionTimes?.length) {
this.transactionTime = transactionTimes[0];
}
});
}
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;
}
this.hasEffectiveFeeRate = hasRelatives || (this.tx.effectiveFeePerVsize && (Math.abs(this.tx.effectiveFeePerVsize - this.tx.feePerVsize) > 0.01));
}
setFeatures(): void {
if (this.tx) {
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');
} else {
this.segwitEnabled = false;
this.taprootEnabled = false;
this.rbfEnabled = false;
}
this.featuresEnabled = this.segwitEnabled || this.taprootEnabled || this.rbfEnabled;
}
resetTransaction() {
this.error = undefined;
this.tx = null;
this.setFeatures();
this.waitingForTransaction = false;
this.isLoadingTx = true;
this.rbfTransaction = undefined;
this.replaced = false;
this.transactionTime = -1;
this.cpfpInfo = null;
2023-09-19 00:18:52 +00:00
this.adjustedVsize = null;
this.sigops = null;
this.hasEffectiveFeeRate = false;
2022-12-17 09:39:06 -06:00
this.rbfInfo = null;
this.rbfReplaces = [];
this.showCpfpDetails = false;
this.txInBlockIndex = null;
this.mempoolPosition = null;
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);
}
2022-09-16 20:50:12 +00:00
setupGraph() {
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
}
toggleGraph() {
const showFlow = !this.flowEnabled;
this.stateService.hideFlow.next(!showFlow);
this.router.navigate([], {
relativeTo: this.route,
queryParams: { showFlow: showFlow },
queryParamsHandling: 'merge',
fragment: 'flow'
});
}
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
}
// simulate normal anchor fragment behavior
applyFragment(): void {
const anchor = Array.from(this.fragmentParams.entries()).find(([frag, value]) => value === '');
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-09-16 20:50:12 +00:00
@HostListener('window:resize', ['$event'])
setGraphSize(): void {
this.isMobile = window.innerWidth < 850;
if (this.graphContainer?.nativeElement) {
setTimeout(() => {
if (this.graphContainer?.nativeElement) {
this.graphWidth = this.graphContainer.nativeElement.clientWidth;
} else {
setTimeout(() => { this.setGraphSize(); }, 1);
}
}, 1);
2022-09-16 20:50:12 +00:00
}
}
ngOnDestroy() {
this.subscription.unsubscribe();
this.fetchCpfpSubscription.unsubscribe();
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();
this.queryParamsSubscription.unsubscribe();
this.flowPrefSubscription.unsubscribe();
this.urlFragmentSubscription.unsubscribe();
this.mempoolBlocksSubscription.unsubscribe();
this.mempoolPositionSubscription.unsubscribe();
this.mempoolBlocksSubscription.unsubscribe();
this.blocksSubscription.unsubscribe();
this.leaveTransaction();
}
}