mirror of
https://github.com/mempool/mempool.git
synced 2025-02-25 07:07:36 +01:00
Show unaccelerated ETA in acceleration timeline
This commit is contained in:
parent
a0992f6091
commit
bf51e3e1c9
6 changed files with 357 additions and 79 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,225 @@
|
||||||
<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]="eta?.wait / 1000"></app-time>
|
|
||||||
} @else if (tx.status.block_time) {
|
|
||||||
<app-time [time]="tx.status.block_time - acceleratedAt"></app-time>
|
<app-time [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 class="nodes">
|
||||||
</div>
|
<div class="node" [id]="'first-seen'">
|
||||||
<div class="nodes">
|
<div class="seen-to-acc right"></div>
|
||||||
<div class="node" [id]="'first-seen'">
|
<a class="shape-border">
|
||||||
<div class="seen-to-acc right" [class.loading]="!isAcceleration && !tx.status.confirmed"></div>
|
<div class="shape"></div>
|
||||||
<a class="shape-border" [class.sent-selected]="!tx.status.confirmed && !isAcceleration">
|
</a>
|
||||||
<div class="shape"></div>
|
<div class="status"><span class="badge badge-primary" i18n="transaction.first-seen|Transaction first seen">First seen</span></div>
|
||||||
</a>
|
<div class="time">
|
||||||
<div class="status"><span class="badge badge-primary" i18n="accelerator.sent-state">Sent</span></div>
|
<app-time *ngIf="transactionTime > 0" kind="since" [time]="transactionTime"></app-time>
|
||||||
<div class="time">
|
</div>
|
||||||
<app-time *ngIf="transactionTime > 0" kind="since" [time]="transactionTime"></app-time>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="interval-spacer">
|
||||||
<div class="interval-spacer">
|
<div class="seen-to-acc"></div>
|
||||||
<div class="seen-to-acc" [class.loading]="!isAcceleration && !tx.status.confirmed"></div>
|
|
||||||
</div>
|
|
||||||
<div class="node" [id]="'accelerated'">
|
|
||||||
<div class="seen-to-acc left" [class.loading]="!isAcceleration && !tx.status.confirmed"></div>
|
|
||||||
<div class="acc-to-confirmed right" [class.loading]="isAcceleration && !tx.status.confirmed"></div>
|
|
||||||
<a class="shape-border" [class.accelerated-selected]="isAcceleration && !tx.status.confirmed" [class.waiting]="!isAcceleration && !tx.status.confirmed">
|
|
||||||
<div class="shape"></div>
|
|
||||||
</a>
|
|
||||||
<div class="status"><span class="badge" [class]="tx.status.confirmed || isAcceleration ? 'badge-accelerated' : 'badge-waiting'" i18n="transaction.audit.accelerated">Accelerated</span></div>
|
|
||||||
<div class="time">
|
|
||||||
<app-time *ngIf="acceleratedAt" kind="since" [time]="acceleratedAt"></app-time>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="node" [id]="'accelerated'">
|
||||||
<div class="interval-spacer">
|
<div class="seen-to-acc left"></div>
|
||||||
<div class="acc-to-confirmed" [class.loading]="isAcceleration && !tx.status.confirmed"></div>
|
<div class="acc-to-confirmed right"></div>
|
||||||
</div>
|
<a class="shape-border">
|
||||||
<div class="node" [id]="'confirmed'" [class.mined]="tx.status.confirmed">
|
<div class="shape"></div>
|
||||||
<div class="acc-to-confirmed left" [class.loading]="isAcceleration && !tx.status.confirmed"></div>
|
</a>
|
||||||
<a class="shape-border" [class.mined-selected]="tx.status.confirmed" [class.waiting]="!tx.status.confirmed">
|
<div class="status"><span class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span></div>
|
||||||
<div class="shape"></div>
|
<div class="time">
|
||||||
</a>
|
<app-time *ngIf="acceleratedAt" kind="since" [time]="acceleratedAt"></app-time>
|
||||||
<div class="status"><span class="badge" [class]="tx.status.confirmed ? 'badge-success' : 'badge-waiting'" i18n="transaction.rbf.mined">Mined</span></div>
|
</div>
|
||||||
<div class="time">
|
</div>
|
||||||
@if (tx.status.block_time) {
|
<div class="interval-spacer">
|
||||||
|
<div class="acc-to-confirmed"></div>
|
||||||
|
</div>
|
||||||
|
<div class="node mined" [id]="'confirmed'" >
|
||||||
|
<div class="acc-to-confirmed left" ></div>
|
||||||
|
<a class="shape-border mined-selected">
|
||||||
|
<div class="shape"></div>
|
||||||
|
</a>
|
||||||
|
<div class="status"><span class="badge badge-success" i18n="transaction.rbf.mined">Mined</span></div>
|
||||||
|
<div class="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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #nodeSpacer>
|
|
||||||
<div class="node-spacer"></div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #intervalSpacer>
|
|
||||||
<div class="interval-spacer"></div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
} @else if (acceleratedETA) { <!-- Not yet accelerated; to be shown only in acceleration checkout -->
|
||||||
|
<div class="acceleration-timeline">
|
||||||
|
<div class="timeline-wrapper">
|
||||||
|
<div class="timeline">
|
||||||
|
<div class="intervals">
|
||||||
|
<div class="node-spacer"></div>
|
||||||
|
<div class="interval">
|
||||||
|
<div class="interval-time">
|
||||||
|
<app-time [time]="now - transactionTime"></app-time>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="node-spacer"></div>
|
||||||
|
<div class="interval">
|
||||||
|
<div class="interval-time">
|
||||||
|
~<app-time [time]="acceleratedETA / 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>
|
||||||
|
<a class="shape-border">
|
||||||
|
<div class="shape"></div>
|
||||||
|
</a>
|
||||||
|
<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="acc-to-confirmed right"></div>
|
||||||
|
<a class="shape-border waiting">
|
||||||
|
<div class="shape accelerating"></div>
|
||||||
|
</a>
|
||||||
|
<div class="status"><span class="badge badge-waiting" i18n="transaction.audit.accelerated">Accelerated</span></div>
|
||||||
|
<div class="time">
|
||||||
|
<span i18n="date.now">Now</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="interval-spacer">
|
||||||
|
<div class="acc-to-confirmed"></div>
|
||||||
|
</div>
|
||||||
|
<div class="node" [id]="'confirmed'">
|
||||||
|
<div class="acc-to-confirmed left"></div>
|
||||||
|
<div class="corner-up"></div>
|
||||||
|
<a class="shape-border waiting">
|
||||||
|
<div class="shape"></div>
|
||||||
|
</a>
|
||||||
|
<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-spacer"></div>
|
||||||
|
<div class="node-spacer"></div>
|
||||||
|
<div class="interval">
|
||||||
|
<div class="interval-time">
|
||||||
|
~<app-time [time]="eta.time / 1000 - now"></app-time> <span *ngIf="accelerateRatio > 1" style="font-style: italic; color: var(--transparent-fg);"> ({{ accelerateRatio }}x slower)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="node-spacer"></div>
|
||||||
|
</div>
|
||||||
|
<div class="nodes">
|
||||||
|
<div class="node-spacer"></div>
|
||||||
|
<div class="interval-spacer"></div>
|
||||||
|
<div class="node-spacer">
|
||||||
|
<div class="connector"><div class="corner-down"></div></div>
|
||||||
|
<div class="seen-to-acc right"></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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} @else if (standardETA) { <!-- Accelerated, to be mined -->
|
||||||
|
<div class="acceleration-timeline box">
|
||||||
|
<div class="timeline-wrapper">
|
||||||
|
<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">
|
||||||
|
@if (eta) {
|
||||||
|
~<app-time [time]="eta?.wait / 1000"></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>
|
||||||
|
<a class="shape-border">
|
||||||
|
<div class="shape"></div>
|
||||||
|
</a>
|
||||||
|
<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="acc-to-confirmed right loading"></div>
|
||||||
|
<a class="shape-border accelerated-selected">
|
||||||
|
<div class="shape accelerating"></div>
|
||||||
|
</a>
|
||||||
|
<div class="status"><span class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span></div>
|
||||||
|
<div class="time sm-margin">
|
||||||
|
<app-time *ngIf="acceleratedAt" kind="since" [time]="acceleratedAt"></app-time>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="interval-spacer">
|
||||||
|
<div class="acc-to-confirmed loading"></div>
|
||||||
|
</div>
|
||||||
|
<div class="node" [id]="'confirmed'">
|
||||||
|
<div class="acc-to-confirmed left loading"></div>
|
||||||
|
<div class="corner-up"></div>
|
||||||
|
<a class="shape-border waiting">
|
||||||
|
<div class="shape"></div>
|
||||||
|
</a>
|
||||||
|
<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-spacer"></div>
|
||||||
|
<div class="node-spacer"></div>
|
||||||
|
<div class="interval">
|
||||||
|
<div class="interval-time">
|
||||||
|
~<app-time [time]="standardETA / 1000 - now"></app-time> <span *ngIf="accelerateRatio > 1" style="font-style: italic; color: var(--transparent-fg);"> ({{ accelerateRatio }}x slower)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="node-spacer"></div>
|
||||||
|
</div>
|
||||||
|
<div class="nodes">
|
||||||
|
<div class="node-spacer"></div>
|
||||||
|
<div class="interval-spacer"></div>
|
||||||
|
<div class="node-spacer">
|
||||||
|
<div class="connector"><div class="corner-down"></div></div>
|
||||||
|
<div class="seen-to-acc right"></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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
|
@ -84,10 +84,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 +115,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.corner-up {
|
||||||
|
position: absolute;
|
||||||
|
left: -5px;
|
||||||
|
left: 48.5%;
|
||||||
|
height: 86px;
|
||||||
|
border-left: solid 10px var(--primary);
|
||||||
|
border-bottom: solid 10px var(--primary);
|
||||||
|
border-bottom-right-radius: 10px;
|
||||||
|
// horrible css:
|
||||||
|
@media (max-width: 1030px) {
|
||||||
|
left: 48%;
|
||||||
|
}
|
||||||
|
@media (max-width: 850px) {
|
||||||
|
left: 47%;
|
||||||
|
}
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
left: 46%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nodes {
|
.nodes {
|
||||||
|
@ -142,6 +158,9 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: white;
|
background: white;
|
||||||
|
&.accelerating {
|
||||||
|
animation: acceleratePulse 1s infinite;
|
||||||
|
}
|
||||||
transition: background-color 300ms, border 300ms;
|
transition: background-color 300ms, border 300ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,12 +170,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.sent-selected {
|
|
||||||
.shape {
|
|
||||||
background: var(--primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.accelerated-selected {
|
&.accelerated-selected {
|
||||||
.shape {
|
.shape {
|
||||||
background: var(--tertiary);
|
background: var(--tertiary);
|
||||||
|
@ -190,6 +203,30 @@
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&.sm-margin {
|
||||||
|
@media (max-width: 650px) {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.connector {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
|
||||||
|
.corner-down {
|
||||||
|
position: absolute;
|
||||||
|
@media (max-width: 650px) {
|
||||||
|
width: 223px;
|
||||||
|
}
|
||||||
|
width: 290px;
|
||||||
|
height: 90px;
|
||||||
|
bottom: 50%;
|
||||||
|
border-left: solid 10px var(--primary);
|
||||||
|
border-bottom: solid 10px var(--primary);
|
||||||
|
border-bottom-left-radius: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,9 +238,8 @@
|
||||||
100% { background-color: var(--tertiary) }
|
100% { background-color: var(--tertiary) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes standardPulse {
|
@keyframes textPulse {
|
||||||
0% { background-color: var(--primary) }
|
0% { color: var(--tertiary) }
|
||||||
50% { background-color: var(--secondary) }
|
50% { color: var(--mainnet-alt) }
|
||||||
100% { background-color: var(--primary) }
|
100% { color: var(--tertiary) }
|
||||||
|
|
||||||
}
|
}
|
|
@ -11,9 +11,14 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges {
|
||||||
@Input() transactionTime: number;
|
@Input() transactionTime: number;
|
||||||
@Input() tx: Transaction;
|
@Input() tx: Transaction;
|
||||||
@Input() eta: ETA;
|
@Input() eta: ETA;
|
||||||
@Input() isAcceleration: boolean;
|
// 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;
|
||||||
|
now: number;
|
||||||
|
accelerateRatio: number;
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
|
@ -22,6 +27,15 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges {
|
||||||
}
|
}
|
||||||
|
|
||||||
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,15 +152,6 @@
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<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)" [isAcceleration]="isAcceleration"></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>
|
||||||
|
@ -170,6 +161,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>
|
||||||
|
|
|
@ -112,6 +112,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>;
|
||||||
|
@ -809,6 +810,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