Simplify acceleration quote

This commit is contained in:
Mononaut 2023-08-30 16:49:54 +09:00
parent c753a8e92a
commit cb363aca23
No known key found for this signature in database
GPG Key ID: A3F058E41374C04E
7 changed files with 253 additions and 160 deletions

View File

@ -11,11 +11,11 @@
</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 class="vsize">
<span [innerHTML]="'&lrm;' + (estimate.txSummary.effectiveVsize | vbytes: 2)"></span>
</div>
</div>

View File

@ -1,9 +1,8 @@
.fee-graph {
height: 100%;
width: 20%;
min-width: 100px;
max-width: 150px;
max-height: 100vh;
min-width: 120px;
width: 120px;
max-height: 90vh;
margin-left: 4em;
margin-right: 1.5em;
padding-bottom: 63px;
@ -20,6 +19,7 @@
left: 0;
right: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
@ -34,11 +34,18 @@
}
.fee {
position: absolute;
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;
@ -69,7 +76,7 @@
&.tx {
.fill {
background: #105fb0;
background: #3bcc49;
}
.line {
.fee-rate {
@ -77,6 +84,7 @@
}
}
.fee {
position: absolute;
opacity: 1;
z-index: 11;
}
@ -84,9 +92,10 @@
&.target {
.fill {
background: #3bcc49;
background: #653b9c;
}
.fee {
position: absolute;
opacity: 1;
z-index: 11;
}
@ -105,7 +114,7 @@
}
&.active, &:hover {
.fill {
background: #653b9c;
background: #105fb0;
}
.line {
.fee-rate .label {
@ -138,10 +147,11 @@
opacity: 0;
}
}
&.max {
.fill {
background: none;
}
}
}
}
.vsize {
text-align: center;
}
}

View File

@ -52,7 +52,7 @@ export class AccelerateFeeGraphComponent implements OnInit, OnChanges {
rate: option.rate,
style: this.getStyle(option.rate, maxRate, baseHeight),
class: 'max',
label: 'max',
label: 'maximum',
active: option.index === this.maxRateIndex,
rateIndex: option.index,
fee: option.fee,
@ -62,14 +62,14 @@ export class AccelerateFeeGraphComponent implements OnInit, OnChanges {
rate: this.estimate.targetFeeRate,
style: this.getStyle(this.estimate.targetFeeRate, maxRate, baseHeight),
class: 'target',
label: 'expected',
label: 'next block',
fee: this.estimate.nextBlockFee - this.estimate.txSummary.effectiveFee
});
bars.push({
rate: baseRate,
style: this.getStyle(baseRate, maxRate, 0),
class: 'tx',
label: 'paid',
label: '',
fee: this.estimate.txSummary.effectiveFee,
});
this.bars = bars;

View File

@ -12,14 +12,6 @@
</div>
</div>
<div class="row" *ngIf="!error && estimate?.txSummary?.ancestorCount ?? 0 > 1">
<div class="col">
<div class="alert alert-mempool">
<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>
</div>
</div>
</div>
<div class="accelerate-cols">
<app-accelerate-fee-graph
[tx]="tx"
@ -31,84 +23,57 @@
<ng-container *ngIf="estimate">
<div [class]="{estimateDisabled: error}">
<div class="row mb-3">
<h5>Your transaction</h5>
<div class="row">
<div class="col">
<small *ngIf="hasAncestors" class="form-text text-muted mb-2">
Plus {{ estimate.txSummary.ancestorCount - 1 }} unconfirmed ancestor{{ estimate.txSummary.ancestorCount > 2 ? 's' : ''}}.
</small>
<table class="table table-borderless table-border table-dark table-accelerator">
<tbody>
<!-- NEXT BLOCK TX FEE -->
<tr>
<td style="width: 45%" class="pb-0">
Next block market price
<tr class="group-first">
<td class="item">
Virtual size
</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 class="units" [innerHTML]="'&lrm;' + (estimate.txSummary.effectiveVsize | vbytes: 2)"></td>
</tr>
<tr class="info">
<td class="info">
<i><small>Size in vbytes of this transaction<span *ngIf="hasAncestors"> and its unconfirmed ancestors</span></small></i>
</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 class="item">
In-band fees
</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 class="units">
{{ estimate.txSummary.effectiveFee | number : '1.0-0' }} <span class="symbol" i18n="shared.sats|sats">sats</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>
<tr class="info group-last">
<td class="info">
<i><small>Fees already paid by this transaction<span *ngIf="hasAncestors"> and its unconfirmed ancestors</span></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>
<br>
<h5>How much more are you willing to pay?</h5>
<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.
Choose the maximum extra transaction fee you're willing to pay to get into the next block.<br>
If the estimated next block rate rises beyond this limit, we will automatically cancel your acceleration request.
</small>
<div class="form-group">
<div class="fee-card">
<div class="d-flex mb-2">
<div class="d-flex mb-0">
<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>
<span class="fee">{{ option.fee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span></span>
<span class="rate">~ <app-fee-rate [fee]="option.rate" rounding="1.0-0"></app-fee-rate></span>
</button>
</ng-container>
</div>
@ -117,108 +82,159 @@
</div>
</div>
<h6>Acceleration summary</h6>
<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 -->
<tr>
<td style="width: 45%" class="pb-0">
Your maximum tx fees
</td>
<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>
</td>
</tr>
<tr>
<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>
</td>
<td class="pt-0 pb-0 text-right">
<span>
{{ userBid | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
<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>
</span>
</td>
</tr>
</td>
</tr>
</ng-container>
<!-- MEMPOOL BASE FEE -->
<tr>
<td style="width: 45%" class="pb-0">
Mempool Accelerator™ fee
</td>
<td class="pb-0 text-right">
+{{ 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>
<td class="item">
Mempool Accelerator™ fees
</td>
</tr>
<tr>
<td style="width: 45%" class="pt-0 pb-0 text-muted">
<tr class="info">
<td class="info">
<i><small>mempool.space fee</small></i>
</td>
<td class="pt-0 pb-0 text-right text-muted">
<small>
{{ 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>
</small>
<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>
<td style="width: 45%" class="pt-0 text-muted">
<tr class="info group-last" style="border-bottom: 1px solid lightgrey">
<td class="info">
<i><small>Transaction vsize fee</small></i>
</td>
<td class="pt-0 pb-0 text-right text-muted">
<small>
{{ 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>
</small>
<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>
<!-- NEXT BLOCK ESTIMATE -->
<tr style="border-top: 1px solid lightgrey">
<td style="width: 45%" class="pb-0 pt-3">
<b style="background-color: #5E35B1" class="p-1 pl-0">Estimated acceleration cost</b>
</td>
<td class="pb-0 pt-3 text-right">
<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>
<ng-container *ngIf="showTable === 'estimated'">
<tr class="group-first">
<td class="item">
<b style="background-color: #5E35B1" class="p-1 pl-0">Estimated acceleration cost</b>
</td>
<td class="amt">
<span style="background-color: #5E35B1" class="p-1 pl-0">
{{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }}
</span>
</td>
<td class="units">
<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>
</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>
</td>
</tr>
<tr class="info group-last">
<td class="info">
<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>
</td>
</tr>
</ng-container>
<!-- 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>
<ng-container *ngIf="showTable === 'maximum'">
<tr class="group-first">
<td class="item">
<b style="background-color: #105fb0;" class="p-1 pl-0">Maximum acceleration cost</b>
</td>
<td class="amt">
<span style="background-color: #105fb0" class="p-1 pl-0">
{{ maxCost | number }}
</span>
</td>
<td class="units">
<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 class="info group-last">
<td class="info">
<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>
</td>
</tr>
</ng-container>
<!-- USER BALANCE -->
<tr style="border-top: 1px dashed grey">
<td style="width: 45%" class="pt-2">
<tr class="group-first group-last" style="border-top: 1px dashed grey">
<td class="item">
Available balance
</td>
<td class="pt-2 text-right">
{{ estimate.userBalance | number }} <span class="symbol" i18n="shared.sats|sats">sats</span>
<td class="amt">
{{ estimate.userBalance | number }}
</td>
<td class="units">
<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>

View File

@ -1,6 +1,23 @@
.fee-card {
padding: 15px;
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 {
@ -19,10 +36,47 @@
pointer-events: none;
}
.table-toggle {
width: 100%;
margin-top: 0.5em;
}
.table-accelerator {
table-layout: fixed;
& tr {
tr {
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;
}
}
}

View File

@ -3,6 +3,7 @@ import { ApiService } from '../../services/api.service';
import { Subscription, catchError, of, tap } from 'rxjs';
import { StorageService } from '../../services/storage.service';
import { Transaction } from '../../interfaces/electrs.interface';
import { nextRoundNumber } from '../../shared/common.utils';
export type AccelerationEstimate = {
txSummary: TxSummary;
@ -47,13 +48,15 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
estimateSubscription: Subscription;
accelerationSubscription: Subscription;
estimate: any;
hasAncestors: boolean = false;
minExtraCost = 0;
minBidAllowed = 0;
maxBidAllowed = 0;
defaultBid = 0;
maxCost = 0;
userBid = 0;
selectFeeRateIndex = 2;
selectFeeRateIndex = 1;
showTable: 'estimated' | 'maximum' = 'maximum';
maxRateOptions: RateOption[] = [];
@ -96,11 +99,13 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
this.scrollToPreviewWithTimeout('mempoolError', 'center');
}
}
this.hasAncestors = this.estimate.txSummary.ancestorCount > 1;
// Make min extra fee at least 50% of the current tx fee
this.minExtraCost = Math.round(Math.max(this.estimate.cost, this.estimate.txSummary.effectiveFee / 2));
this.minExtraCost = nextRoundNumber(Math.max(this.estimate.cost * 2, this.estimate.txSummary.effectiveFee));
this.maxRateOptions = [2, 5, 10, 20].map((multiplier, index) => {
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,

View File

@ -135,4 +135,12 @@ export function haversineDistance(lat1: number, lon1: number, lat2: number, lon2
export function kmToMiles(km: number): number {
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]);
}