From 1be10b9dfd8a78109cf625810d72daf730609e78 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 29 Oct 2025 08:51:36 +0000 Subject: [PATCH 1/9] Use precise fee recommendations in websocket --- backend/src/api/websocket-handler.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 57fd46730..532d6d4a1 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -102,7 +102,7 @@ class WebsocketHandler { 'backendInfo': backendInfo.getBackendInfo(), 'loadingIndicators': loadingIndicators.getLoadingIndicators(), 'da': da?.previousTime ? da : undefined, - 'fees': feeApi.getRecommendedFee(), + 'fees': feeApi.getPreciseRecommendedFee(), }); } @@ -639,7 +639,7 @@ class WebsocketHandler { } memPool.removeFromSpendMap(deletedTransactions); memPool.addToSpendMap(newTransactions); - const recommendedFees = feeApi.getRecommendedFee(); + const recommendedFees = feeApi.getPreciseRecommendedFee(); const latestTransactions = memPool.getLatestTransactions(); @@ -1137,7 +1137,7 @@ class WebsocketHandler { const mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas(); const da = difficultyAdjustment.getDifficultyAdjustment(); - const fees = feeApi.getRecommendedFee(); + const fees = feeApi.getPreciseRecommendedFee(); const mempoolInfo = memPool.getMempoolInfo(); // pre-compute address transactions From e4e8e49ed8d6b97108abaddf0368b7561de50fad Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 29 Oct 2025 11:13:53 +0000 Subject: [PATCH 2/9] Support fractional fees in frontend fee box --- frontend/src/app/components/clock/clock.component.html | 2 +- .../app/components/fees-box/fees-box.component.html | 8 ++++---- frontend/src/app/services/state.service.ts | 2 ++ frontend/src/app/services/websocket.service.ts | 10 +++++++++- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/components/clock/clock.component.html b/frontend/src/app/components/clock/clock.component.html index 376d72bfc..3d23f68dc 100644 --- a/frontend/src/app/components/clock/clock.component.html +++ b/frontend/src/app/components/clock/clock.component.html @@ -46,7 +46,7 @@

High Priority

- +

diff --git a/frontend/src/app/components/fees-box/fees-box.component.html b/frontend/src/app/components/fees-box/fees-box.component.html index 580307df5..1ad4a2c23 100644 --- a/frontend/src/app/components/fees-box/fees-box.component.html +++ b/frontend/src/app/components/fees-box/fees-box.component.html @@ -13,23 +13,23 @@
-
+
-
+
-
+
-
+
diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index f4bed10af..95eb68567 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -87,6 +87,7 @@ export interface Env { SERVICES_API?: string; customize?: Customization; PROD_DOMAINS: string[]; + MIN_RECOMMENDED_FEE: number; } const defaultEnv: Env = { @@ -129,6 +130,7 @@ const defaultEnv: Env = { 'STRATUM_ENABLED': false, 'SERVICES_API': 'https://mempool.space/api/v1/services', 'PROD_DOMAINS': [], + 'MIN_RECOMMENDED_FEE': 1, }; @Injectable({ diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index b82b32dd5..4eb7c3ed4 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -46,6 +46,8 @@ export class WebsocketService { private subscription: Subscription; private network = ''; + private minRecommendedFee = this.stateService.env.MIN_RECOMMENDED_FEE; + constructor( private stateService: StateService, private apiService: ApiService, @@ -428,7 +430,13 @@ export class WebsocketService { } if (response.fees) { - this.stateService.recommendedFees$.next(response.fees); + this.stateService.recommendedFees$.next({ + fastestFee: Math.max(response.fees.fastestFee, this.minRecommendedFee), + halfHourFee: Math.max(response.fees.halfHourFee, this.minRecommendedFee), + hourFee: Math.max(response.fees.hourFee, this.minRecommendedFee), + minimumFee: Math.max(response.fees.minimumFee, this.minRecommendedFee), + economyFee: Math.max(response.fees.economyFee, this.minRecommendedFee), + }); } if (response.backendInfo) { From 116b08fd6754ebfef9ba47095e88a819dcb3cfa4 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 31 Oct 2025 08:29:26 +0000 Subject: [PATCH 3/9] adjust fee box decimal places --- .../app/components/fees-box/fees-box.component.html | 8 ++++---- .../components/fee-rate/fee-rate.component.html | 4 ++-- .../shared/components/fee-rate/fee-rate.component.ts | 1 + .../shared/pipes/fee-rounding/fee-rounding.pipe.ts | 12 ++++++------ 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/components/fees-box/fees-box.component.html b/frontend/src/app/components/fees-box/fees-box.component.html index 1ad4a2c23..1fec3921e 100644 --- a/frontend/src/app/components/fees-box/fees-box.component.html +++ b/frontend/src/app/components/fees-box/fees-box.component.html @@ -13,23 +13,23 @@
-
+
-
+
-
+
-
+
diff --git a/frontend/src/app/shared/components/fee-rate/fee-rate.component.html b/frontend/src/app/shared/components/fee-rate/fee-rate.component.html index f9e86cc2b..0f4491dd1 100644 --- a/frontend/src/app/shared/components/fee-rate/fee-rate.component.html +++ b/frontend/src/app/shared/components/fee-rate/fee-rate.component.html @@ -1,7 +1,7 @@ - {{ fee / (weight / 4) | feeRounding:rounding }} sat/vB - {{ fee / weight | feeRounding:rounding }} sat/WU + {{ fee / (weight / 4) | feeRounding:rounding:dp }} sat/vB + {{ fee / weight | feeRounding:rounding:dp }} sat/WU - sat/vB diff --git a/frontend/src/app/shared/components/fee-rate/fee-rate.component.ts b/frontend/src/app/shared/components/fee-rate/fee-rate.component.ts index 857b653c3..083065f1e 100644 --- a/frontend/src/app/shared/components/fee-rate/fee-rate.component.ts +++ b/frontend/src/app/shared/components/fee-rate/fee-rate.component.ts @@ -12,6 +12,7 @@ export class FeeRateComponent implements OnInit { @Input() fee: number | undefined; @Input() weight: number = 4; @Input() rounding: string = null; + @Input() dp: number = null; @Input() showUnit: boolean = true; @Input() unitClass: string = 'symbol'; @Input() unitStyle: any; diff --git a/frontend/src/app/shared/pipes/fee-rounding/fee-rounding.pipe.ts b/frontend/src/app/shared/pipes/fee-rounding/fee-rounding.pipe.ts index e2a6e9968..1e7a51e9b 100644 --- a/frontend/src/app/shared/pipes/fee-rounding/fee-rounding.pipe.ts +++ b/frontend/src/app/shared/pipes/fee-rounding/fee-rounding.pipe.ts @@ -10,16 +10,16 @@ export class FeeRoundingPipe implements PipeTransform { @Inject(LOCALE_ID) private locale: string, ) {} - transform(fee: number, rounding = null): string { + transform(fee: number, rounding = null, dp = 3): string { if (rounding) { return formatNumber(fee, this.locale, rounding); } - if (fee >= 100) { - return formatNumber(fee, this.locale, '1.0-0') - } else if (fee < 10) { - return formatNumber(fee, this.locale, '1.2-2') + if (fee >= Math.pow(10, (dp || 3) - 1)) { + return formatNumber(fee, this.locale, '1.0-0'); + } else if (fee < Math.pow(10, (dp || 3) - 2)) { + return formatNumber(fee, this.locale, '1.2-2'); } - return formatNumber(fee, this.locale, '1.1-1') + return formatNumber(fee, this.locale, '1.1-1'); } } From c8ac540925305099ac809287c30df55f42af6011 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 2 Dec 2025 07:24:41 +0000 Subject: [PATCH 4/9] softer fee decimals --- .../components/fees-box/fees-box.component.html | 9 +++++---- .../components/fee-rate/fee-rate.component.html | 9 +++++++-- .../components/fee-rate/fee-rate.component.scss | 4 ++++ .../components/fee-rate/fee-rate.component.ts | 15 +++++++++++++++ frontend/src/app/shared/shared.module.ts | 1 + 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/components/fees-box/fees-box.component.html b/frontend/src/app/components/fees-box/fees-box.component.html index 1fec3921e..625764d5f 100644 --- a/frontend/src/app/components/fees-box/fees-box.component.html +++ b/frontend/src/app/components/fees-box/fees-box.component.html @@ -10,26 +10,27 @@ High Priority
+
-
+
-
+
-
+
-
+
diff --git a/frontend/src/app/shared/components/fee-rate/fee-rate.component.html b/frontend/src/app/shared/components/fee-rate/fee-rate.component.html index 0f4491dd1..12020c64d 100644 --- a/frontend/src/app/shared/components/fee-rate/fee-rate.component.html +++ b/frontend/src/app/shared/components/fee-rate/fee-rate.component.html @@ -1,7 +1,12 @@ - {{ fee / (weight / 4) | feeRounding:rounding:dp }} sat/vB - {{ fee / weight | feeRounding:rounding:dp }} sat/WU + @if (softDecimals) { + {{ getIntegerPart(fee / (weight / 4)) }}{{ getDecimalPart(fee / (weight / 4)) }} sat/vB + {{ getIntegerPart(fee / weight) }}{{ getDecimalPart(fee / weight) }} sat/WU + } @else { + {{ fee / (weight / 4) | feeRounding:rounding:dp }} sat/vB + {{ fee / weight | feeRounding:rounding:dp }} sat/WU + } - sat/vB diff --git a/frontend/src/app/shared/components/fee-rate/fee-rate.component.scss b/frontend/src/app/shared/components/fee-rate/fee-rate.component.scss index e69de29bb..4487e5157 100644 --- a/frontend/src/app/shared/components/fee-rate/fee-rate.component.scss +++ b/frontend/src/app/shared/components/fee-rate/fee-rate.component.scss @@ -0,0 +1,4 @@ +.soft-decimals { + font-size: 0.8em; + vertical-align: text-bottom; +} diff --git a/frontend/src/app/shared/components/fee-rate/fee-rate.component.ts b/frontend/src/app/shared/components/fee-rate/fee-rate.component.ts index 083065f1e..c71f5f5f9 100644 --- a/frontend/src/app/shared/components/fee-rate/fee-rate.component.ts +++ b/frontend/src/app/shared/components/fee-rate/fee-rate.component.ts @@ -1,6 +1,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { StateService } from '@app/services/state.service'; +import { FeeRoundingPipe } from '@app/shared/pipes/fee-rounding/fee-rounding.pipe'; @Component({ selector: 'app-fee-rate', @@ -13,6 +14,7 @@ export class FeeRateComponent implements OnInit { @Input() weight: number = 4; @Input() rounding: string = null; @Input() dp: number = null; + @Input() softDecimals: boolean = false; @Input() showUnit: boolean = true; @Input() unitClass: string = 'symbol'; @Input() unitStyle: any; @@ -21,9 +23,22 @@ export class FeeRateComponent implements OnInit { constructor( private stateService: StateService, + private feeRoundingPipe: FeeRoundingPipe, ) { } ngOnInit() { this.rateUnits$ = this.stateService.rateUnits$; } + + getIntegerPart(rate: number): string { + const formatted = this.feeRoundingPipe.transform(rate, this.rounding, this.dp); + const decimalIndex = formatted.indexOf('.'); + return decimalIndex === -1 ? formatted : formatted.substring(0, decimalIndex); + } + + getDecimalPart(rate: number): string { + const formatted = this.feeRoundingPipe.transform(rate, this.rounding, this.dp); + const decimalIndex = formatted.indexOf('.'); + return decimalIndex === -1 ? ' ' : formatted.substring(decimalIndex) + ' '; + } } diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 6ac11ca90..146c4c9b6 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -279,6 +279,7 @@ import { GithubLogin } from '@components/github-login.component/github-login.com ShortenStringPipe, CapAddressPipe, AmountShortenerPipe, + FeeRoundingPipe, ], exports: [ MenuComponent, From 800824ae079493cb4db1db5d67a9cac811eff115 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 2 Dec 2025 08:51:08 +0000 Subject: [PATCH 5/9] remove precise fee min query param --- backend/src/api/bitcoin/bitcoin.routes.ts | 12 +----------- backend/src/api/fee-api.ts | 8 ++++---- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index 69d42f570..e6075ffa2 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -129,17 +129,7 @@ class BitcoinRoutes { res.send('Service Unavailable'); return; } - let minFee = 0; - if (req.query.min) { - try { - minFee = parseFloat(req.query.min as string); - } catch (e) { - res.statusCode = 400; - res.send('Invalid minimum fee'); - return; - } - } - const result = feeApi.getPreciseRecommendedFee(minFee); + const result = feeApi.getPreciseRecommendedFee(); res.json(result); } diff --git a/backend/src/api/fee-api.ts b/backend/src/api/fee-api.ts index 2edb8c0e4..7f6608fbb 100644 --- a/backend/src/api/fee-api.ts +++ b/backend/src/api/fee-api.ts @@ -26,17 +26,17 @@ class FeeApi { return this.calculateRecommendedFee(pBlocks, mPool); } - public getPreciseRecommendedFee(minimum: number = 0): RecommendedFees { + public getPreciseRecommendedFee(): RecommendedFees { const pBlocks = projectedBlocks.getMempoolBlocks(); const mPool = mempool.getMempoolInfo(); // minimum non-zero minrelaytxfee / incrementalrelayfee is 1 sat/kvB = 0.001 sat/vB - return this.calculateRecommendedFee(pBlocks, mPool, minimum, 0.001); + return this.calculateRecommendedFee(pBlocks, mPool, 0.001); } - public calculateRecommendedFee(pBlocks: MempoolBlock[], mPool: IBitcoinApi.MempoolInfo, minimumRecommendation: number = 0, minIncrement: number = this.minimumIncrement): RecommendedFees { + public calculateRecommendedFee(pBlocks: MempoolBlock[], mPool: IBitcoinApi.MempoolInfo, minIncrement: number = this.minimumIncrement): RecommendedFees { const purgeRate = this.roundUpToNearest(mPool.mempoolminfee * 100000, minIncrement); - const minimumFee = Math.max(purgeRate, minimumRecommendation, minIncrement); + const minimumFee = Math.max(purgeRate, minIncrement); if (!pBlocks.length) { return { From b22891c467e2fe39c6bd9c696dcf1703ca60200c Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 2 Dec 2025 09:03:06 +0000 Subject: [PATCH 6/9] remove MIN_RECOMMENDED_FEE config option --- frontend/src/app/services/state.service.ts | 2 -- frontend/src/app/services/websocket.service.ts | 8 +------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 95eb68567..f4bed10af 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -87,7 +87,6 @@ export interface Env { SERVICES_API?: string; customize?: Customization; PROD_DOMAINS: string[]; - MIN_RECOMMENDED_FEE: number; } const defaultEnv: Env = { @@ -130,7 +129,6 @@ const defaultEnv: Env = { 'STRATUM_ENABLED': false, 'SERVICES_API': 'https://mempool.space/api/v1/services', 'PROD_DOMAINS': [], - 'MIN_RECOMMENDED_FEE': 1, }; @Injectable({ diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index 4eb7c3ed4..8979ce4cf 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -46,8 +46,6 @@ export class WebsocketService { private subscription: Subscription; private network = ''; - private minRecommendedFee = this.stateService.env.MIN_RECOMMENDED_FEE; - constructor( private stateService: StateService, private apiService: ApiService, @@ -431,11 +429,7 @@ export class WebsocketService { if (response.fees) { this.stateService.recommendedFees$.next({ - fastestFee: Math.max(response.fees.fastestFee, this.minRecommendedFee), - halfHourFee: Math.max(response.fees.halfHourFee, this.minRecommendedFee), - hourFee: Math.max(response.fees.hourFee, this.minRecommendedFee), - minimumFee: Math.max(response.fees.minimumFee, this.minRecommendedFee), - economyFee: Math.max(response.fees.economyFee, this.minRecommendedFee), + ...response.fees, }); } From 4c88b4f2bba611eb3b2cd93047d61d61f3c300d0 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 2 Dec 2025 09:03:20 +0000 Subject: [PATCH 7/9] tweak precise fee algorithm for higher priority fees --- backend/src/api/fee-api.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/api/fee-api.ts b/backend/src/api/fee-api.ts index 7f6608fbb..0914a1098 100644 --- a/backend/src/api/fee-api.ts +++ b/backend/src/api/fee-api.ts @@ -18,6 +18,9 @@ class FeeApi { constructor() { } minimumIncrement = isLiquid ? 0.1 : 1; + minFastestFee = isLiquid ? 0.1 : 1; + minHalfHourFee = isLiquid ? 0.1 : 0.5; + priorityFactor = isLiquid ? 0 : 0.5; public getRecommendedFee(): RecommendedFees { const pBlocks = projectedBlocks.getMempoolBlocks(); @@ -67,8 +70,8 @@ class FeeApi { hourFee = Math.max(hourFee, economyFee); return { - 'fastestFee': this.roundToNearest(fastestFee, minIncrement), - 'halfHourFee': this.roundToNearest(halfHourFee, minIncrement), + 'fastestFee': Math.max(this.roundToNearest(fastestFee + this.priorityFactor, minIncrement), this.minFastestFee), + 'halfHourFee': Math.max(this.roundToNearest(halfHourFee + (this.priorityFactor / 2), minIncrement), this.minHalfHourFee), 'hourFee': this.roundToNearest(hourFee, minIncrement), 'economyFee': this.roundToNearest(economyFee, minIncrement), 'minimumFee': this.roundToNearest(minimumFee, minIncrement), From e36fb3d4a186b7a9080add0553bba652873c70f4 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 2 Dec 2025 09:25:01 +0000 Subject: [PATCH 8/9] remove soft decimals --- .../src/app/components/fees-box/fees-box.component.html | 8 ++++---- .../shared/components/fee-rate/fee-rate.component.html | 9 ++------- .../shared/components/fee-rate/fee-rate.component.scss | 4 ---- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/frontend/src/app/components/fees-box/fees-box.component.html b/frontend/src/app/components/fees-box/fees-box.component.html index 625764d5f..55e64d4c1 100644 --- a/frontend/src/app/components/fees-box/fees-box.component.html +++ b/frontend/src/app/components/fees-box/fees-box.component.html @@ -14,23 +14,23 @@
-
+
-
+
-
+
-
+
diff --git a/frontend/src/app/shared/components/fee-rate/fee-rate.component.html b/frontend/src/app/shared/components/fee-rate/fee-rate.component.html index 12020c64d..0f4491dd1 100644 --- a/frontend/src/app/shared/components/fee-rate/fee-rate.component.html +++ b/frontend/src/app/shared/components/fee-rate/fee-rate.component.html @@ -1,12 +1,7 @@ - @if (softDecimals) { - {{ getIntegerPart(fee / (weight / 4)) }}{{ getDecimalPart(fee / (weight / 4)) }} sat/vB - {{ getIntegerPart(fee / weight) }}{{ getDecimalPart(fee / weight) }} sat/WU - } @else { - {{ fee / (weight / 4) | feeRounding:rounding:dp }} sat/vB - {{ fee / weight | feeRounding:rounding:dp }} sat/WU - } + {{ fee / (weight / 4) | feeRounding:rounding:dp }} sat/vB + {{ fee / weight | feeRounding:rounding:dp }} sat/WU - sat/vB diff --git a/frontend/src/app/shared/components/fee-rate/fee-rate.component.scss b/frontend/src/app/shared/components/fee-rate/fee-rate.component.scss index 4487e5157..e69de29bb 100644 --- a/frontend/src/app/shared/components/fee-rate/fee-rate.component.scss +++ b/frontend/src/app/shared/components/fee-rate/fee-rate.component.scss @@ -1,4 +0,0 @@ -.soft-decimals { - font-size: 0.8em; - vertical-align: text-bottom; -} From 7b6bf4f9613603b1f9849ab413a6c400fe723221 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 2 Dec 2025 09:39:28 +0000 Subject: [PATCH 9/9] only 1 d.p --- .../src/app/components/fees-box/fees-box.component.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/components/fees-box/fees-box.component.html b/frontend/src/app/components/fees-box/fees-box.component.html index 55e64d4c1..a3b2a5286 100644 --- a/frontend/src/app/components/fees-box/fees-box.component.html +++ b/frontend/src/app/components/fees-box/fees-box.component.html @@ -14,23 +14,23 @@
-
+
-
+
-
+
-
+