+
+ {connectivity.isOffline ? '!' : '~'}
-
-
{getConnectionMessage()}
+
+
+
{message}
{showDetails && (
-
- {connectivity.connectionType && (
- Connection: {connectivity.connectionType}
- )}
- {connectivity.effectiveType && (
- Speed: {connectivity.effectiveType}
- )}
- {connectivity.saveData && (
- Data saver: ON
- )}
-
+
+ {connectivity.connectionType ? `Connection: ${connectivity.connectionType}. ` : ''}
+ {connectivity.effectiveType ? `Speed: ${connectivity.effectiveType}. ` : ''}
+ {connectivity.saveData ? 'Data saver is on.' : ''}
+
)}
-
+
{connectivity.isOffline && (
)}
From 8776d0bf094374933397dd5977a3e26dbc965ccf Mon Sep 17 00:00:00 2001
From: Asher <141028690+No-bodyq@users.noreply.github.com>
Date: Fri, 29 May 2026 09:48:10 +0100
Subject: [PATCH 2/3] Add missing test runner dependency
---
package-lock.json | 187 +++++++++++++++++++++++++++++++++++++++-------
package.json | 1 +
2 files changed, 163 insertions(+), 25 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index da9824ed..e3f05033 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -37,6 +37,7 @@
"eslint-config-next": "15.2.0",
"sharp": "^0.34.5",
"tailwindcss": "^4",
+ "ts-node": "^10.9.2",
"typescript": "^5"
}
},
@@ -53,6 +54,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@discoveryjs/json-ext": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
@@ -120,7 +134,6 @@
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
"integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==",
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -354,7 +367,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -377,7 +389,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -400,7 +411,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -417,7 +427,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -434,7 +443,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -451,7 +459,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -468,7 +475,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -485,7 +491,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -502,7 +507,6 @@
"cpu": [
"s390x"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -519,7 +523,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -536,7 +539,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -553,7 +555,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -570,7 +571,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -593,7 +593,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -616,7 +615,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -639,7 +637,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -662,7 +659,6 @@
"cpu": [
"s390x"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -685,7 +681,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -708,7 +703,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -731,7 +725,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -754,7 +747,6 @@
"cpu": [
"wasm32"
],
- "dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
@@ -774,7 +766,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -794,7 +785,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -814,7 +804,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -827,6 +816,34 @@
"url": "https://opencollective.com/libvips"
}
},
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
"node_modules/@next/bundle-analyzer": {
"version": "15.5.14",
"resolved": "https://registry.npmjs.org/@next/bundle-analyzer/-/bundle-analyzer-15.5.14.tgz",
@@ -1413,6 +1430,34 @@
"tailwindcss": "4.0.9"
}
},
+ "node_modules/@tsconfig/node10": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
+ "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node12": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node14": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node16": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/d3-array": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
@@ -1863,6 +1908,13 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -2396,6 +2448,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2714,6 +2773,16 @@
"node": ">=0.10"
}
},
+ "node_modules/diff": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
+ "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
"node_modules/dijkstrajs": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
@@ -4845,6 +4914,13 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -6404,6 +6480,50 @@
"typescript": ">=4.8.4"
}
},
+ "node_modules/ts-node": {
+ "version": "10.9.2",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cspotcode/source-map-support": "^0.8.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.1",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-cwd": "dist/bin-cwd.js",
+ "ts-node-esm": "dist/bin-esm.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "peerDependencies": {
+ "@swc/core": ">=1.2.50",
+ "@swc/wasm": ">=1.2.50",
+ "@types/node": "*",
+ "typescript": ">=2.7"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/wasm": {
+ "optional": true
+ }
+ }
+ },
"node_modules/tsconfig-paths": {
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
@@ -6583,6 +6703,13 @@
"integrity": "sha512-OZeJfZP+R0z9D6TmBgLq2LHzSSptGMGDGigGiEe0pr8UBe/7fdflgHlHBNDASTXB5jnFuxHpNaJywSg8YFeGng==",
"license": "MIT"
},
+ "node_modules/v8-compile-cache-lib": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/victory-vendor": {
"version": "37.3.6",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
@@ -6881,6 +7008,16 @@
"node": ">=8"
}
},
+ "node_modules/yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/package.json b/package.json
index c14077c7..b2ae41da 100644
--- a/package.json
+++ b/package.json
@@ -47,6 +47,7 @@
"eslint-config-next": "15.2.0",
"sharp": "^0.34.5",
"tailwindcss": "^4",
+ "ts-node": "^10.9.2",
"typescript": "^5"
}
}
From f965207616cba7bc001d3837c3a1959ef444796f Mon Sep 17 00:00:00 2001
From: Asher <141028690+No-bodyq@users.noreply.github.com>
Date: Fri, 29 May 2026 10:03:42 +0100
Subject: [PATCH 3/3] Fix Wata board test startup
---
wata-board-frontend/postcss.config.mjs | 3 +
.../src/services/feeEstimation.ts | 122 +++++++++---------
2 files changed, 67 insertions(+), 58 deletions(-)
create mode 100644 wata-board-frontend/postcss.config.mjs
diff --git a/wata-board-frontend/postcss.config.mjs b/wata-board-frontend/postcss.config.mjs
new file mode 100644
index 00000000..3e0d24c4
--- /dev/null
+++ b/wata-board-frontend/postcss.config.mjs
@@ -0,0 +1,3 @@
+export default {
+ plugins: {},
+};
diff --git a/wata-board-frontend/src/services/feeEstimation.ts b/wata-board-frontend/src/services/feeEstimation.ts
index 68e00515..8a8a9017 100644
--- a/wata-board-frontend/src/services/feeEstimation.ts
+++ b/wata-board-frontend/src/services/feeEstimation.ts
@@ -1,29 +1,32 @@
/**
* Fee Estimation Service for Stellar Transactions
- * Queries Horizon fee_stats endpoint for accurate, real-time fee data.
+ * Queries Horizon fee_stats for current fee tiers and payment estimates.
*/
-import { Horizon, BASE_FEE } from '@stellar/stellar-sdk';
-import { getCurrentNetworkConfig } from '../utils/network-config';
-import { Horizon, Networks, TransactionBuilder, Operation, Asset, BASE_FEE } from '@stellar/stellar-sdk';
-import { requestAccess } from '../utils/wallet-bridge';
+import { BASE_FEE, Horizon } from '@stellar/stellar-sdk';
import { getCurrentNetworkConfig, NETWORK_CHANGE_EVENT } from '../utils/network-config';
const STROOPS_PER_XLM = 10_000_000;
-const CACHE_TTL_MS = 10_000; // 10 second TTL
-const SURGE_MULTIPLIER = 1.5; // Applied when network is congested
+const CACHE_TTL_MS = 10_000;
+const SURGE_MULTIPLIER = 1.5;
export interface FeeTiers {
- min: number; // Minimum fee in XLM
- recommended: number; // Recommended fee in XLM (p50)
- max: number; // High-priority fee in XLM (p90)
+ min: number;
+ recommended: number;
+ max: number;
}
export interface FeeEstimate {
tiers: FeeTiers;
- totalFee: number; // recommended total fee in XLM for given op count
+ totalFee: number;
operationCount: number;
- isSurge: boolean; // true when surge pricing is active
+ isSurge: boolean;
+ estimatedTimeSeconds: number;
+}
+
+export interface FeeRecommendation {
+ label: 'low' | 'recommended' | 'priority';
+ fee: number;
estimatedTimeSeconds: number;
}
@@ -33,42 +36,31 @@ interface FeeCache {
timestamp: number;
}
-// Utility conversions
export const stroopsToXLM = (stroops: number): number => stroops / STROOPS_PER_XLM;
export const xlmToStroops = (xlm: number): number => Math.floor(xlm * STROOPS_PER_XLM);
export class FeeEstimationService {
private server: Horizon.Server;
private cache: FeeCache | null = null;
-
- constructor() {
- const config = getCurrentNetworkConfig();
- const horizonUrl = config.rpcUrl.replace('soroban', 'horizon');
- private networkConfig: any;
private networkChangeHandler: (() => void) | null = null;
constructor() {
- this.networkConfig = getCurrentNetworkConfig();
- this.updateServer();
-
- // Listen for network changes
+ this.server = this.createServer();
+
if (typeof window !== 'undefined') {
this.networkChangeHandler = () => {
- this.networkConfig = getCurrentNetworkConfig();
- this.updateServer();
+ this.server = this.createServer();
+ this.clearCache();
};
- window.addEventListener(NETWORK_CHANGE_EVENT, this.networkChangeHandler as any);
+ window.addEventListener(NETWORK_CHANGE_EVENT, this.networkChangeHandler);
}
}
- private updateServer(): void {
- const horizonUrl = this.networkConfig.rpcUrl.replace('soroban', 'horizon');
- this.server = new Horizon.Server(horizonUrl);
+ private createServer(): Horizon.Server {
+ const config = getCurrentNetworkConfig();
+ return new Horizon.Server(config.rpcUrl.replace('soroban', 'horizon'));
}
- /**
- * Fetch fee tiers from Horizon fee_stats, with TTL caching.
- */
async getFeeTiers(): Promise<{ tiers: FeeTiers; isSurge: boolean }> {
if (this.cache && Date.now() - this.cache.timestamp < CACHE_TTL_MS) {
return { tiers: this.cache.tiers, isSurge: this.cache.isSurge };
@@ -76,26 +68,23 @@ export class FeeEstimationService {
try {
const feeStats = await this.server.feeStats();
-
- const p10 = parseInt(feeStats.fee_charged.p10);
- const p50 = parseInt(feeStats.fee_charged.p50);
- const p90 = parseInt(feeStats.fee_charged.p90);
- const ledgerCapacityUsage = parseFloat(feeStats.ledger_capacity_usage);
-
+ const p10 = Number.parseInt(feeStats.fee_charged.p10, 10);
+ const p50 = Number.parseInt(feeStats.fee_charged.p50, 10);
+ const p90 = Number.parseInt(feeStats.fee_charged.p90, 10);
+ const ledgerCapacityUsage = Number.parseFloat(feeStats.ledger_capacity_usage);
const isSurge = ledgerCapacityUsage > 0.8;
- const surgeMultiplier = isSurge ? SURGE_MULTIPLIER : 1;
+ const multiplier = isSurge ? SURGE_MULTIPLIER : 1;
const tiers: FeeTiers = {
- min: stroopsToXLM(Math.max(p10, parseInt(BASE_FEE))),
- recommended: stroopsToXLM(Math.ceil(p50 * surgeMultiplier)),
- max: stroopsToXLM(Math.ceil(p90 * surgeMultiplier)),
+ min: stroopsToXLM(Math.max(p10, Number.parseInt(BASE_FEE, 10))),
+ recommended: stroopsToXLM(Math.ceil(p50 * multiplier)),
+ max: stroopsToXLM(Math.ceil(p90 * multiplier)),
};
this.cache = { tiers, isSurge, timestamp: Date.now() };
return { tiers, isSurge };
} catch {
- // Fallback to BASE_FEE if Horizon is unreachable
- const base = parseInt(BASE_FEE);
+ const base = Number.parseInt(BASE_FEE, 10);
const tiers: FeeTiers = {
min: stroopsToXLM(base),
recommended: stroopsToXLM(base * 2),
@@ -105,41 +94,58 @@ export class FeeEstimationService {
}
}
- /**
- * Estimate fee for a transaction with the given number of operations.
- */
- async estimateFee(operationCount: number = 1): Promise
{
+ async estimateFee(operationCount = 1): Promise {
const { tiers, isSurge } = await this.getFeeTiers();
- const totalFee = tiers.recommended * operationCount;
- const estimatedTimeSeconds = isSurge ? 10 : 5;
-
return {
tiers,
- totalFee,
+ totalFee: tiers.recommended * operationCount,
operationCount,
isSurge,
- estimatedTimeSeconds,
+ estimatedTimeSeconds: isSurge ? 10 : 5,
};
}
- /** Format a fee value in XLM for display. */
- formatFee(feeXLM: number, decimals: number = 7): string {
+ async estimatePaymentFee(amount: string, destination?: string): Promise {
+ const operationCount = destination ? 1 : 1;
+ const parsedAmount = Number.parseFloat(amount);
+
+ if (!Number.isFinite(parsedAmount) || parsedAmount <= 0) {
+ throw new Error('Enter a valid payment amount before estimating fees.');
+ }
+
+ return this.estimateFee(operationCount);
+ }
+
+ async getFeeRecommendations(): Promise {
+ const { tiers, isSurge } = await this.getFeeTiers();
+
+ return [
+ { label: 'low', fee: tiers.min, estimatedTimeSeconds: isSurge ? 20 : 10 },
+ { label: 'recommended', fee: tiers.recommended, estimatedTimeSeconds: isSurge ? 10 : 5 },
+ { label: 'priority', fee: tiers.max, estimatedTimeSeconds: isSurge ? 5 : 3 },
+ ];
+ }
+
+ formatFee(feeXLM: number, decimals = 7): string {
return `${feeXLM.toFixed(decimals)} XLM`;
}
- /** Total cost of a payment including the recommended fee. */
- async totalCost(amountXLM: number, operationCount: number = 1): Promise {
+ async totalCost(amountXLM: number, operationCount = 1): Promise {
const estimate = await this.estimateFee(operationCount);
return amountXLM + estimate.totalFee;
}
- /** Invalidate the cache (useful for testing). */
clearCache(): void {
this.cache = null;
}
+
+ dispose(): void {
+ if (this.networkChangeHandler && typeof window !== 'undefined') {
+ window.removeEventListener(NETWORK_CHANGE_EVENT, this.networkChangeHandler);
+ }
+ }
}
export const feeEstimationService = new FeeEstimationService();
export default feeEstimationService;
-export default feeEstimationService;