Merge pull request #4219 from mempool/mononaut/accelerator-preview-concept
Accelerator preview concept
This commit is contained in:
commit
a7c0d33de8
@ -0,0 +1,21 @@
|
|||||||
|
<div class="fee-graph" *ngIf="tx && estimate">
|
||||||
|
<div class="column">
|
||||||
|
<ng-container *ngFor="let bar of bars">
|
||||||
|
<div class="bar {{ bar.class }}" [class.active]="bar.active" [style]="bar.style" (click)="onClick($event, bar);">
|
||||||
|
<div class="fill"></div>
|
||||||
|
<div class="line">
|
||||||
|
<p class="fee-rate">
|
||||||
|
<span class="label">{{ bar.label }}</span>
|
||||||
|
<span class="rate">
|
||||||
|
<app-fee-rate [fee]="bar.rate"></app-fee-rate>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<span class="fee">{{ bar.class === 'tx' ? '' : '+' }} {{ bar.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span></span>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,157 @@
|
|||||||
|
.fee-graph {
|
||||||
|
height: 100%;
|
||||||
|
min-width: 120px;
|
||||||
|
width: 120px;
|
||||||
|
max-height: 90vh;
|
||||||
|
margin-left: 4em;
|
||||||
|
margin-right: 1.5em;
|
||||||
|
padding-bottom: 63px;
|
||||||
|
|
||||||
|
.column {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
background: #181b2d;
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.fill {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
opacity: 0.75;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fee {
|
||||||
|
font-size: 0.9em;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
flex-grow: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
left: -4.5em;
|
||||||
|
border-top: dashed white 1.5px;
|
||||||
|
|
||||||
|
.fee-rate {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0.2em;
|
||||||
|
font-size: 0.8em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
margin-right: .2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rate .symbol {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tx {
|
||||||
|
.fill {
|
||||||
|
background: #3bcc49;
|
||||||
|
}
|
||||||
|
.line {
|
||||||
|
.fee-rate {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fee {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.target {
|
||||||
|
.fill {
|
||||||
|
background: #653b9c;
|
||||||
|
}
|
||||||
|
.fee {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
.line .fee-rate {
|
||||||
|
bottom: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.max {
|
||||||
|
cursor: pointer;
|
||||||
|
.line .fee-rate {
|
||||||
|
.label {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
bottom: 2px;
|
||||||
|
}
|
||||||
|
&.active, &:hover {
|
||||||
|
.fill {
|
||||||
|
background: #105fb0;
|
||||||
|
}
|
||||||
|
.line {
|
||||||
|
.fee-rate .label {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.fill {
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.line {
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
.fee {
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover > .bar:not(:hover) {
|
||||||
|
&.target, &.max {
|
||||||
|
.fee {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.line .fee-rate .label {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.max {
|
||||||
|
.fill {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
import { Component, OnInit, Input, Output, OnChanges, EventEmitter, HostListener, Inject, LOCALE_ID } from '@angular/core';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.interface';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { ReplaySubject, merge, Subscription, of } from 'rxjs';
|
||||||
|
import { tap, switchMap } from 'rxjs/operators';
|
||||||
|
import { ApiService } from '../../services/api.service';
|
||||||
|
import { AccelerationEstimate, RateOption } from './accelerate-preview.component';
|
||||||
|
|
||||||
|
interface GraphBar {
|
||||||
|
rate: number;
|
||||||
|
style: any;
|
||||||
|
class: 'tx' | 'target' | 'max';
|
||||||
|
label: string;
|
||||||
|
active?: boolean;
|
||||||
|
rateIndex?: number;
|
||||||
|
fee?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-accelerate-fee-graph',
|
||||||
|
templateUrl: './accelerate-fee-graph.component.html',
|
||||||
|
styleUrls: ['./accelerate-fee-graph.component.scss'],
|
||||||
|
})
|
||||||
|
export class AccelerateFeeGraphComponent implements OnInit, OnChanges {
|
||||||
|
@Input() tx: Transaction;
|
||||||
|
@Input() estimate: AccelerationEstimate;
|
||||||
|
@Input() maxRateOptions: RateOption[] = [];
|
||||||
|
@Input() maxRateIndex: number = 0;
|
||||||
|
@Output() setUserBid = new EventEmitter<{ fee: number, index: number }>();
|
||||||
|
|
||||||
|
bars: GraphBar[] = [];
|
||||||
|
tooltipPosition = { x: 0, y: 0 };
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.initGraph();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(): void {
|
||||||
|
this.initGraph();
|
||||||
|
}
|
||||||
|
|
||||||
|
initGraph(): void {
|
||||||
|
if (!this.tx || !this.estimate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const maxRate = Math.max(...this.maxRateOptions.map(option => option.rate));
|
||||||
|
const baseRate = this.estimate.txSummary.effectiveFee / this.estimate.txSummary.effectiveVsize;
|
||||||
|
const baseHeight = baseRate / maxRate;
|
||||||
|
const bars: GraphBar[] = this.maxRateOptions.slice().reverse().map(option => {
|
||||||
|
return {
|
||||||
|
rate: option.rate,
|
||||||
|
style: this.getStyle(option.rate, maxRate, baseHeight),
|
||||||
|
class: 'max',
|
||||||
|
label: 'maximum',
|
||||||
|
active: option.index === this.maxRateIndex,
|
||||||
|
rateIndex: option.index,
|
||||||
|
fee: option.fee,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
bars.push({
|
||||||
|
rate: this.estimate.targetFeeRate,
|
||||||
|
style: this.getStyle(this.estimate.targetFeeRate, maxRate, baseHeight),
|
||||||
|
class: 'target',
|
||||||
|
label: 'next block',
|
||||||
|
fee: this.estimate.nextBlockFee - this.estimate.txSummary.effectiveFee
|
||||||
|
});
|
||||||
|
bars.push({
|
||||||
|
rate: baseRate,
|
||||||
|
style: this.getStyle(baseRate, maxRate, 0),
|
||||||
|
class: 'tx',
|
||||||
|
label: '',
|
||||||
|
fee: this.estimate.txSummary.effectiveFee,
|
||||||
|
});
|
||||||
|
this.bars = bars;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStyle(rate, maxRate, base) {
|
||||||
|
const top = (rate / maxRate);
|
||||||
|
return {
|
||||||
|
height: `${(top - base) * 100}%`,
|
||||||
|
bottom: base ? `${base * 100}%` : '0',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick(event, bar): void {
|
||||||
|
if (bar.rateIndex != null) {
|
||||||
|
this.setUserBid.emit({ fee: bar.fee, index: bar.rateIndex });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('pointermove', ['$event'])
|
||||||
|
onPointerMove(event) {
|
||||||
|
this.tooltipPosition = { x: event.offsetX, y: event.offsetY };
|
||||||
|
}
|
||||||
|
}
|
@ -12,220 +12,251 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row" *ngIf="!error && estimate?.txSummary?.ancestorCount ?? 0 > 1">
|
<div class="accelerate-cols">
|
||||||
<div class="col">
|
<ng-container *ngIf="!isMobile">
|
||||||
<div class="alert alert-mempool">
|
<app-accelerate-fee-graph
|
||||||
<small>This transactions is part of a CPFP tree. Fee rates (in sats/vb) are provided for your information. Change in the CPFP tree will lead to different fee rates values.</small>
|
[tx]="tx"
|
||||||
</div>
|
[estimate]="estimate"
|
||||||
</div>
|
[maxRateOptions]="maxRateOptions"
|
||||||
</div>
|
[maxRateIndex]="selectFeeRateIndex"
|
||||||
|
(setUserBid)="setUserBid($event)"
|
||||||
|
></app-accelerate-fee-graph>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="estimate">
|
<ng-container *ngIf="estimate">
|
||||||
<div [class]="{estimateDisabled: error}">
|
<div [class]="{estimateDisabled: error}">
|
||||||
<div class="row mb-3">
|
<h5>Your transaction</h5>
|
||||||
<div class="col">
|
<div class="row">
|
||||||
<table class="table table-borderless table-border table-dark table-accelerator">
|
<div class="col">
|
||||||
<tbody>
|
<small *ngIf="hasAncestors" class="form-text text-muted mb-2">
|
||||||
<!-- NEXT BLOCK TX FEE -->
|
Plus {{ estimate.txSummary.ancestorCount - 1 }} unconfirmed ancestor{{ estimate.txSummary.ancestorCount > 2 ? 's' : ''}}.
|
||||||
<tr>
|
</small>
|
||||||
<td style="width: 45%" class="pb-0">
|
<table class="table table-borderless table-border table-dark table-accelerator">
|
||||||
Next block market price
|
<tbody>
|
||||||
</td>
|
<tr class="group-first">
|
||||||
<td class="pb-0 text-right" style="font-size: 20px">
|
<td class="item">
|
||||||
{{ estimate.targetFeeRate | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
Virtual size
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
<td class="units" [innerHTML]="'‎' + (estimate.txSummary.effectiveVsize | vbytes: 2)"></td>
|
||||||
<tr>
|
</tr>
|
||||||
<td style="width: 45%" class="pt-0 pb-0 text-muted">
|
<tr class="info">
|
||||||
<i><small>Currently estimated fee to get into next block</small></i>
|
<td class="info">
|
||||||
</td>
|
<i><small>Size in vbytes of this transaction<span *ngIf="hasAncestors"> and its unconfirmed ancestors</span></small></i>
|
||||||
<td class="pt-0 pb-0 text-right">
|
</td>
|
||||||
<span>
|
</tr>
|
||||||
{{ estimate.nextBlockFee| number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
<tr>
|
||||||
<span class="fiat"><app-fiat [value]="estimate.nextBlockFee"></app-fiat></span>
|
<td class="item">
|
||||||
</span>
|
In-band fees
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
<td class="units">
|
||||||
|
{{ estimate.txSummary.effectiveFee | number : '1.0-0' }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||||
<!-- CURRENT TX FEE -->
|
</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td style="width: 45%" class="pb-0">
|
<tr class="info group-last">
|
||||||
Fees paid in-band
|
<td class="info">
|
||||||
</td>
|
<i><small>Fees already paid by this transaction<span *ngIf="hasAncestors"> and its unconfirmed ancestors</span></small></i>
|
||||||
<td class="pb-0 text-right" style="font-size: 20px">
|
</td>
|
||||||
<small>~</small>{{ (estimate.txSummary.effectiveFee / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
</tr>
|
||||||
</td>
|
</tbody>
|
||||||
</tr>
|
</table>
|
||||||
<tr>
|
</div>
|
||||||
<td style="width: 45%" class="pt-0 text-muted">
|
|
||||||
<i><small>What you already paid when you made the transaction</small></i>
|
|
||||||
</td>
|
|
||||||
<td class="pt-0 text-right">
|
|
||||||
<span>
|
|
||||||
{{ estimate.txSummary.effectiveFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
|
||||||
<span class="fiat"><app-fiat [value]="estimate.txSummary.effectiveFee"></app-fiat></span>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- MIN EXTRA FEE FOR NEXT BLOCK -->
|
|
||||||
<tr style="border-top: 1px solid lightgrey">
|
|
||||||
<td style="width: 45%" class="pb-0">
|
|
||||||
Extra fee required
|
|
||||||
</td>
|
|
||||||
<td class="pb-1 text-right">
|
|
||||||
{{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
|
||||||
<span class="fiat"><app-fiat [value]="math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee)"></app-fiat></span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td style="width: 45%" class="pt-0 text-muted">
|
|
||||||
<i><small>Difference between the next block fee and your tx fee</small></i>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<br>
|
||||||
|
<h5>How much more are you willing to pay?</h5>
|
||||||
<h6>How much more are you willing to pay at most to get into the next block?</h6>
|
<div class="row">
|
||||||
<div class="row">
|
<div class="col">
|
||||||
<div class="col">
|
<small class="form-text text-muted mb-2">
|
||||||
<small class="form-text text-muted mb-2">
|
Choose the maximum extra transaction fee you're willing to pay to get into the next block.<br>
|
||||||
The maximum extra transaction fee you're willing to pay to get into the next block. If the next block market price becomes too expensive for you, we will automatically cancel your acceleration request. Final charged fee may be smaller based on the fee market.
|
If the estimated next block rate rises beyond this limit, we will automatically cancel your acceleration request.
|
||||||
</small>
|
</small>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="fee-card">
|
<div class="fee-card">
|
||||||
<div class="d-flex mb-2">
|
<div class="d-flex mb-0">
|
||||||
<button type="button" class="btn btn-primary flex-grow-1 btn-border btn-sm feerate" [class]="{active: selectFeeRateIndex === 1}" (click)="setUserBid(2, 1)">{{ (estimate.txSummary.effectiveFee + minExtraCost * 2) / estimate.txSummary.effectiveVsize | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></button>
|
<ng-container *ngFor="let option of maxRateOptions">
|
||||||
<button type="button" class="btn btn-primary flex-grow-1 btn-border btn-sm feerate" [class]="{active: selectFeeRateIndex === 2}" (click)="setUserBid(5, 2)">{{ (estimate.txSummary.effectiveFee + minExtraCost * 5) / estimate.txSummary.effectiveVsize | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></button>
|
<button type="button" class="btn btn-primary flex-grow-1 btn-border btn-sm feerate" [class]="{active: selectFeeRateIndex === option.index}" (click)="setUserBid(option)">
|
||||||
<button type="button" class="btn btn-primary flex-grow-1 btn-border btn-sm feerate" [class]="{active: selectFeeRateIndex === 3}" (click)="setUserBid(10, 3)">{{ (estimate.txSummary.effectiveFee + minExtraCost * 10) / estimate.txSummary.effectiveVsize | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></button>
|
<span class="fee">{{ option.fee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span></span>
|
||||||
<button type="button" class="btn btn-primary flex-grow-1 btn-border btn-sm feerate" [class]="{active: selectFeeRateIndex === 4}" (click)="setUserBid(20, 4)">{{ (estimate.txSummary.effectiveFee + minExtraCost * 20) / estimate.txSummary.effectiveVsize | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></button>
|
<span class="rate">~ <app-fee-rate [fee]="option.rate" rounding="1.0-0"></app-fee-rate></span>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<h5>Acceleration summary</h5>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col">
|
||||||
|
<div class="table-toggle btn-group btn-group-toggle">
|
||||||
|
<div class="btn btn-primary btn-sm" [class.active]="showTable === 'estimated'" (click)="showTable = 'estimated'">
|
||||||
|
<span>Estimated cost</span>
|
||||||
|
</div>
|
||||||
|
<div class="btn btn-primary btn-sm" [class.active]="showTable === 'maximum'" (click)="showTable = 'maximum'">
|
||||||
|
<span>Maximum cost</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="table table-borderless table-border table-dark table-accelerator">
|
||||||
|
<tbody>
|
||||||
|
<!-- ESTIMATED FEE -->
|
||||||
|
<ng-container *ngIf="showTable === 'estimated'">
|
||||||
|
<tr class="group-first">
|
||||||
|
<td class="item">
|
||||||
|
Next block market rate
|
||||||
|
</td>
|
||||||
|
<td class="amt" style="font-size: 20px">
|
||||||
|
{{ estimate.targetFeeRate | number : '1.0-0' }}
|
||||||
|
</td>
|
||||||
|
<td class="units"><span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="info">
|
||||||
|
<td class="info">
|
||||||
|
<i><small>Estimated extra fee required</small></i>
|
||||||
|
</td>
|
||||||
|
<td class="amt">
|
||||||
|
{{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }}
|
||||||
|
</td>
|
||||||
|
<td class="units">
|
||||||
|
<span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||||
|
<span class="fiat"><app-fiat [value]="math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee)"></app-fiat></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
<!-- USER MAX BID -->
|
||||||
|
<ng-container *ngIf="showTable === 'maximum'">
|
||||||
|
<tr class="group-first">
|
||||||
|
<td class="item">
|
||||||
|
Your maximum
|
||||||
|
</td>
|
||||||
|
<td class="amt" style="width: 45%; font-size: 20px">
|
||||||
|
~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }}
|
||||||
|
</td>
|
||||||
|
<td class="units"><span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="info">
|
||||||
|
<td class="info">
|
||||||
|
<i><small>The maximum extra transaction fee you could pay</small></i>
|
||||||
|
</td>
|
||||||
|
<td class="amt">
|
||||||
|
<span>
|
||||||
|
{{ userBid | number }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="units">
|
||||||
|
<span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||||
|
<span class="fiat"><app-fiat [value]="userBid"></app-fiat></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- MEMPOOL BASE FEE -->
|
||||||
|
<tr>
|
||||||
|
<td class="item">
|
||||||
|
Mempool Accelerator™ fees
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="info">
|
||||||
|
<td class="info">
|
||||||
|
<i><small>mempool.space fee</small></i>
|
||||||
|
</td>
|
||||||
|
<td class="amt">
|
||||||
|
+{{ estimate.mempoolBaseFee | number }}
|
||||||
|
</td>
|
||||||
|
<td class="units">
|
||||||
|
<span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||||
|
<span class="fiat"><app-fiat [value]="estimate.mempoolBaseFee"></app-fiat></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="info group-last" style="border-bottom: 1px solid lightgrey">
|
||||||
|
<td class="info">
|
||||||
|
<i><small>Transaction vsize fee</small></i>
|
||||||
|
</td>
|
||||||
|
<td class="amt">
|
||||||
|
+{{ estimate.vsizeFee | number }}
|
||||||
|
</td>
|
||||||
|
<td class="units">
|
||||||
|
<span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||||
|
<span class="fiat"><app-fiat [value]="estimate.vsizeFee"></app-fiat></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<h6>Acceleration summary</h6>
|
<!-- NEXT BLOCK ESTIMATE -->
|
||||||
<div class="row mb-3">
|
<ng-container *ngIf="showTable === 'estimated'">
|
||||||
<div class="col">
|
<tr class="group-first">
|
||||||
<table class="table table-borderless table-border table-dark table-accelerator">
|
<td class="item">
|
||||||
<tbody>
|
<b style="background-color: #5E35B1" class="p-1 pl-0">Estimated acceleration cost</b>
|
||||||
<!-- USER MAX BID -->
|
</td>
|
||||||
<tr>
|
<td class="amt">
|
||||||
<td style="width: 45%" class="pb-0">
|
<span style="background-color: #5E35B1" class="p-1 pl-0">
|
||||||
Your maximum tx fees
|
{{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }}
|
||||||
</td>
|
</span>
|
||||||
<td class="pb-0 text-right" style="font-size: 20px">
|
</td>
|
||||||
~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
<td class="units">
|
||||||
</td>
|
<span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||||
</tr>
|
<span class="fiat"><app-fiat [value]="estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee"></app-fiat></span>
|
||||||
<tr>
|
</td>
|
||||||
<td style="width: 45%" class="pt-0 pb-0 text-muted">
|
</tr>
|
||||||
<i><small>The maximum extra transaction fee you're willing to pay</small></i>
|
<tr class="info group-last">
|
||||||
</td>
|
<td class="info">
|
||||||
<td class="pt-0 pb-0 text-right">
|
<i><small>If your tx is accelerated to </small><small>{{ estimate.targetFeeRate | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></small></i>
|
||||||
<span>
|
</td>
|
||||||
{{ userBid | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
</tr>
|
||||||
<span class="fiat"><app-fiat [value]="userBid"></app-fiat></span>
|
</ng-container>
|
||||||
</span>
|
|
||||||
</td>
|
<!-- MAX COST -->
|
||||||
</tr>
|
<ng-container *ngIf="showTable === 'maximum'">
|
||||||
|
<tr class="group-first">
|
||||||
<!-- MEMPOOL BASE FEE -->
|
<td class="item">
|
||||||
<tr>
|
<b style="background-color: #105fb0;" class="p-1 pl-0">Maximum acceleration cost</b>
|
||||||
<td style="width: 45%" class="pb-0">
|
</td>
|
||||||
Mempool Accelerator™ fee
|
<td class="amt">
|
||||||
</td>
|
<span style="background-color: #105fb0" class="p-1 pl-0">
|
||||||
<td class="pb-0 text-right">
|
{{ maxCost | number }}
|
||||||
+{{ estimate.mempoolBaseFee + estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
</span>
|
||||||
<span class="fiat"><app-fiat [value]="estimate.mempoolBaseFee + estimate.vsizeFee"></app-fiat></span>
|
</td>
|
||||||
</td>
|
<td class="units">
|
||||||
</tr>
|
<span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||||
<tr>
|
<span class="fiat">
|
||||||
<td style="width: 45%" class="pt-0 pb-0 text-muted">
|
<app-fiat [value]="maxCost" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat>
|
||||||
<i><small>mempool.space fee</small></i>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="pt-0 pb-0 text-right text-muted">
|
</tr>
|
||||||
<small>
|
<tr class="info group-last">
|
||||||
{{ estimate.mempoolBaseFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
<td class="info">
|
||||||
<span class="fiat"><app-fiat [value]="estimate.mempoolBaseFee" colorClass="text-success"></app-fiat></span>
|
<i><small>If your tx is accelerated to </small><small>~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></small></i>
|
||||||
</small>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
</ng-container>
|
||||||
<tr>
|
|
||||||
<td style="width: 45%" class="pt-0 text-muted">
|
<!-- USER BALANCE -->
|
||||||
<i><small>Transaction vsize fee</small></i>
|
<ng-container *ngIf="estimate.userBalance < maxCost">
|
||||||
</td>
|
<tr class="group-first group-last" style="border-top: 1px dashed grey">
|
||||||
<td class="pt-0 pb-0 text-right text-muted">
|
<td class="item">
|
||||||
<small>
|
Available balance
|
||||||
{{ estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
</td>
|
||||||
<span class="fiat"><app-fiat [value]="estimate.vsizeFee" colorClass="text-success"></app-fiat></span>
|
<td class="amt">
|
||||||
</small>
|
{{ estimate.userBalance | number }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
<td class="units">
|
||||||
|
<span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||||
<!-- NEXT BLOCK ESTIMATE -->
|
<span class="fiat">
|
||||||
<tr style="border-top: 1px solid lightgrey">
|
<app-fiat [value]="estimate.userBalance" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat>
|
||||||
<td style="width: 45%" class="pb-0 pt-3">
|
</span>
|
||||||
<b style="background-color: #5E35B1" class="p-1 pl-0">Estimated acceleration cost</b>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
<td class="pb-0 pt-3 text-right">
|
</ng-container>
|
||||||
<span style="background-color: #5E35B1" class="p-1 pl-0">
|
</tbody>
|
||||||
{{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
</table>
|
||||||
<span class="fiat"><app-fiat [value]="estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee"></app-fiat></span>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td style="width: 45%" class="pt-0 pb-0">
|
|
||||||
<i><small class="text-muted">Cost if your tx is accelerated using </small><small>{{ estimate.targetFeeRate | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></small></i>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- MAX COST -->
|
|
||||||
<tr>
|
|
||||||
<td style="width: 45%;" class="pt-2 pb-0">
|
|
||||||
Maximum acceleration cost
|
|
||||||
</td>
|
|
||||||
<td class="pt-2 pb-0 text-right">
|
|
||||||
{{ maxCost | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
|
||||||
<span class="fiat">
|
|
||||||
<app-fiat [value]="maxCost" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td style="width: 45%" class="pt-0 pb-3">
|
|
||||||
<i><small class="text-muted">Cost if your tx is accelerated using </small><small>~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></small></i>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- USER BALANCE -->
|
|
||||||
<tr style="border-top: 1px dashed grey">
|
|
||||||
<td style="width: 45%" class="pt-2">
|
|
||||||
Available balance
|
|
||||||
</td>
|
|
||||||
<td class="pt-2 text-right">
|
|
||||||
{{ estimate.userBalance | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
|
||||||
<span class="fiat">
|
|
||||||
<app-fiat [value]="estimate.userBalance" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mb-3" *ngIf="isLoggedIn()">
|
|
||||||
<div class="col">
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<button class="btn btn-sm btn-primary btn-success" style="width: 150px" (click)="accelerate()">Accelerate</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3" *ngIf="isLoggedIn()">
|
||||||
|
<div class="col">
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<button class="btn btn-sm btn-primary btn-success" style="width: 150px" (click)="accelerate()">Accelerate</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
|
@ -1,6 +1,23 @@
|
|||||||
.fee-card {
|
.fee-card {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
background-color: #1d1f31;
|
background-color: #1d1f31;
|
||||||
|
|
||||||
|
.feerate {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.fee {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
.rate {
|
||||||
|
font-size: 0.9em;
|
||||||
|
.symbol {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-border {
|
.btn-border {
|
||||||
@ -19,9 +36,53 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-toggle {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.table-accelerator {
|
.table-accelerator {
|
||||||
table-layout: fixed;
|
tr {
|
||||||
& tr {
|
|
||||||
text-wrap: wrap;
|
text-wrap: wrap;
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.group-first {
|
||||||
|
td {
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.group-last {
|
||||||
|
td {
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
td {
|
||||||
|
&:first-child {
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
&.info {
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
&.amt {
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 0.2em;
|
||||||
|
}
|
||||||
|
&.units {
|
||||||
|
padding-left: 0.2em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.accelerate-cols {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
margin-top: 1em;
|
||||||
}
|
}
|
@ -1,7 +1,9 @@
|
|||||||
import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges } from '@angular/core';
|
import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener } from '@angular/core';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
import { Subscription, catchError, of, tap } from 'rxjs';
|
import { Subscription, catchError, of, tap } from 'rxjs';
|
||||||
import { StorageService } from '../../services/storage.service';
|
import { StorageService } from '../../services/storage.service';
|
||||||
|
import { Transaction } from '../../interfaces/electrs.interface';
|
||||||
|
import { nextRoundNumber } from '../../shared/common.utils';
|
||||||
|
|
||||||
export type AccelerationEstimate = {
|
export type AccelerationEstimate = {
|
||||||
txSummary: TxSummary;
|
txSummary: TxSummary;
|
||||||
@ -20,9 +22,15 @@ export type TxSummary = {
|
|||||||
ancestorCount: number; // Number of ancestors
|
ancestorCount: number; // Number of ancestors
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_BID_RATIO = 5;
|
export interface RateOption {
|
||||||
export const MIN_BID_RATIO = 2;
|
fee: number;
|
||||||
export const MAX_BID_RATIO = 20;
|
rate: number;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MIN_BID_RATIO = 1;
|
||||||
|
export const DEFAULT_BID_RATIO = 2;
|
||||||
|
export const MAX_BID_RATIO = 4;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-accelerate-preview',
|
selector: 'app-accelerate-preview',
|
||||||
@ -30,7 +38,7 @@ export const MAX_BID_RATIO = 20;
|
|||||||
styleUrls: ['accelerate-preview.component.scss']
|
styleUrls: ['accelerate-preview.component.scss']
|
||||||
})
|
})
|
||||||
export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges {
|
export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges {
|
||||||
@Input() txid: string | undefined;
|
@Input() tx: Transaction | undefined;
|
||||||
@Input() scrollEvent: boolean;
|
@Input() scrollEvent: boolean;
|
||||||
|
|
||||||
math = Math;
|
math = Math;
|
||||||
@ -39,13 +47,18 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
estimateSubscription: Subscription;
|
estimateSubscription: Subscription;
|
||||||
accelerationSubscription: Subscription;
|
accelerationSubscription: Subscription;
|
||||||
estimate: any;
|
estimate: any;
|
||||||
|
hasAncestors: boolean = false;
|
||||||
minExtraCost = 0;
|
minExtraCost = 0;
|
||||||
minBidAllowed = 0;
|
minBidAllowed = 0;
|
||||||
maxBidAllowed = 0;
|
maxBidAllowed = 0;
|
||||||
defaultBid = 0;
|
defaultBid = 0;
|
||||||
maxCost = 0;
|
maxCost = 0;
|
||||||
userBid = 0;
|
userBid = 0;
|
||||||
selectFeeRateIndex = 2;
|
selectFeeRateIndex = 1;
|
||||||
|
showTable: 'estimated' | 'maximum' = 'maximum';
|
||||||
|
isMobile: boolean = window.innerWidth <= 767.98;
|
||||||
|
|
||||||
|
maxRateOptions: RateOption[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
@ -65,7 +78,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.estimateSubscription = this.apiService.estimate$(this.txid).pipe(
|
this.estimateSubscription = this.apiService.estimate$(this.tx.txid).pipe(
|
||||||
tap((response) => {
|
tap((response) => {
|
||||||
if (response.status === 204) {
|
if (response.status === 204) {
|
||||||
this.estimate = undefined;
|
this.estimate = undefined;
|
||||||
@ -86,14 +99,23 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
this.scrollToPreviewWithTimeout('mempoolError', 'center');
|
this.scrollToPreviewWithTimeout('mempoolError', 'center');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.hasAncestors = this.estimate.txSummary.ancestorCount > 1;
|
||||||
|
|
||||||
// Make min extra fee at least 50% of the current tx fee
|
// Make min extra fee at least 50% of the current tx fee
|
||||||
this.minExtraCost = Math.max(this.estimate.cost, this.estimate.txSummary.effectiveFee / 2);
|
this.minExtraCost = nextRoundNumber(Math.max(this.estimate.cost * 2, this.estimate.txSummary.effectiveFee));
|
||||||
this.minExtraCost = Math.round(this.minExtraCost);
|
|
||||||
|
this.maxRateOptions = [1, 2, 4].map((multiplier, index) => {
|
||||||
|
return {
|
||||||
|
fee: this.minExtraCost * multiplier,
|
||||||
|
rate: (this.estimate.txSummary.effectiveFee + (this.minExtraCost * multiplier)) / this.estimate.txSummary.effectiveVsize,
|
||||||
|
index,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
this.minBidAllowed = this.minExtraCost * MIN_BID_RATIO;
|
this.minBidAllowed = this.minExtraCost * MIN_BID_RATIO;
|
||||||
this.maxBidAllowed = this.minExtraCost * MAX_BID_RATIO;
|
|
||||||
this.defaultBid = this.minExtraCost * DEFAULT_BID_RATIO;
|
this.defaultBid = this.minExtraCost * DEFAULT_BID_RATIO;
|
||||||
|
this.maxBidAllowed = this.minExtraCost * MAX_BID_RATIO;
|
||||||
|
|
||||||
this.userBid = this.defaultBid;
|
this.userBid = this.defaultBid;
|
||||||
if (this.userBid < this.minBidAllowed) {
|
if (this.userBid < this.minBidAllowed) {
|
||||||
@ -121,10 +143,10 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
/**
|
/**
|
||||||
* User changed his bid
|
* User changed his bid
|
||||||
*/
|
*/
|
||||||
setUserBid(multiplier: number, index: number) {
|
setUserBid({ fee, index }: { fee: number, index: number}) {
|
||||||
if (this.estimate) {
|
if (this.estimate) {
|
||||||
this.selectFeeRateIndex = index;
|
this.selectFeeRateIndex = index;
|
||||||
this.userBid = Math.max(0, this.minExtraCost * multiplier);
|
this.userBid = Math.max(0, fee);
|
||||||
this.maxCost = this.userBid + this.estimate.mempoolBaseFee + this.estimate.vsizeFee;
|
this.maxCost = this.userBid + this.estimate.mempoolBaseFee + this.estimate.vsizeFee;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,7 +178,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
this.accelerationSubscription.unsubscribe();
|
this.accelerationSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
this.accelerationSubscription = this.apiService.accelerate$(
|
this.accelerationSubscription = this.apiService.accelerate$(
|
||||||
this.txid,
|
this.tx.txid,
|
||||||
this.userBid
|
this.userBid
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
@ -175,4 +197,9 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
const auth = this.storageService.getAuth();
|
const auth = this.storageService.getAuth();
|
||||||
return auth !== null;
|
return auth !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HostListener('window:resize', ['$event'])
|
||||||
|
onResize(): void {
|
||||||
|
this.isMobile = window.innerWidth <= 767.98;
|
||||||
|
}
|
||||||
}
|
}
|
@ -84,7 +84,7 @@
|
|||||||
<h2>Accelerate</h2>
|
<h2>Accelerate</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<app-accelerate-preview [txid]="txId" [scrollEvent]="scrollIntoAccelPreview"></app-accelerate-preview>
|
<app-accelerate-preview [tx]="tx" [scrollEvent]="scrollIntoAccelPreview"></app-accelerate-preview>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -135,4 +135,12 @@ export function haversineDistance(lat1: number, lon1: number, lat2: number, lon2
|
|||||||
|
|
||||||
export function kmToMiles(km: number): number {
|
export function kmToMiles(km: number): number {
|
||||||
return km * 0.62137119;
|
return km * 0.62137119;
|
||||||
|
}
|
||||||
|
|
||||||
|
const roundNumbers = [1, 2, 5, 10, 15, 20, 25, 50, 75, 100, 125, 150, 175, 200, 250, 300, 350, 400, 450, 500, 600, 700, 750, 800, 900, 1000];
|
||||||
|
export function nextRoundNumber(num: number): number {
|
||||||
|
const log = Math.floor(Math.log10(num));
|
||||||
|
const factor = log >= 3 ? Math.pow(10, log - 2) : 1;
|
||||||
|
num /= factor;
|
||||||
|
return factor * (roundNumbers.find(val => val >= num) || roundNumbers[roundNumbers.length - 1]);
|
||||||
}
|
}
|
@ -94,6 +94,7 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
|
|||||||
import { TestnetAlertComponent } from './components/testnet-alert/testnet-alert.component';
|
import { TestnetAlertComponent } from './components/testnet-alert/testnet-alert.component';
|
||||||
import { GlobalFooterComponent } from './components/global-footer/global-footer.component';
|
import { GlobalFooterComponent } from './components/global-footer/global-footer.component';
|
||||||
import { AcceleratePreviewComponent } from '../components/accelerate-preview/accelerate-preview.component';
|
import { AcceleratePreviewComponent } from '../components/accelerate-preview/accelerate-preview.component';
|
||||||
|
import { AccelerateFeeGraphComponent } from '../components/accelerate-preview/accelerate-fee-graph.component';
|
||||||
import { MempoolErrorComponent } from './components/mempool-error/mempool-error.component';
|
import { MempoolErrorComponent } from './components/mempool-error/mempool-error.component';
|
||||||
|
|
||||||
import { MempoolBlockOverviewComponent } from '../components/mempool-block-overview/mempool-block-overview.component';
|
import { MempoolBlockOverviewComponent } from '../components/mempool-block-overview/mempool-block-overview.component';
|
||||||
@ -192,6 +193,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||||||
TestnetAlertComponent,
|
TestnetAlertComponent,
|
||||||
GlobalFooterComponent,
|
GlobalFooterComponent,
|
||||||
AcceleratePreviewComponent,
|
AcceleratePreviewComponent,
|
||||||
|
AccelerateFeeGraphComponent,
|
||||||
CalculatorComponent,
|
CalculatorComponent,
|
||||||
BitcoinsatoshisPipe,
|
BitcoinsatoshisPipe,
|
||||||
MempoolBlockOverviewComponent,
|
MempoolBlockOverviewComponent,
|
||||||
@ -315,6 +317,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||||||
PreviewTitleComponent,
|
PreviewTitleComponent,
|
||||||
GlobalFooterComponent,
|
GlobalFooterComponent,
|
||||||
AcceleratePreviewComponent,
|
AcceleratePreviewComponent,
|
||||||
|
AccelerateFeeGraphComponent,
|
||||||
MempoolErrorComponent,
|
MempoolErrorComponent,
|
||||||
|
|
||||||
MempoolBlockOverviewComponent,
|
MempoolBlockOverviewComponent,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user