Accelerator fee diagram concept
This commit is contained in:
parent
c44276027c
commit
c753a8e92a
@ -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>
|
||||||
|
<span class="fee">{{ bar.class === 'tx' ? '' : '+' }} {{ bar.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span></span>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
<div class="vsize">
|
||||||
|
<span [innerHTML]="'‎' + (estimate.txSummary.effectiveVsize | vbytes: 2)"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,147 @@
|
|||||||
|
.fee-graph {
|
||||||
|
height: 100%;
|
||||||
|
width: 20%;
|
||||||
|
min-width: 100px;
|
||||||
|
max-width: 150px;
|
||||||
|
max-height: 100vh;
|
||||||
|
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;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.fill {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
opacity: 0.75;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fee {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
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: #105fb0;
|
||||||
|
}
|
||||||
|
.line {
|
||||||
|
.fee-rate {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fee {
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.target {
|
||||||
|
.fill {
|
||||||
|
background: #3bcc49;
|
||||||
|
}
|
||||||
|
.fee {
|
||||||
|
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: #653b9c;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vsize {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
@ -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: 'max',
|
||||||
|
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: 'expected',
|
||||||
|
fee: this.estimate.nextBlockFee - this.estimate.txSummary.effectiveFee
|
||||||
|
});
|
||||||
|
bars.push({
|
||||||
|
rate: baseRate,
|
||||||
|
style: this.getStyle(baseRate, maxRate, 0),
|
||||||
|
class: 'tx',
|
||||||
|
label: 'paid',
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
}
|
@ -20,212 +20,223 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="estimate">
|
<div class="accelerate-cols">
|
||||||
<div [class]="{estimateDisabled: error}">
|
<app-accelerate-fee-graph
|
||||||
<div class="row mb-3">
|
[tx]="tx"
|
||||||
<div class="col">
|
[estimate]="estimate"
|
||||||
<table class="table table-borderless table-border table-dark table-accelerator">
|
[maxRateOptions]="maxRateOptions"
|
||||||
<tbody>
|
[maxRateIndex]="selectFeeRateIndex"
|
||||||
<!-- NEXT BLOCK TX FEE -->
|
(setUserBid)="setUserBid($event)"
|
||||||
<tr>
|
></app-accelerate-fee-graph>
|
||||||
<td style="width: 45%" class="pb-0">
|
|
||||||
Next block market price
|
|
||||||
</td>
|
|
||||||
<td class="pb-0 text-right" style="font-size: 20px">
|
|
||||||
{{ estimate.targetFeeRate | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td style="width: 45%" class="pt-0 pb-0 text-muted">
|
|
||||||
<i><small>Currently estimated fee to get into next block</small></i>
|
|
||||||
</td>
|
|
||||||
<td class="pt-0 pb-0 text-right">
|
|
||||||
<span>
|
|
||||||
{{ estimate.nextBlockFee| number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
|
||||||
<span class="fiat"><app-fiat [value]="estimate.nextBlockFee"></app-fiat></span>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- CURRENT TX FEE -->
|
|
||||||
<tr>
|
|
||||||
<td style="width: 45%" class="pb-0">
|
|
||||||
Fees paid in-band
|
|
||||||
</td>
|
|
||||||
<td class="pb-0 text-right" style="font-size: 20px">
|
|
||||||
<small>~</small>{{ (estimate.txSummary.effectiveFee / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<h6>How much more are you willing to pay at most to get into the next block?</h6>
|
<ng-container *ngIf="estimate">
|
||||||
<div class="row">
|
<div [class]="{estimateDisabled: error}">
|
||||||
<div class="col">
|
<div class="row mb-3">
|
||||||
<small class="form-text text-muted mb-2">
|
<div class="col">
|
||||||
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.
|
<table class="table table-borderless table-border table-dark table-accelerator">
|
||||||
</small>
|
<tbody>
|
||||||
<div class="form-group">
|
<!-- NEXT BLOCK TX FEE -->
|
||||||
<div class="fee-card">
|
<tr>
|
||||||
<div class="d-flex mb-2">
|
<td style="width: 45%" class="pb-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>
|
Next block market price
|
||||||
<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>
|
</td>
|
||||||
<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>
|
<td class="pb-0 text-right" style="font-size: 20px">
|
||||||
<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>
|
{{ estimate.targetFeeRate | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 45%" class="pt-0 pb-0 text-muted">
|
||||||
|
<i><small>Currently estimated fee to get into next block</small></i>
|
||||||
|
</td>
|
||||||
|
<td class="pt-0 pb-0 text-right">
|
||||||
|
<span>
|
||||||
|
{{ estimate.nextBlockFee| number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||||
|
<span class="fiat"><app-fiat [value]="estimate.nextBlockFee"></app-fiat></span>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- CURRENT TX FEE -->
|
||||||
|
<tr>
|
||||||
|
<td style="width: 45%" class="pb-0">
|
||||||
|
Fees paid in-band
|
||||||
|
</td>
|
||||||
|
<td class="pb-0 text-right" style="font-size: 20px">
|
||||||
|
<small>~</small>{{ (estimate.txSummary.effectiveFee / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<h6>How much more are you willing to pay at most to get into the next block?</h6>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<small class="form-text text-muted mb-2">
|
||||||
|
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.
|
||||||
|
</small>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="fee-card">
|
||||||
|
<div class="d-flex mb-2">
|
||||||
|
<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 === option.index}" (click)="setUserBid(option)">
|
||||||
|
{{ option.rate | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<h6>Acceleration summary</h6>
|
||||||
<h6>Acceleration summary</h6>
|
<div class="row mb-3">
|
||||||
<div class="row mb-3">
|
<div class="col">
|
||||||
<div class="col">
|
<table class="table table-borderless table-border table-dark table-accelerator">
|
||||||
<table class="table table-borderless table-border table-dark table-accelerator">
|
<tbody>
|
||||||
<tbody>
|
<!-- USER MAX BID -->
|
||||||
<!-- USER MAX BID -->
|
<tr>
|
||||||
<tr>
|
<td style="width: 45%" class="pb-0">
|
||||||
<td style="width: 45%" class="pb-0">
|
Your maximum tx fees
|
||||||
Your maximum tx fees
|
</td>
|
||||||
</td>
|
<td class="pb-0 text-right" style="font-size: 20px">
|
||||||
<td class="pb-0 text-right" style="font-size: 20px">
|
~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
||||||
~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td style="width: 45%" class="pt-0 pb-0 text-muted">
|
||||||
<td style="width: 45%" class="pt-0 pb-0 text-muted">
|
<i><small>The maximum extra transaction fee you're willing to pay</small></i>
|
||||||
<i><small>The maximum extra transaction fee you're willing to pay</small></i>
|
</td>
|
||||||
</td>
|
<td class="pt-0 pb-0 text-right">
|
||||||
<td class="pt-0 pb-0 text-right">
|
<span>
|
||||||
<span>
|
{{ userBid | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||||
{{ userBid | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
<span class="fiat"><app-fiat [value]="userBid"></app-fiat></span>
|
||||||
<span class="fiat"><app-fiat [value]="userBid"></app-fiat></span>
|
</span>
|
||||||
</span>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
|
||||||
|
<!-- MEMPOOL BASE FEE -->
|
||||||
<!-- MEMPOOL BASE FEE -->
|
<tr>
|
||||||
<tr>
|
<td style="width: 45%" class="pb-0">
|
||||||
<td style="width: 45%" class="pb-0">
|
Mempool Accelerator™ fee
|
||||||
Mempool Accelerator™ fee
|
</td>
|
||||||
</td>
|
<td class="pb-0 text-right">
|
||||||
<td class="pb-0 text-right">
|
+{{ estimate.mempoolBaseFee + estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||||
+{{ estimate.mempoolBaseFee + estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
<span class="fiat"><app-fiat [value]="estimate.mempoolBaseFee + estimate.vsizeFee"></app-fiat></span>
|
||||||
<span class="fiat"><app-fiat [value]="estimate.mempoolBaseFee + estimate.vsizeFee"></app-fiat></span>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td style="width: 45%" class="pt-0 pb-0 text-muted">
|
||||||
<td style="width: 45%" class="pt-0 pb-0 text-muted">
|
<i><small>mempool.space fee</small></i>
|
||||||
<i><small>mempool.space fee</small></i>
|
</td>
|
||||||
</td>
|
<td class="pt-0 pb-0 text-right text-muted">
|
||||||
<td class="pt-0 pb-0 text-right text-muted">
|
<small>
|
||||||
<small>
|
{{ estimate.mempoolBaseFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||||
{{ estimate.mempoolBaseFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
<span class="fiat"><app-fiat [value]="estimate.mempoolBaseFee" colorClass="text-success"></app-fiat></span>
|
||||||
<span class="fiat"><app-fiat [value]="estimate.mempoolBaseFee" colorClass="text-success"></app-fiat></span>
|
</small>
|
||||||
</small>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td style="width: 45%" class="pt-0 text-muted">
|
||||||
<td style="width: 45%" class="pt-0 text-muted">
|
<i><small>Transaction vsize fee</small></i>
|
||||||
<i><small>Transaction vsize fee</small></i>
|
</td>
|
||||||
</td>
|
<td class="pt-0 pb-0 text-right text-muted">
|
||||||
<td class="pt-0 pb-0 text-right text-muted">
|
<small>
|
||||||
<small>
|
{{ estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||||
{{ estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
<span class="fiat"><app-fiat [value]="estimate.vsizeFee" colorClass="text-success"></app-fiat></span>
|
||||||
<span class="fiat"><app-fiat [value]="estimate.vsizeFee" colorClass="text-success"></app-fiat></span>
|
</small>
|
||||||
</small>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
|
||||||
|
<!-- NEXT BLOCK ESTIMATE -->
|
||||||
<!-- NEXT BLOCK ESTIMATE -->
|
<tr style="border-top: 1px solid lightgrey">
|
||||||
<tr style="border-top: 1px solid lightgrey">
|
<td style="width: 45%" class="pb-0 pt-3">
|
||||||
<td style="width: 45%" class="pb-0 pt-3">
|
<b style="background-color: #5E35B1" class="p-1 pl-0">Estimated acceleration cost</b>
|
||||||
<b style="background-color: #5E35B1" class="p-1 pl-0">Estimated acceleration cost</b>
|
</td>
|
||||||
</td>
|
<td class="pb-0 pt-3 text-right">
|
||||||
<td class="pb-0 pt-3 text-right">
|
<span style="background-color: #5E35B1" class="p-1 pl-0">
|
||||||
<span style="background-color: #5E35B1" class="p-1 pl-0">
|
{{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||||
{{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
<span class="fiat"><app-fiat [value]="estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee"></app-fiat></span>
|
||||||
<span class="fiat"><app-fiat [value]="estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee"></app-fiat></span>
|
</span>
|
||||||
</span>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td style="width: 45%" class="pt-0 pb-0">
|
||||||
<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>
|
||||||
<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>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
|
||||||
|
<!-- MAX COST -->
|
||||||
<!-- MAX COST -->
|
<tr>
|
||||||
<tr>
|
<td style="width: 45%;" class="pt-2 pb-0">
|
||||||
<td style="width: 45%;" class="pt-2 pb-0">
|
Maximum acceleration cost
|
||||||
Maximum acceleration cost
|
</td>
|
||||||
</td>
|
<td class="pt-2 pb-0 text-right">
|
||||||
<td class="pt-2 pb-0 text-right">
|
{{ maxCost | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||||
{{ maxCost | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
<span class="fiat">
|
||||||
<span class="fiat">
|
<app-fiat [value]="maxCost" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat>
|
||||||
<app-fiat [value]="maxCost" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat>
|
</span>
|
||||||
</span>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td style="width: 45%" class="pt-0 pb-3">
|
||||||
<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>
|
||||||
<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>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
|
||||||
|
<!-- USER BALANCE -->
|
||||||
<!-- USER BALANCE -->
|
<tr style="border-top: 1px dashed grey">
|
||||||
<tr style="border-top: 1px dashed grey">
|
<td style="width: 45%" class="pt-2">
|
||||||
<td style="width: 45%" class="pt-2">
|
Available balance
|
||||||
Available balance
|
</td>
|
||||||
</td>
|
<td class="pt-2 text-right">
|
||||||
<td class="pt-2 text-right">
|
{{ estimate.userBalance | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||||
{{ estimate.userBalance | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
<span class="fiat">
|
||||||
<span class="fiat">
|
<app-fiat [value]="estimate.userBalance" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat>
|
||||||
<app-fiat [value]="estimate.userBalance" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat>
|
</span>
|
||||||
</span>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
</tbody>
|
||||||
</tbody>
|
</table>
|
||||||
</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>
|
|
@ -24,4 +24,11 @@
|
|||||||
& tr {
|
& tr {
|
||||||
text-wrap: wrap;
|
text-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.accelerate-cols {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
margin-top: 1em;
|
||||||
}
|
}
|
@ -2,6 +2,7 @@ import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges } from '@
|
|||||||
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';
|
||||||
|
|
||||||
export type AccelerationEstimate = {
|
export type AccelerationEstimate = {
|
||||||
txSummary: TxSummary;
|
txSummary: TxSummary;
|
||||||
@ -20,17 +21,24 @@ export type TxSummary = {
|
|||||||
ancestorCount: number; // Number of ancestors
|
ancestorCount: number; // Number of ancestors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RateOption {
|
||||||
|
fee: number;
|
||||||
|
rate: number;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
export const DEFAULT_BID_RATIO = 5;
|
export const DEFAULT_BID_RATIO = 5;
|
||||||
export const MIN_BID_RATIO = 2;
|
export const MIN_BID_RATIO = 2;
|
||||||
export const MAX_BID_RATIO = 20;
|
export const MAX_BID_RATIO = 20;
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-accelerate-preview',
|
selector: 'app-accelerate-preview',
|
||||||
templateUrl: 'accelerate-preview.component.html',
|
templateUrl: 'accelerate-preview.component.html',
|
||||||
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;
|
||||||
@ -47,6 +55,8 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
userBid = 0;
|
userBid = 0;
|
||||||
selectFeeRateIndex = 2;
|
selectFeeRateIndex = 2;
|
||||||
|
|
||||||
|
maxRateOptions: RateOption[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private storageService: StorageService
|
private storageService: StorageService
|
||||||
@ -65,7 +75,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;
|
||||||
@ -88,8 +98,15 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 = Math.round(Math.max(this.estimate.cost, this.estimate.txSummary.effectiveFee / 2));
|
||||||
this.minExtraCost = Math.round(this.minExtraCost);
|
|
||||||
|
this.maxRateOptions = [2, 5, 10, 20].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.maxBidAllowed = this.minExtraCost * MAX_BID_RATIO;
|
||||||
@ -121,10 +138,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 +173,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: () => {
|
||||||
|
@ -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>
|
||||||
|
@ -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