Add projected acceleration ETA to tracker page
This commit is contained in:
@@ -21,7 +21,7 @@
|
||||
<input type="radio" class="form-check-input" id="accelerate" name="accelerate" (change)="selectedOptionChanged($event)">
|
||||
<label class="form-check-label d-flex flex-column" for="accelerate">
|
||||
<span class="font-weight-bold">Accelerate</span>
|
||||
<span style="color: rgb(186, 186, 186); font-size: 14px;">Confirmation expected within ~30 minutes<br>
|
||||
<span style="color: rgb(186, 186, 186); font-size: 14px;" *ngIf="(etaInfo$ | async) as etaInfo">Confirmation expected <app-time kind="within" [time]="etaInfo.acceleratedETA" [fastRender]="false" [fixedRender]="true"></app-time><br>
|
||||
@if (!calculating) {
|
||||
<app-fiat [value]="cost"></app-fiat>fee (<span><small style="font-family: monospace;">{{ cost | number }}</small> <span class="symbol" i18n="shared.sats">sats</span></span>)
|
||||
} @else {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Component, OnInit, OnDestroy, Output, EventEmitter, Input, ChangeDetectorRef, SimpleChanges } from '@angular/core';
|
||||
import { Subscription, tap, of, catchError } from 'rxjs';
|
||||
import { Subscription, tap, of, catchError, Observable } from 'rxjs';
|
||||
import { ServicesApiServices } from '../../services/services-api.service';
|
||||
import { nextRoundNumber } from '../../shared/common.utils';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { AudioService } from '../../services/audio.service';
|
||||
import { AccelerationEstimate } from '../accelerate-preview/accelerate-preview.component';
|
||||
import { EtaService } from '../../services/eta.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-accelerate-checkout',
|
||||
@@ -24,8 +26,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
square: { appId: string, locationId: string};
|
||||
accelerationUUID: string;
|
||||
estimateSubscription: Subscription;
|
||||
estimate: AccelerationEstimate;
|
||||
maxBidBoost: number; // sats
|
||||
cost: number; // sats
|
||||
etaInfo$: Observable<{ hashratePercentage: number, ETA: number, acceleratedETA: number }>;
|
||||
|
||||
// square
|
||||
loadingCashapp = false;
|
||||
@@ -39,6 +43,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private servicesApiService: ServicesApiServices,
|
||||
private stateService: StateService,
|
||||
private etaService: EtaService,
|
||||
private audioService: AudioService,
|
||||
private cd: ChangeDetectorRef
|
||||
) {
|
||||
@@ -59,7 +64,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
locationId: ids.squareLocationId
|
||||
};
|
||||
if (this.step === 'cta') {
|
||||
this.estimate();
|
||||
this.fetchEstimate();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -99,7 +104,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
/**
|
||||
* Accelerator
|
||||
*/
|
||||
estimate() {
|
||||
fetchEstimate() {
|
||||
if (this.estimateSubscription) {
|
||||
this.estimateSubscription.unsubscribe();
|
||||
}
|
||||
@@ -110,16 +115,17 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
if (response.status === 204) {
|
||||
this.error = `cannot_accelerate_tx`;
|
||||
} else {
|
||||
const estimation = response.body;
|
||||
if (!estimation) {
|
||||
this.estimate = response.body;
|
||||
if (!this.estimate) {
|
||||
this.error = `cannot_accelerate_tx`;
|
||||
return;
|
||||
}
|
||||
// Make min extra fee at least 50% of the current tx fee
|
||||
const minExtraBoost = nextRoundNumber(Math.max(estimation.cost * 2, estimation.txSummary.effectiveFee));
|
||||
const minExtraBoost = nextRoundNumber(Math.max(this.estimate.cost * 2, this.estimate.txSummary.effectiveFee));
|
||||
const DEFAULT_BID_RATIO = 1.5;
|
||||
this.maxBidBoost = minExtraBoost * DEFAULT_BID_RATIO;
|
||||
this.cost = this.maxBidBoost + estimation.mempoolBaseFee + estimation.vsizeFee;
|
||||
this.cost = this.maxBidBoost + this.estimate.mempoolBaseFee + this.estimate.vsizeFee;
|
||||
this.etaInfo$ = this.etaService.getProjectedEtaObservable(this.estimate);
|
||||
}
|
||||
}),
|
||||
|
||||
|
||||
@@ -68,8 +68,10 @@
|
||||
<h5 *ngIf="estimate?.pools?.length" i18n="accelerator.how-much-faster">How much faster?</h5>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<small class="form-text text-muted mb-2" i18n="accelerator.hashrate-percentage-description">Your transaction will be prioritized by up to {{ hashratePercentage | number : '1.1-1' }}% of miners.</small>
|
||||
<small class="form-text text-muted mb-2" i18n="accelerator.time-estimate-description">This will reduce your expected waiting time until the first confirmation to <app-time kind="within" [time]="acceleratedETA" [fastRender]="false" [fixedRender]="true"></app-time></small>
|
||||
<ng-container *ngIf="(etaInfo$ | async) as etaInfo">
|
||||
<small class="form-text text-muted mb-2" i18n="accelerator.hashrate-percentage-description">Your transaction will be prioritized by up to {{ etaInfo.hashratePercentage | number : '1.1-1' }}% of miners.</small>
|
||||
<small class="form-text text-muted mb-2" i18n="accelerator.time-estimate-description">This will reduce your expected waiting time until the first confirmation to <app-time kind="within" [time]="etaInfo.acceleratedETA" [fastRender]="false" [fixedRender]="true"></app-time></small>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="col pie">
|
||||
<app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true"></app-active-acceleration-box>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef } from '@angular/core';
|
||||
import { Subscription, catchError, of, tap } from 'rxjs';
|
||||
import { Observable, Subscription, catchError, of, tap } from 'rxjs';
|
||||
import { StorageService } from '../../services/storage.service';
|
||||
import { Transaction } from '../../interfaces/electrs.interface';
|
||||
import { nextRoundNumber } from '../../shared/common.utils';
|
||||
@@ -8,7 +8,6 @@ import { AudioService } from '../../services/audio.service';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { MiningStats } from '../../services/mining.service';
|
||||
import { EtaService } from '../../services/eta.service';
|
||||
import { DifficultyAdjustment, MempoolPosition, SinglePoolStats } from '../../interfaces/node-api.interface';
|
||||
|
||||
export type AccelerationEstimate = {
|
||||
txSummary: TxSummary;
|
||||
@@ -19,6 +18,7 @@ export type AccelerationEstimate = {
|
||||
cost: number;
|
||||
mempoolBaseFee: number;
|
||||
vsizeFee: number;
|
||||
pools: number[]
|
||||
}
|
||||
export type TxSummary = {
|
||||
txid: string; // txid of the current transaction
|
||||
@@ -44,7 +44,6 @@ export const MAX_BID_RATIO = 4;
|
||||
})
|
||||
export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges {
|
||||
@Input() tx: Transaction;
|
||||
@Input() mempoolPosition: MempoolPosition;
|
||||
@Input() miningStats: MiningStats;
|
||||
@Input() scrollEvent: boolean;
|
||||
|
||||
@@ -54,11 +53,8 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
||||
estimateSubscription: Subscription;
|
||||
accelerationSubscription: Subscription;
|
||||
difficultySubscription: Subscription;
|
||||
da: DifficultyAdjustment;
|
||||
estimate: any;
|
||||
hashratePercentage?: number;
|
||||
ETA?: number;
|
||||
acceleratedETA?: number;
|
||||
etaInfo$: Observable<{ hashratePercentage: number, ETA: number, acceleratedETA: number }>;
|
||||
hasAncestors: boolean = false;
|
||||
minExtraCost = 0;
|
||||
minBidAllowed = 0;
|
||||
@@ -87,27 +83,19 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
||||
if (this.estimateSubscription) {
|
||||
this.estimateSubscription.unsubscribe();
|
||||
}
|
||||
this.difficultySubscription.unsubscribe();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
this.accelerationUUID = window.crypto.randomUUID();
|
||||
this.difficultySubscription = this.stateService.difficultyAdjustment$.subscribe(da => {
|
||||
this.da = da;
|
||||
this.updateETA();
|
||||
})
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.scrollEvent) {
|
||||
this.scrollToPreview('acceleratePreviewAnchor', 'start');
|
||||
}
|
||||
if (changes.miningStats || changes.mempoolPosition) {
|
||||
this.updateETA();
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
ngAfterViewInit(): void {
|
||||
this.user = this.storageService.getAuth()?.user ?? null;
|
||||
|
||||
this.estimateSubscription = this.servicesApiService.estimate$(this.tx.txid).pipe(
|
||||
@@ -132,7 +120,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
||||
}
|
||||
}
|
||||
|
||||
this.updateETA();
|
||||
this.etaInfo$ = this.etaService.getProjectedEtaObservable(this.estimate, this.miningStats);
|
||||
|
||||
this.hasAncestors = this.estimate.txSummary.ancestorCount > 1;
|
||||
|
||||
@@ -178,40 +166,10 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
||||
).subscribe();
|
||||
}
|
||||
|
||||
updateETA(): void {
|
||||
if (!this.mempoolPosition || !this.estimate?.pools?.length || !this.miningStats || !this.da) {
|
||||
this.hashratePercentage = undefined;
|
||||
this.ETA = undefined;
|
||||
this.acceleratedETA = undefined;
|
||||
return;
|
||||
}
|
||||
const pools: { [id: number]: SinglePoolStats } = {};
|
||||
for (const pool of this.miningStats.pools) {
|
||||
pools[pool.poolUniqueId] = pool;
|
||||
}
|
||||
|
||||
let totalAcceleratedHashrate = 0;
|
||||
for (const poolId of this.estimate.pools) {
|
||||
const pool = pools[poolId];
|
||||
if (!pool) {
|
||||
continue;
|
||||
}
|
||||
totalAcceleratedHashrate += pool.lastEstimatedHashrate;
|
||||
}
|
||||
const acceleratingHashrateFraction = (totalAcceleratedHashrate / this.miningStats.lastEstimatedHashrate)
|
||||
this.hashratePercentage = acceleratingHashrateFraction * 100;
|
||||
|
||||
this.ETA = Date.now() + this.da.timeAvg * this.mempoolPosition.block;
|
||||
this.acceleratedETA = this.etaService.calculateETAFromShares([
|
||||
{ block: this.mempoolPosition.block, hashrateShare: (1 - acceleratingHashrateFraction) },
|
||||
{ block: 0, hashrateShare: acceleratingHashrateFraction },
|
||||
], this.da).time;
|
||||
}
|
||||
|
||||
/**
|
||||
* User changed his bid
|
||||
*/
|
||||
setUserBid({ fee, index }: { fee: number, index: number}) {
|
||||
setUserBid({ fee, index }: { fee: number, index: number}): void {
|
||||
if (this.estimate) {
|
||||
this.selectFeeRateIndex = index;
|
||||
this.userBid = Math.max(0, fee);
|
||||
@@ -222,12 +180,12 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
||||
/**
|
||||
* Scroll to element id with or without setTimeout
|
||||
*/
|
||||
scrollToPreviewWithTimeout(id: string, position: ScrollLogicalPosition) {
|
||||
scrollToPreviewWithTimeout(id: string, position: ScrollLogicalPosition): void {
|
||||
setTimeout(() => {
|
||||
this.scrollToPreview(id, position);
|
||||
}, 100);
|
||||
}
|
||||
scrollToPreview(id: string, position: ScrollLogicalPosition) {
|
||||
scrollToPreview(id: string, position: ScrollLogicalPosition): void {
|
||||
const acceleratePreviewAnchor = document.getElementById(id);
|
||||
if (acceleratePreviewAnchor) {
|
||||
this.cd.markForCheck();
|
||||
@@ -242,7 +200,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
||||
/**
|
||||
* Send acceleration request
|
||||
*/
|
||||
accelerate() {
|
||||
accelerate(): void {
|
||||
if (this.accelerationSubscription) {
|
||||
this.accelerationSubscription.unsubscribe();
|
||||
}
|
||||
@@ -268,7 +226,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
||||
});
|
||||
}
|
||||
|
||||
isLoggedIn() {
|
||||
isLoggedIn(): boolean {
|
||||
const auth = this.storageService.getAuth();
|
||||
return auth !== null;
|
||||
}
|
||||
@@ -280,7 +238,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
||||
|
||||
|
||||
@HostListener('window:scroll', ['$event']) // for window scroll events
|
||||
onScroll() {
|
||||
onScroll(): void {
|
||||
if (this.estimate) {
|
||||
setTimeout(() => {
|
||||
this.onScroll();
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="box">
|
||||
<app-accelerate-preview [tx]="tx" [miningStats]="miningStats" [mempoolPosition]="mempoolPosition" [scrollEvent]="scrollIntoAccelPreview"></app-accelerate-preview>
|
||||
<app-accelerate-preview [tx]="tx" [miningStats]="miningStats" [scrollEvent]="scrollIntoAccelPreview"></app-accelerate-preview>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user