From c753a8e92af5ec56599fa50c592c9430e1c3f8cc Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 29 Aug 2023 21:20:36 +0900 Subject: [PATCH 1/5] Accelerator fee diagram concept --- .../accelerate-fee-graph.component.html | 21 + .../accelerate-fee-graph.component.scss | 147 +++++++ .../accelerate-fee-graph.component.ts | 96 ++++ .../accelerate-preview.component.html | 413 +++++++++--------- .../accelerate-preview.component.scss | 7 + .../accelerate-preview.component.ts | 31 +- .../transaction/transaction.component.html | 2 +- frontend/src/app/shared/shared.module.ts | 3 + 8 files changed, 511 insertions(+), 209 deletions(-) create mode 100644 frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.html create mode 100644 frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.scss create mode 100644 frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.ts diff --git a/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.html b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.html new file mode 100644 index 000000000..727f833bc --- /dev/null +++ b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.html @@ -0,0 +1,21 @@ +
+
+ +
+
+
+

+ {{ bar.label }} + + + +

+
+ {{ bar.class === 'tx' ? '' : '+' }} {{ bar.fee | number }} sat +
+
+
+
+ +
+
diff --git a/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.scss b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.scss new file mode 100644 index 000000000..84d530e34 --- /dev/null +++ b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.scss @@ -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; + } +} \ No newline at end of file diff --git a/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.ts b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.ts new file mode 100644 index 000000000..98b47bea0 --- /dev/null +++ b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.ts @@ -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 }; + } +} diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html index c2bcbb7bf..ad96eb671 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html @@ -20,212 +20,223 @@ - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Next block market price - - {{ estimate.targetFeeRate | number : '1.0-0' }} sat/vB -
- Currently estimated fee to get into next block - - - {{ estimate.nextBlockFee| number }} sats - - -
- Fees paid in-band - - ~{{ (estimate.txSummary.effectiveFee / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} sat/vB -
- What you already paid when you made the transaction - - - {{ estimate.txSummary.effectiveFee | number }} sats - - -
- Extra fee required - - {{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }} sats - -
- Difference between the next block fee and your tx fee -
-
-
+
+ -
How much more are you willing to pay at most to get into the next block?
-
-
- - 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. - -
-
-
- - - - + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Next block market price + + {{ estimate.targetFeeRate | number : '1.0-0' }} sat/vB +
+ Currently estimated fee to get into next block + + + {{ estimate.nextBlockFee| number }} sats + + +
+ Fees paid in-band + + ~{{ (estimate.txSummary.effectiveFee / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} sat/vB +
+ What you already paid when you made the transaction + + + {{ estimate.txSummary.effectiveFee | number }} sats + + +
+ Extra fee required + + {{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }} sats + +
+ Difference between the next block fee and your tx fee +
+
+
+ +
How much more are you willing to pay at most to get into the next block?
+
+
+ + 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. + +
+
+
+ + + +
-
- -
Acceleration summary
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Your maximum tx fees - - ~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} sat/vB -
- The maximum extra transaction fee you're willing to pay - - - {{ userBid | number }} sats - - -
- Mempool Accelerator™ fee - - +{{ estimate.mempoolBaseFee + estimate.vsizeFee | number }} sats - -
- mempool.space fee - - - {{ estimate.mempoolBaseFee | number }} sats - - -
- Transaction vsize fee - - - {{ estimate.vsizeFee | number }} sats - - -
- Estimated acceleration cost - - - {{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }} sats - - -
- Cost if your tx is accelerated using {{ estimate.targetFeeRate | number : '1.0-0' }} sat/vB -
- Maximum acceleration cost - - {{ maxCost | number }} sats - - - -
- Cost if your tx is accelerated using ~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} sat/vB -
- Available balance - - {{ estimate.userBalance | number }} sats - - - -
-
-
- -
-
-
- + +
Acceleration summary
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Your maximum tx fees + + ~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} sat/vB +
+ The maximum extra transaction fee you're willing to pay + + + {{ userBid | number }} sats + + +
+ Mempool Accelerator™ fee + + +{{ estimate.mempoolBaseFee + estimate.vsizeFee | number }} sats + +
+ mempool.space fee + + + {{ estimate.mempoolBaseFee | number }} sats + + +
+ Transaction vsize fee + + + {{ estimate.vsizeFee | number }} sats + + +
+ Estimated acceleration cost + + + {{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }} sats + + +
+ Cost if your tx is accelerated using {{ estimate.targetFeeRate | number : '1.0-0' }} sat/vB +
+ Maximum acceleration cost + + {{ maxCost | number }} sats + + + +
+ Cost if your tx is accelerated using ~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} sat/vB +
+ Available balance + + {{ estimate.userBalance | number }} sats + + + +
+ +
+
+
+ +
+
+
+
- -
- \ No newline at end of file + +
\ No newline at end of file diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.scss b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.scss index e6c717369..f6e68a77a 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.scss +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.scss @@ -24,4 +24,11 @@ & tr { text-wrap: wrap; } +} + +.accelerate-cols { + display: flex; + flex-direction: row; + align-items: stretch; + margin-top: 1em; } \ No newline at end of file diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts index b4c7af704..27074c724 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges } from '@ 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'; export type AccelerationEstimate = { txSummary: TxSummary; @@ -20,17 +21,24 @@ export type TxSummary = { ancestorCount: number; // Number of ancestors } +export interface RateOption { + fee: number; + rate: number; + index: number; +} + export const DEFAULT_BID_RATIO = 5; export const MIN_BID_RATIO = 2; export const MAX_BID_RATIO = 20; + @Component({ selector: 'app-accelerate-preview', templateUrl: 'accelerate-preview.component.html', styleUrls: ['accelerate-preview.component.scss'] }) export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges { - @Input() txid: string | undefined; + @Input() tx: Transaction | undefined; @Input() scrollEvent: boolean; math = Math; @@ -47,6 +55,8 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges userBid = 0; selectFeeRateIndex = 2; + maxRateOptions: RateOption[] = []; + constructor( private apiService: ApiService, private storageService: StorageService @@ -65,7 +75,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges } ngOnInit() { - this.estimateSubscription = this.apiService.estimate$(this.txid).pipe( + this.estimateSubscription = this.apiService.estimate$(this.tx.txid).pipe( tap((response) => { if (response.status === 204) { 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 - this.minExtraCost = Math.max(this.estimate.cost, this.estimate.txSummary.effectiveFee / 2); - this.minExtraCost = Math.round(this.minExtraCost); + this.minExtraCost = Math.round(Math.max(this.estimate.cost, this.estimate.txSummary.effectiveFee / 2)); + + 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.maxBidAllowed = this.minExtraCost * MAX_BID_RATIO; @@ -121,10 +138,10 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges /** * User changed his bid */ - setUserBid(multiplier: number, index: number) { + setUserBid({ fee, index }: { fee: number, index: number}) { if (this.estimate) { 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; } } @@ -156,7 +173,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges this.accelerationSubscription.unsubscribe(); } this.accelerationSubscription = this.apiService.accelerate$( - this.txid, + this.tx.txid, this.userBid ).subscribe({ next: () => { diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index ce5d703fb..006870864 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -84,7 +84,7 @@

Accelerate

- +
diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index d77eea2cf..f7c253a96 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -94,6 +94,7 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati import { TestnetAlertComponent } from './components/testnet-alert/testnet-alert.component'; import { GlobalFooterComponent } from './components/global-footer/global-footer.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 { MempoolBlockOverviewComponent } from '../components/mempool-block-overview/mempool-block-overview.component'; @@ -192,6 +193,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir TestnetAlertComponent, GlobalFooterComponent, AcceleratePreviewComponent, + AccelerateFeeGraphComponent, CalculatorComponent, BitcoinsatoshisPipe, MempoolBlockOverviewComponent, @@ -315,6 +317,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir PreviewTitleComponent, GlobalFooterComponent, AcceleratePreviewComponent, + AccelerateFeeGraphComponent, MempoolErrorComponent, MempoolBlockOverviewComponent, From cb363aca23215780d6a6b5cc4a5e2aa2fe75b94f Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 30 Aug 2023 16:49:54 +0900 Subject: [PATCH 2/5] Simplify acceleration quote --- .../accelerate-fee-graph.component.html | 6 +- .../accelerate-fee-graph.component.scss | 34 +- .../accelerate-fee-graph.component.ts | 6 +- .../accelerate-preview.component.html | 290 +++++++++--------- .../accelerate-preview.component.scss | 58 +++- .../accelerate-preview.component.ts | 11 +- frontend/src/app/shared/common.utils.ts | 8 + 7 files changed, 253 insertions(+), 160 deletions(-) diff --git a/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.html b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.html index 727f833bc..fe0718ecc 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.html +++ b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.html @@ -11,11 +11,11 @@

+
{{ bar.class === 'tx' ? '' : '+' }} {{ bar.fee | number }} sat +
+
-
- -
diff --git a/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.scss b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.scss index 84d530e34..6137b53ee 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.scss +++ b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.scss @@ -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; - } } \ No newline at end of file diff --git a/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.ts b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.ts index 98b47bea0..4d746a0d9 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.ts +++ b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.ts @@ -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; diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html index ad96eb671..8fc77dae4 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html @@ -12,14 +12,6 @@
-
-
-
- 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. -
-
-
-
-
+
Your transaction
+
+ + Plus {{ estimate.txSummary.ancestorCount - 1 }} unconfirmed ancestor{{ estimate.txSummary.ancestorCount > 2 ? 's' : ''}}. + - - - + - + + + - - - - - - - - - - - - - - - - - - - - +
- Next block market price +
+ Virtual size - {{ estimate.targetFeeRate | number : '1.0-0' }} sat/vB +
+ Size in vbytes of this transaction and its unconfirmed ancestors
- Currently estimated fee to get into next block + + In-band fees - - {{ estimate.nextBlockFee| number }} sats - - + + {{ estimate.txSummary.effectiveFee | number : '1.0-0' }} sats
- Fees paid in-band - - ~{{ (estimate.txSummary.effectiveFee / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} sat/vB -
- What you already paid when you made the transaction - - - {{ estimate.txSummary.effectiveFee | number }} sats - - -
- Extra fee required - - {{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }} sats - -
- Difference between the next block fee and your tx fee +
+ Fees already paid by this transaction and its unconfirmed ancestors
- -
How much more are you willing to pay at most to get into the next block?
+
+
How much more are you willing to pay?
- 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.
+ If the estimated next block rate rises beyond this limit, we will automatically cancel your acceleration request.
-
+
@@ -117,108 +82,159 @@
-
Acceleration summary
+
Acceleration summary
+
+
+ Estimated cost +
+
+ Maximum cost +
+
+ + + + + + + + + + + + + - - - - - - - + + + + + + + + - + + + - - - - + - + - - + - + - + - - - + + + - - - - + + + + + + - - - - - - - + + + + + + + + + + - - + - + - - - - + + + + + + +
+ Next block market rate + + {{ estimate.targetFeeRate | number : '1.0-0' }} + sat/vB
+ Estimated extra fee required + + {{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }} + + sats + +
- Your maximum tx fees - - ~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} sat/vB -
- The maximum extra transaction fee you're willing to pay - - - {{ userBid | number }} sats + +
+ Your maximum + + ~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} + sat/vB
+ The maximum extra transaction fee you could pay + + + {{ userBid | number }} + + + sats - -
- Mempool Accelerator™ fee - - +{{ estimate.mempoolBaseFee + estimate.vsizeFee | number }} sats - + + Mempool Accelerator™ fees
+
mempool.space fee - - {{ estimate.mempoolBaseFee | number }} sats - - + + +{{ estimate.mempoolBaseFee | number }} + + sats +
+
Transaction vsize fee - - {{ estimate.vsizeFee | number }} sats - - + + +{{ estimate.vsizeFee | number }} + + sats +
- Estimated acceleration cost - - - {{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }} sats + +
+ Estimated acceleration cost + + + {{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }} + + + sats - -
- Cost if your tx is accelerated using {{ estimate.targetFeeRate | number : '1.0-0' }} sat/vB -
+ If your tx is accelerated to {{ estimate.targetFeeRate | number : '1.0-0' }} sat/vB +
- Maximum acceleration cost - - {{ maxCost | number }} sats - - - -
- Cost if your tx is accelerated using ~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} sat/vB -
+ Maximum acceleration cost + + + {{ maxCost | number }} + + + sats + + + +
+ If your tx is accelerated to ~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} sat/vB +
+
Available balance - {{ estimate.userBalance | number }} sats + + {{ estimate.userBalance | number }} + + sats diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.scss b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.scss index f6e68a77a..433c05520 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.scss +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.scss @@ -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; + } } } diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts index 27074c724..be47b25fe 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts @@ -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, diff --git a/frontend/src/app/shared/common.utils.ts b/frontend/src/app/shared/common.utils.ts index 7d206f4b5..af72c0e72 100644 --- a/frontend/src/app/shared/common.utils.ts +++ b/frontend/src/app/shared/common.utils.ts @@ -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]); } \ No newline at end of file From ed12e305178a4bc0158a340bcef56484ea22196d Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 30 Aug 2023 17:16:39 +0900 Subject: [PATCH 3/5] Hide fee diagram on mobile --- .../accelerate-preview.component.html | 16 +++++++++------- .../accelerate-preview.component.ts | 8 +++++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html index 8fc77dae4..7755d1b80 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html @@ -13,13 +13,15 @@
- + + +
diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts index be47b25fe..abdcb7467 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts @@ -1,4 +1,4 @@ -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 { Subscription, catchError, of, tap } from 'rxjs'; import { StorageService } from '../../services/storage.service'; @@ -57,6 +57,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges userBid = 0; selectFeeRateIndex = 1; showTable: 'estimated' | 'maximum' = 'maximum'; + isMobile: boolean = window.innerWidth <= 767.98; maxRateOptions: RateOption[] = []; @@ -197,4 +198,9 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges const auth = this.storageService.getAuth(); return auth !== null; } + + @HostListener('window:resize', ['$event']) + onResize(): void { + this.isMobile = window.innerWidth <= 767.98; + } } \ No newline at end of file From d6044331e1ae84de5fced4a163d93dbcec686ed4 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Thu, 31 Aug 2023 00:15:09 +0900 Subject: [PATCH 4/5] Hide balance unless insufficient for max cost --- .../accelerate-preview.component.html | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html index 7755d1b80..9bb66eda1 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html @@ -228,20 +228,22 @@ -
- Available balance - - {{ estimate.userBalance | number }} - - sats - - - -
+ Available balance + + {{ estimate.userBalance | number }} + + sats + + + +
From d1ef1afc9c085d97bb11b3ea72d716ae7e035d88 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Thu, 31 Aug 2023 00:32:54 +0900 Subject: [PATCH 5/5] Fix default min/max/default userbid --- .../accelerate-preview/accelerate-preview.component.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts index abdcb7467..1c356a80b 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts @@ -28,10 +28,9 @@ export interface RateOption { index: number; } -export const DEFAULT_BID_RATIO = 5; -export const MIN_BID_RATIO = 2; -export const MAX_BID_RATIO = 20; - +export const MIN_BID_RATIO = 1; +export const DEFAULT_BID_RATIO = 2; +export const MAX_BID_RATIO = 4; @Component({ selector: 'app-accelerate-preview', @@ -115,8 +114,8 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges }); this.minBidAllowed = this.minExtraCost * MIN_BID_RATIO; - this.maxBidAllowed = this.minExtraCost * MAX_BID_RATIO; this.defaultBid = this.minExtraCost * DEFAULT_BID_RATIO; + this.maxBidAllowed = this.minExtraCost * MAX_BID_RATIO; this.userBid = this.defaultBid; if (this.userBid < this.minBidAllowed) {