mirror of
https://github.com/mempool/mempool.git
synced 2025-02-25 15:10:12 +01:00
Merge pull request #5287 from mempool/natsoni/acc-timeline-polish
Acceleration timeline polishing
This commit is contained in:
commit
8d2e7bef7a
6 changed files with 268 additions and 91 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
@if (tx.status.confirmed) {
|
||||||
<div class="acceleration-timeline box">
|
<div class="acceleration-timeline box">
|
||||||
<div class="timeline-wrapper">
|
<div class="timeline-wrapper">
|
||||||
<div class="timeline">
|
<div class="timeline">
|
||||||
|
@ -11,68 +12,141 @@
|
||||||
<div class="node-spacer"></div>
|
<div class="node-spacer"></div>
|
||||||
<div class="interval">
|
<div class="interval">
|
||||||
<div class="interval-time">
|
<div class="interval-time">
|
||||||
@if (eta) {
|
<app-time [time]="tx.status.block_time - acceleratedAt"></app-time>
|
||||||
~<app-time kind="plain" [time]="eta?.wait / 1000"></app-time>
|
|
||||||
} @else if (tx.status.block_time) {
|
|
||||||
<app-time kind="plain" [time]="tx.status.block_time - acceleratedAt"></app-time>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="node-spacer"></div>
|
<div class="node-spacer"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="nodes">
|
<div class="nodes">
|
||||||
<div class="node" [id]="'first-seen'">
|
<div class="node" [id]="'first-seen'">
|
||||||
<div class="seen-to-acc right" [class.loading]="!tx.acceleration && !tx.status.confirmed"></div>
|
<div class="seen-to-acc right"></div>
|
||||||
<a class="shape-border" [class.sent-selected]="!tx.status.confirmed && !tx.acceleration">
|
<div class="shape-border">
|
||||||
<div class="shape"></div>
|
<div class="shape"></div>
|
||||||
</a>
|
</div>
|
||||||
<div class="status"><span class="badge badge-primary">Sent</span></div>
|
<div class="status"><span class="badge badge-primary" i18n="transaction.first-seen|Transaction first seen">First seen</span></div>
|
||||||
<div class="time">
|
<div class="time">
|
||||||
<app-time *ngIf="transactionTime > 0" kind="since" [time]="transactionTime"></app-time>
|
<app-time *ngIf="transactionTime > 0" kind="since" [time]="transactionTime"></app-time>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="interval-spacer">
|
<div class="interval-spacer">
|
||||||
<div class="seen-to-acc" [class.loading]="!tx.acceleration && !tx.status.confirmed"></div>
|
<div class="seen-to-acc"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="node" [id]="'accelerated'">
|
<div class="node" [id]="'accelerated'">
|
||||||
<div class="seen-to-acc left" [class.loading]="!tx.acceleration && !tx.status.confirmed"></div>
|
<div class="seen-to-acc left"></div>
|
||||||
<div class="acc-to-confirmed right" [class.loading]="tx.acceleration && !tx.status.confirmed"></div>
|
<div class="acc-to-confirmed right"></div>
|
||||||
<a class="shape-border" [class.accelerated-selected]="tx.acceleration && !tx.status.confirmed">
|
<div class="shape-border">
|
||||||
<div class="shape"></div>
|
<div class="shape"></div>
|
||||||
</a>
|
</div>
|
||||||
<div class="status" [style]="!tx.acceleration && !tx.status.confirmed ? 'opacity: 0.5' : ''"><span class="badge badge-accelerated">Accelerated</span></div>
|
<div class="status"><span class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span></div>
|
||||||
<div class="time">
|
<div class="time">
|
||||||
<app-time *ngIf="acceleratedAt" kind="since" [time]="acceleratedAt"></app-time>
|
<app-time *ngIf="acceleratedAt" kind="since" [time]="acceleratedAt"></app-time>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="interval-spacer">
|
<div class="interval-spacer">
|
||||||
<div class="acc-to-confirmed" [class.loading]="tx.acceleration && !tx.status.confirmed"></div>
|
<div class="acc-to-confirmed"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="node" [id]="'confirmed'" [class.mined]="tx.status.confirmed">
|
<div class="node mined" [id]="'confirmed'" >
|
||||||
<div class="acc-to-confirmed left" [class.loading]="tx.acceleration && !tx.status.confirmed"></div>
|
<div class="acc-to-confirmed left" ></div>
|
||||||
<a class="shape-border" [class.mined-selected]="tx.status.confirmed">
|
<div class="shape-border mined-selected">
|
||||||
<div class="shape"></div>
|
<div class="shape"></div>
|
||||||
</a>
|
</div>
|
||||||
<div class="status" [style]="!tx.status.confirmed ? 'opacity: 0.5' : ''"><span class="badge badge-success">Mined</span></div>
|
<div class="status"><span class="badge badge-success" i18n="transaction.rbf.mined">Mined</span></div>
|
||||||
<div class="time">
|
<div class="time">
|
||||||
@if (tx.status.block_time) {
|
|
||||||
<app-time kind="since" [time]="tx.status.block_time"></app-time>
|
<app-time kind="since" [time]="tx.status.block_time"></app-time>
|
||||||
} @else if (eta) {
|
</div>
|
||||||
<app-time kind="until" [time]="eta?.time"></app-time>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} @else if (acceleratedETA) { <!-- Not yet accelerated; to be shown only in acceleration checkout -->
|
||||||
|
} @else if (standardETA) { <!-- Accelerated -->
|
||||||
|
<div class="acceleration-timeline box">
|
||||||
|
<div class="timeline-wrapper">
|
||||||
|
<div class="timeline">
|
||||||
|
<div class="intervals">
|
||||||
|
<div class="node-spacer"></div>
|
||||||
|
<div class="interval-spacer"></div>
|
||||||
|
<div class="node-spacer"></div>
|
||||||
|
<div class="interval">
|
||||||
|
<div class="interval-time">
|
||||||
|
@if (eta) {
|
||||||
|
~<app-time [time]="eta?.wait / 1000"></app-time> <span *ngIf="accelerateRatio > 1" class="compare"> ({{ accelerateRatio }}x faster)</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ng-template #nodeSpacer>
|
|
||||||
<div class="node-spacer"></div>
|
<div class="node-spacer"></div>
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #intervalSpacer>
|
|
||||||
<div class="interval-spacer"></div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="nodes">
|
||||||
|
<div class="node-spacer"></div>
|
||||||
|
<div class="interval-spacer"></div>
|
||||||
|
<div class="node">
|
||||||
|
<div class="acc-to-confirmed loading right"></div>
|
||||||
|
</div>
|
||||||
|
<div class="interval-spacer">
|
||||||
|
<div class="acc-to-confirmed loading"></div>
|
||||||
|
</div>
|
||||||
|
<div class="node" [id]="'confirmed'">
|
||||||
|
<div class="acc-to-confirmed loading left"></div>
|
||||||
|
<div class="shape-border waiting">
|
||||||
|
<div class="shape animate"></div>
|
||||||
|
</div>
|
||||||
|
<div class="status"><span class="badge badge-waiting" i18n="transaction.rbf.mined">Mined</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="timeline">
|
||||||
|
<div class="intervals">
|
||||||
|
<div class="node-spacer"></div>
|
||||||
|
<div class="interval">
|
||||||
|
<div class="interval-time">
|
||||||
|
<app-time [time]="acceleratedAt - transactionTime"></app-time>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="node-spacer"></div>
|
||||||
|
<div class="interval">
|
||||||
|
<div class="interval-time">
|
||||||
|
~<app-time [time]="standardETA / 1000 - now"></app-time>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="node-spacer"></div>
|
||||||
|
</div>
|
||||||
|
<div class="nodes">
|
||||||
|
<div class="node" [id]="'first-seen'">
|
||||||
|
<div class="seen-to-acc right"></div>
|
||||||
|
<div class="shape-border">
|
||||||
|
<div class="shape"></div>
|
||||||
|
</div>
|
||||||
|
<div class="status"><span class="badge badge-primary" i18n="transaction.first-seen|Transaction first seen">First seen</span></div>
|
||||||
|
<div class="time">
|
||||||
|
<app-time *ngIf="transactionTime > 0" kind="since" [time]="transactionTime"></app-time>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="interval-spacer">
|
||||||
|
<div class="seen-to-acc"></div>
|
||||||
|
</div>
|
||||||
|
<div class="node" [id]="'accelerated'">
|
||||||
|
<div class="seen-to-acc left"></div>
|
||||||
|
<div class="seen-to-acc right"></div>
|
||||||
|
<div class="shape-border accelerated-selected">
|
||||||
|
<div class="shape accelerating"></div>
|
||||||
|
<div class="connector down loading"></div>
|
||||||
|
</div>
|
||||||
|
<div class="time" style="margin-top: 3px;">
|
||||||
|
<span i18n="transaction.audit.accelerated">Accelerated</span> <app-time *ngIf="acceleratedAt" kind="since" [time]="acceleratedAt"></app-time>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="interval-spacer">
|
||||||
|
<div class="seen-to-acc"></div>
|
||||||
|
</div>
|
||||||
|
<div class="node" [id]="'confirmed'">
|
||||||
|
<div class="seen-to-acc left"></div>
|
||||||
|
<div class="shape-border waiting">
|
||||||
|
<div class="shape"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
.acceleration-timeline {
|
.acceleration-timeline {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 1em 0;
|
padding: 0.5em 0 1em;
|
||||||
|
|
||||||
&::after, &::before {
|
&::after, &::before {
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -69,6 +69,15 @@
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
|
.compare {
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--mainnet-alt);
|
||||||
|
font-weight: 600;
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,10 +93,6 @@
|
||||||
background: var(--primary);
|
background: var(--primary);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|
||||||
&.loading {
|
|
||||||
animation: standardPulse 1s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.left {
|
&.left {
|
||||||
right: 50%;
|
right: 50%;
|
||||||
}
|
}
|
||||||
|
@ -119,6 +124,27 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.connector {
|
||||||
|
position: absolute;
|
||||||
|
height: 88px;
|
||||||
|
width: 10px;
|
||||||
|
left: -5px;
|
||||||
|
top: -73px;
|
||||||
|
transform: translateX(120%);
|
||||||
|
background: var(--tertiary);
|
||||||
|
|
||||||
|
&.down {
|
||||||
|
border-top-left-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.up {
|
||||||
|
border-top-right-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
animation: acceleratePulse 1s infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nodes {
|
.nodes {
|
||||||
|
@ -134,20 +160,20 @@
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
background: transparent;
|
|
||||||
transition: background-color 300ms, padding 300ms;
|
|
||||||
|
|
||||||
.shape {
|
.shape {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: white;
|
background: white;
|
||||||
transition: background-color 300ms, border 300ms;
|
&.accelerating {
|
||||||
|
animation: acceleratePulse 1s infinite;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.sent-selected {
|
&.waiting {
|
||||||
.shape {
|
.shape {
|
||||||
background: var(--primary);
|
background: var(--grey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,6 +193,12 @@
|
||||||
.status {
|
.status {
|
||||||
margin-top: -64px;
|
margin-top: -64px;
|
||||||
|
|
||||||
|
.badge.badge-waiting {
|
||||||
|
opacity: 0.5;
|
||||||
|
background-color: var(--grey);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.badge.badge-accelerated {
|
.badge.badge-accelerated {
|
||||||
background-color: var(--tertiary);
|
background-color: var(--tertiary);
|
||||||
color: white;
|
color: white;
|
||||||
|
@ -188,10 +220,3 @@
|
||||||
50% { background-color: var(--mainnet-alt) }
|
50% { background-color: var(--mainnet-alt) }
|
||||||
100% { background-color: var(--tertiary) }
|
100% { background-color: var(--tertiary) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes standardPulse {
|
|
||||||
0% { background-color: var(--primary) }
|
|
||||||
50% { background-color: var(--secondary) }
|
|
||||||
100% { background-color: var(--primary) }
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component, Input, OnInit, OnChanges, Inject, LOCALE_ID } from '@angular/core';
|
import { Component, Input, OnInit, OnChanges } from '@angular/core';
|
||||||
import { ETA } from '../../services/eta.service';
|
import { ETA } from '../../services/eta.service';
|
||||||
import { Transaction } from '../../interfaces/electrs.interface';
|
import { Transaction } from '../../interfaces/electrs.interface';
|
||||||
|
|
||||||
|
@ -11,23 +11,31 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges {
|
||||||
@Input() transactionTime: number;
|
@Input() transactionTime: number;
|
||||||
@Input() tx: Transaction;
|
@Input() tx: Transaction;
|
||||||
@Input() eta: ETA;
|
@Input() eta: ETA;
|
||||||
|
// A mined transaction has standard ETA and accelerated ETA undefined
|
||||||
|
// A transaction in mempool has either standardETA defined (if accelerated) or acceleratedETA defined (if not accelerated yet)
|
||||||
|
@Input() standardETA: number;
|
||||||
|
@Input() acceleratedETA: number;
|
||||||
|
|
||||||
acceleratedAt: number;
|
acceleratedAt: number;
|
||||||
dir: 'rtl' | 'ltr' = 'ltr';
|
now: number;
|
||||||
|
accelerateRatio: number;
|
||||||
|
|
||||||
constructor(
|
constructor() {}
|
||||||
@Inject(LOCALE_ID) private locale: string,
|
|
||||||
) {
|
|
||||||
if (this.locale.startsWith('ar') || this.locale.startsWith('fa') || this.locale.startsWith('he')) {
|
|
||||||
this.dir = 'rtl';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.acceleratedAt = this.tx.acceleratedAt ?? new Date().getTime() / 1000;
|
this.acceleratedAt = this.tx.acceleratedAt ?? new Date().getTime() / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes): void {
|
ngOnChanges(changes): void {
|
||||||
|
this.now = Math.floor(new Date().getTime() / 1000);
|
||||||
|
if (changes?.eta?.currentValue || changes?.standardETA?.currentValue || changes?.acceleratedETA?.currentValue) {
|
||||||
|
if (changes?.eta?.currentValue) {
|
||||||
|
if (changes?.acceleratedETA?.currentValue) {
|
||||||
|
this.accelerateRatio = Math.floor((Math.floor(changes.eta.currentValue.time / 1000) - this.now) / (Math.floor(changes.acceleratedETA.currentValue / 1000) - this.now));
|
||||||
|
} else if (changes?.standardETA?.currentValue) {
|
||||||
|
this.accelerateRatio = Math.floor((Math.floor(changes.standardETA.currentValue / 1000) - this.now) / (Math.floor(changes.eta.currentValue.time / 1000) - this.now));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,15 +153,6 @@
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<ng-container *ngIf="transactionTime && (tx.acceleration || isAcceleration)">
|
|
||||||
<div class="title float-left">
|
|
||||||
<h2 id="acceleration-timeline" i18n="transaction.acceleration-timeline|Acceleration Timeline">Acceleration Timeline</h2>
|
|
||||||
</div>
|
|
||||||
<div class="clearfix"></div>
|
|
||||||
<app-acceleration-timeline [transactionTime]="transactionTime" [tx]="tx" [eta]="(ETA$ | async)"></app-acceleration-timeline>
|
|
||||||
<br>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container *ngIf="rbfInfo">
|
<ng-container *ngIf="rbfInfo">
|
||||||
<div class="title float-left">
|
<div class="title float-left">
|
||||||
<h2 id="rbf" i18n="transaction.rbf-history|RBF Timeline">RBF Timeline</h2>
|
<h2 id="rbf" i18n="transaction.rbf-history|RBF Timeline">RBF Timeline</h2>
|
||||||
|
@ -171,6 +162,15 @@
|
||||||
<br>
|
<br>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="transactionTime && isAcceleration">
|
||||||
|
<div class="title float-left">
|
||||||
|
<h2 id="acceleration-timeline" i18n="transaction.acceleration-timeline|Acceleration Timeline">Acceleration Timeline</h2>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
<app-acceleration-timeline [transactionTime]="transactionTime" [tx]="tx" [eta]="(ETA$ | async)" [standardETA]="(standardETA$ | async)?.time"></app-acceleration-timeline>
|
||||||
|
<br>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="flowEnabled; else flowPlaceholder">
|
<ng-container *ngIf="flowEnabled; else flowPlaceholder">
|
||||||
<div class="title float-left">
|
<div class="title float-left">
|
||||||
<h2 id="flow" i18n="transaction.flow|Transaction flow">Flow</h2>
|
<h2 id="flow" i18n="transaction.flow|Transaction flow">Flow</h2>
|
||||||
|
|
|
@ -113,6 +113,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
txChanged$ = new BehaviorSubject<boolean>(false); // triggered whenever this.tx changes (long term, we should refactor to make this.tx an observable itself)
|
txChanged$ = new BehaviorSubject<boolean>(false); // triggered whenever this.tx changes (long term, we should refactor to make this.tx an observable itself)
|
||||||
isAccelerated$ = new BehaviorSubject<boolean>(false); // refactor this to make isAccelerated an observable itself
|
isAccelerated$ = new BehaviorSubject<boolean>(false); // refactor this to make isAccelerated an observable itself
|
||||||
ETA$: Observable<ETA | null>;
|
ETA$: Observable<ETA | null>;
|
||||||
|
standardETA$: Observable<ETA | null>;
|
||||||
isCached: boolean = false;
|
isCached: boolean = false;
|
||||||
now = Date.now();
|
now = Date.now();
|
||||||
da$: Observable<DifficultyAdjustment>;
|
da$: Observable<DifficultyAdjustment>;
|
||||||
|
@ -814,6 +815,21 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
this.miningStats = stats;
|
this.miningStats = stats;
|
||||||
this.isAccelerated$.next(this.isAcceleration); // hack to trigger recalculation of ETA without adding another source observable
|
this.isAccelerated$.next(this.isAcceleration); // hack to trigger recalculation of ETA without adding another source observable
|
||||||
});
|
});
|
||||||
|
if (!this.tx.status?.confirmed) {
|
||||||
|
this.standardETA$ = combineLatest([
|
||||||
|
this.stateService.mempoolBlocks$.pipe(startWith(null)),
|
||||||
|
this.stateService.difficultyAdjustment$.pipe(startWith(null)),
|
||||||
|
]).pipe(
|
||||||
|
map(([mempoolBlocks, da]) => {
|
||||||
|
return this.etaService.calculateUnacceleratedETA(
|
||||||
|
this.tx,
|
||||||
|
mempoolBlocks,
|
||||||
|
da,
|
||||||
|
this.cpfpInfo,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.isAccelerated$.next(this.isAcceleration);
|
this.isAccelerated$.next(this.isAcceleration);
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,4 +225,58 @@ export class EtaService {
|
||||||
blocks: Math.ceil(eta / da.adjustedTimeAvg),
|
blocks: Math.ceil(eta / da.adjustedTimeAvg),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
calculateUnacceleratedETA(
|
||||||
|
tx: Transaction,
|
||||||
|
mempoolBlocks: MempoolBlock[],
|
||||||
|
da: DifficultyAdjustment,
|
||||||
|
cpfpInfo: CpfpInfo | null,
|
||||||
|
): ETA | null {
|
||||||
|
if (!tx || !mempoolBlocks) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// use known projected position, or fall back to feerate-based estimate
|
||||||
|
const mempoolPosition = this.mempoolPositionFromFees(this.getFeeRateFromCpfpInfo(tx, cpfpInfo), mempoolBlocks);
|
||||||
|
if (!mempoolPosition) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// difficulty adjustment estimate is required to know avg block time on non-Liquid networks
|
||||||
|
if (!da) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blocks = mempoolPosition.block + 1;
|
||||||
|
const wait = da.adjustedTimeAvg * (mempoolPosition.block + 1);
|
||||||
|
return {
|
||||||
|
now,
|
||||||
|
time: wait + now + da.timeOffset,
|
||||||
|
wait,
|
||||||
|
blocks,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getFeeRateFromCpfpInfo(tx: Transaction, cpfpInfo: CpfpInfo | null): number {
|
||||||
|
if (!cpfpInfo) {
|
||||||
|
return tx.fee / (tx.weight / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
const relatives = [...(cpfpInfo.ancestors || []), ...(cpfpInfo.descendants || [])];
|
||||||
|
if (cpfpInfo.bestDescendant && !cpfpInfo.descendants?.length) {
|
||||||
|
relatives.push(cpfpInfo.bestDescendant);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!relatives.length) {
|
||||||
|
const totalWeight = tx.weight + relatives.reduce((prev, val) => prev + val.weight, 0);
|
||||||
|
const totalFees = tx.fee + relatives.reduce((prev, val) => prev + val.fee, 0);
|
||||||
|
|
||||||
|
return totalFees / (totalWeight / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.fee / (tx.weight / 4);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue