From 021d686148470394ad209c22e8cd10f1c2b2bd51 Mon Sep 17 00:00:00 2001 From: Ivan Vershigora Date: Tue, 26 Nov 2024 22:22:10 +0000 Subject: [PATCH 1/2] fix: do not start LDK unless we have fresh fee and block data --- __tests__/lightning.ts | 204 ++++++++++++++++++ e2e/send.e2e.js | 16 +- .../bottom-sheet/SendNavigation.tsx | 2 +- src/screens/Settings/AddressViewer/index.tsx | 9 +- src/screens/Settings/DevSettings/LdkDebug.tsx | 9 +- src/screens/Settings/RGSServer/index.tsx | 20 +- src/store/actions/wallet.ts | 1 - src/store/utils/fees.ts | 19 +- src/utils/helpers.ts | 6 + src/utils/i18n/locales/en/wallet.json | 3 + src/utils/lightning/index.ts | 158 ++++++++++++-- src/utils/startup/index.ts | 7 + src/utils/wallet/index.ts | 9 + src/utils/wallet/transactions.ts | 9 +- 14 files changed, 406 insertions(+), 66 deletions(-) create mode 100644 __tests__/lightning.ts diff --git a/__tests__/lightning.ts b/__tests__/lightning.ts new file mode 100644 index 000000000..9b6cb790a --- /dev/null +++ b/__tests__/lightning.ts @@ -0,0 +1,204 @@ +import { IBtInfo, IGetFeeEstimatesResponse } from 'beignet'; +import { getFees } from '../src/utils/lightning'; + +jest.mock('../src/utils/wallet', () => ({ + getSelectedNetwork: jest.fn(() => 'bitcoin'), +})); + +describe('getFees', () => { + const MEMPOOL_URL = 'https://mempool.space/api/v1/fees/recommended'; + const BLOCKTANK_URL = 'https://api1.blocktank.to/api/info'; + + const mockMempoolResponse: IGetFeeEstimatesResponse = { + fastestFee: 111, + halfHourFee: 110, + hourFee: 109, + minimumFee: 108, + }; + + const mockBlocktankResponse: IBtInfo = { + onchain: { + feeRates: { + fast: 999, + mid: 998, + slow: 997, + }, + }, + } as IBtInfo; + + beforeEach(() => { + jest.clearAllMocks(); + (global.fetch as jest.Mock) = jest.fn(url => { + if (url === MEMPOOL_URL) { + return Promise.resolve({ + ok: true, + json: () => Promise.resolve(mockMempoolResponse), + }); + } + if (url === BLOCKTANK_URL) { + return Promise.resolve({ + ok: true, + json: () => Promise.resolve(mockBlocktankResponse), + }); + } + return Promise.reject(new Error(`Unexpected URL: ${url}`)); + }); + }); + + it('should use mempool.space when both APIs succeed', async () => { + const result = await getFees(); + + expect(result).toEqual({ + onChainSweep: 111, + maxAllowedNonAnchorChannelRemoteFee: Math.max(25, 111 * 10), + minAllowedAnchorChannelRemoteFee: 108, + minAllowedNonAnchorChannelRemoteFee: 107, + anchorChannelFee: 109, + nonAnchorChannelFee: 110, + channelCloseMinimum: 108, + }); + expect(fetch).toHaveBeenCalledTimes(2); + expect(fetch).toHaveBeenCalledWith(MEMPOOL_URL); + expect(fetch).toHaveBeenCalledWith(BLOCKTANK_URL); + }); + + it('should use blocktank when mempool.space fails', async () => { + (global.fetch as jest.Mock) = jest.fn(url => { + if (url === MEMPOOL_URL) { + return Promise.reject('Mempool failed'); + } + if (url === BLOCKTANK_URL) { + return Promise.resolve({ + ok: true, + json: () => Promise.resolve(mockBlocktankResponse), + }); + } + return Promise.reject(new Error(`Unexpected URL: ${url}`)); + }); + + const result = await getFees(); + expect(result).toEqual({ + onChainSweep: 999, + maxAllowedNonAnchorChannelRemoteFee: Math.max(25, 999 * 10), + minAllowedAnchorChannelRemoteFee: 997, + minAllowedNonAnchorChannelRemoteFee: 996, + anchorChannelFee: 997, + nonAnchorChannelFee: 998, + channelCloseMinimum: 997, + }); + expect(fetch).toHaveBeenCalledTimes(3); + }); + + it('should retry mempool once and succeed even if blocktank fails', async () => { + let mempoolAttempts = 0; + (global.fetch as jest.Mock) = jest.fn(url => { + if (url === MEMPOOL_URL) { + mempoolAttempts++; + return mempoolAttempts === 1 + ? Promise.reject('First mempool try failed') + : Promise.resolve({ + ok: true, + json: () => Promise.resolve(mockMempoolResponse), + }); + } + if (url === BLOCKTANK_URL) { + return Promise.reject('Blocktank failed'); + } + return Promise.reject(new Error(`Unexpected URL: ${url}`)); + }); + + const result = await getFees(); + expect(result.onChainSweep).toBe(111); + expect(fetch).toHaveBeenCalledTimes(4); + expect(fetch).toHaveBeenCalledWith(MEMPOOL_URL); + expect(fetch).toHaveBeenCalledWith(BLOCKTANK_URL); + }); + + it('should throw error when all fetches fail', async () => { + (global.fetch as jest.Mock) = jest.fn(url => { + if (url === MEMPOOL_URL || url === BLOCKTANK_URL) { + return Promise.reject('API failed'); + } + return Promise.reject(new Error(`Unexpected URL: ${url}`)); + }); + + await expect(getFees()).rejects.toThrow(); + expect(fetch).toHaveBeenCalledTimes(4); + }); + + it('should handle invalid mempool response', async () => { + (global.fetch as jest.Mock) = jest.fn(url => { + if (url === MEMPOOL_URL) { + return Promise.resolve({ + ok: true, + json: () => Promise.resolve({ fastestFee: 0 }), + }); + } + if (url === BLOCKTANK_URL) { + return Promise.resolve({ + ok: true, + json: () => Promise.resolve(mockBlocktankResponse), + }); + } + return Promise.reject(new Error(`Unexpected URL: ${url}`)); + }); + + const result = await getFees(); + expect(result.onChainSweep).toBe(999); + }); + + it('should handle invalid blocktank response', async () => { + (global.fetch as jest.Mock) = jest.fn(url => { + if (url === MEMPOOL_URL) { + return Promise.resolve({ + ok: true, + json: () => Promise.resolve(mockMempoolResponse), + }); + } + if (url === BLOCKTANK_URL) { + return Promise.resolve({ + ok: true, + json: () => Promise.resolve({ onchain: { feeRates: { fast: 0 } } }), + }); + } + return Promise.reject(new Error(`Unexpected URL: ${url}`)); + }); + + const result = await getFees(); + expect(result.onChainSweep).toBe(111); + }); + + it('should handle timeout errors gracefully', async () => { + jest.useFakeTimers(); + + (global.fetch as jest.Mock) = jest.fn(url => { + if (url === MEMPOOL_URL) { + return new Promise(resolve => { + setTimeout(() => resolve({ + ok: true, + json: () => Promise.resolve(mockMempoolResponse), + }), 15000); // longer than timeout + }); + } + if (url === BLOCKTANK_URL) { + return new Promise(resolve => { + setTimeout(() => resolve({ + ok: true, + json: () => Promise.resolve(mockBlocktankResponse), + }), 15000); // longer than timeout + }); + } + return Promise.reject(new Error(`Unexpected URL: ${url}`)); + }); + + const feesPromise = getFees(); + + jest.advanceTimersByTime(11000); + + await expect(feesPromise).rejects.toThrow(); + expect(fetch).toHaveBeenCalledTimes(2); + + jest.useRealTimers(); + }); +}); + diff --git a/e2e/send.e2e.js b/e2e/send.e2e.js index 27ef10805..fd4264d8a 100644 --- a/e2e/send.e2e.js +++ b/e2e/send.e2e.js @@ -343,7 +343,7 @@ d('Send', () => { await waitFor( element(by.id('MoneyText').withAncestor(by.id('TotalBalance'))), ) - .toHaveText('109 170') + .toHaveText('109 004') .withTimeout(10000); // send to unified invoice w/ expired invoice @@ -365,7 +365,7 @@ d('Send', () => { await waitFor( element(by.id('MoneyText').withAncestor(by.id('TotalBalance'))), ) - .toHaveText('98 838') + .toHaveText('98 506') .withTimeout(10000); // send to unified invoice w/o amount (lightning) @@ -377,7 +377,7 @@ d('Send', () => { await expect(element(by.text('28 900'))).toBeVisible(); await element(by.id('AssetButton-switch')).tap(); // max amount (onchain) - await expect(element(by.text('68 506'))).toBeVisible(); + await expect(element(by.text('68 008'))).toBeVisible(); await element(by.id('AssetButton-switch')).tap(); await element(by.id('N1').withAncestor(by.id('SendAmountNumberPad'))).tap(); await element( @@ -392,7 +392,7 @@ d('Send', () => { await waitFor( element(by.id('MoneyText').withAncestor(by.id('TotalBalance'))), ) - .toHaveText('88 838') + .toHaveText('88 506') .withTimeout(10000); // send to unified invoice w/o amount (switch to onchain) @@ -411,7 +411,7 @@ d('Send', () => { await element(by.id('AssetButton-switch')).tap(); await element(by.id('AvailableAmount')).tap(); await element(by.id('ContinueAmount')).tap(); - await expect(element(by.text('68 506'))).toBeVisible(); + await expect(element(by.text('68 008'))).toBeVisible(); await element(by.id('NavigationBack')).atIndex(0).tap(); await element( @@ -430,7 +430,7 @@ d('Send', () => { await waitFor( element(by.id('MoneyText').withAncestor(by.id('TotalBalance'))), ) - .toHaveText('78 506') + .toHaveText('78 008') .withTimeout(10000); // send to lightning invoice w/ amount (quickpay) @@ -452,7 +452,7 @@ d('Send', () => { await waitFor( element(by.id('MoneyText').withAncestor(by.id('TotalBalance'))), ) - .toHaveText('77 506') + .toHaveText('77 008') .withTimeout(10000); // send to unified invoice w/ amount (quickpay) @@ -470,7 +470,7 @@ d('Send', () => { await waitFor( element(by.id('MoneyText').withAncestor(by.id('TotalBalance'))), ) - .toHaveText('76 506') + .toHaveText('76 008') .withTimeout(10000); // send to lightning invoice w/ amount (skip quickpay for large amounts) diff --git a/src/navigation/bottom-sheet/SendNavigation.tsx b/src/navigation/bottom-sheet/SendNavigation.tsx index c9c7d073e..3a8b082d3 100644 --- a/src/navigation/bottom-sheet/SendNavigation.tsx +++ b/src/navigation/bottom-sheet/SendNavigation.tsx @@ -121,7 +121,7 @@ const SendNavigation = (): ReactElement => { const onOpen = async (): Promise => { if (!transaction?.lightningInvoice) { - await updateOnchainFeeEstimates({ selectedNetwork, forceUpdate: true }); + await updateOnchainFeeEstimates({ forceUpdate: true }); if (!transaction?.inputs.length) { await setupOnChainTransaction(); } diff --git a/src/screens/Settings/AddressViewer/index.tsx b/src/screens/Settings/AddressViewer/index.tsx index dd7b501fa..025370463 100644 --- a/src/screens/Settings/AddressViewer/index.tsx +++ b/src/screens/Settings/AddressViewer/index.tsx @@ -750,13 +750,8 @@ const AddressViewer = ({ // Switching networks requires us to reset LDK. await setupLdk({ selectedWallet, selectedNetwork }); // Start wallet services with the newly selected network. - await startWalletServices({ - selectedNetwork: config.selectedNetwork, - }); - await updateOnchainFeeEstimates({ - selectedNetwork: config.selectedNetwork, - forceUpdate: true, - }); + await startWalletServices({ selectedNetwork: config.selectedNetwork }); + await updateOnchainFeeEstimates({ forceUpdate: true }); updateActivityList(); await syncLedger(); } diff --git a/src/screens/Settings/DevSettings/LdkDebug.tsx b/src/screens/Settings/DevSettings/LdkDebug.tsx index d7a408dd7..e24ff5462 100644 --- a/src/screens/Settings/DevSettings/LdkDebug.tsx +++ b/src/screens/Settings/DevSettings/LdkDebug.tsx @@ -76,7 +76,14 @@ const LdkDebug = (): ReactElement => { const onRestartLdk = async (): Promise => { setRestartingLdk(true); - await setupLdk({ selectedWallet, selectedNetwork }); + const res = await setupLdk({ selectedWallet, selectedNetwork }); + if (res.isErr()) { + showToast({ + type: 'error', + title: t('wallet:ldk_start_error_title'), + description: res.error.message, + }); + } setRestartingLdk(false); }; diff --git a/src/screens/Settings/RGSServer/index.tsx b/src/screens/Settings/RGSServer/index.tsx index 29f3bd7de..e8dfd43c0 100644 --- a/src/screens/Settings/RGSServer/index.tsx +++ b/src/screens/Settings/RGSServer/index.tsx @@ -74,15 +74,23 @@ const RGSServer = ({ const connectToRGSServer = async (): Promise => { setLoading(true); dispatch(updateSettings({ rapidGossipSyncUrl: rgsUrl })); - await setupLdk({ + const res = await setupLdk({ selectedWallet, selectedNetwork, }); - showToast({ - type: 'success', - title: t('rgs.update_success_title'), - description: t('rgs.update_success_description'), - }); + if (res.isOk()) { + showToast({ + type: 'success', + title: t('rgs.update_success_title'), + description: t('rgs.update_success_description'), + }); + } else { + showToast({ + type: 'error', + title: t('wallet:ldk_start_error_title'), + description: res.error.message, + }); + } setLoading(false); }; diff --git a/src/store/actions/wallet.ts b/src/store/actions/wallet.ts index bb1409d58..fd75aaf9a 100644 --- a/src/store/actions/wallet.ts +++ b/src/store/actions/wallet.ts @@ -663,7 +663,6 @@ export const setWalletData = async ( case 'feeEstimates': { const feeEstimates = data2 as IWalletData[typeof value]; updateOnchainFeeEstimates({ - selectedNetwork: getNetworkFromBeignet(network), feeEstimates, forceUpdate: true, }); diff --git a/src/store/utils/fees.ts b/src/store/utils/fees.ts index 8edd89ec4..bba26be12 100644 --- a/src/store/utils/fees.ts +++ b/src/store/utils/fees.ts @@ -1,20 +1,14 @@ -import { ok, err, Result } from '@synonymdev/result'; +import { IOnchainFees } from 'beignet'; +import { Result, err, ok } from '@synonymdev/result'; +import { getOnChainWalletAsync } from '../../utils/wallet'; import { dispatch, getFeesStore } from '../helpers'; import { updateOnchainFees } from '../slices/fees'; -import { getFeeEstimates } from '../../utils/wallet/transactions'; -import { EAvailableNetwork } from '../../utils/networks'; -import { getOnChainWalletAsync, getSelectedNetwork } from '../../utils/wallet'; -import { IOnchainFees } from 'beignet'; - -export const REFRESH_INTERVAL = 60 * 30; // in seconds, 30 minutes export const updateOnchainFeeEstimates = async ({ - selectedNetwork = getSelectedNetwork(), forceUpdate = false, feeEstimates, }: { - selectedNetwork: EAvailableNetwork; forceUpdate?: boolean; feeEstimates?: IOnchainFees; }): Promise> => { @@ -24,12 +18,7 @@ export const updateOnchainFeeEstimates = async ({ } if (!feeEstimates) { - const timestamp = feesStore.onchain.timestamp; - const difference = Math.floor((Date.now() - timestamp) / 1000); - if (!forceUpdate && difference < REFRESH_INTERVAL) { - return ok('On-chain fee estimates are up to date.'); - } - const feeEstimatesRes = await getFeeEstimates(selectedNetwork); + const feeEstimatesRes = await refreshOnchainFeeEstimates({ forceUpdate }); if (feeEstimatesRes.isErr()) { return err(feeEstimatesRes.error); } diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 0370856d6..bda673e69 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -5,6 +5,12 @@ import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import { i18nTime } from '../utils/i18n'; +/** + * Returns the result of a promise, or an error if the promise takes too long to resolve. + * @param {number} ms The time to wait in milliseconds. + * @param {Promise} promise The promise to resolve. + * @returns {Promise} + */ export const promiseTimeout = ( ms: number, promise: Promise, diff --git a/src/utils/i18n/locales/en/wallet.json b/src/utils/i18n/locales/en/wallet.json index 24ba32990..c5f20d8d0 100644 --- a/src/utils/i18n/locales/en/wallet.json +++ b/src/utils/i18n/locales/en/wallet.json @@ -689,6 +689,9 @@ "ldk_sync_error_title": { "string": "Lightning Sync Error" }, + "ldk_start_error_title": { + "string": "Lightning Startup Error" + }, "receive_insufficient_title": { "string": "Insufficient receiving balance." }, diff --git a/src/utils/lightning/index.ts b/src/utils/lightning/index.ts index 5c72782ce..6fadd60ae 100644 --- a/src/utils/lightning/index.ts +++ b/src/utils/lightning/index.ts @@ -4,7 +4,13 @@ import * as bitcoin from 'bitcoinjs-lib'; import ecc from '@bitcoinerlab/secp256k1'; import RNFS from 'react-native-fs'; import { err, ok, Result } from '@synonymdev/result'; -import { EPaymentType, TGetAddressHistory } from 'beignet'; +import { + EPaymentType, + IBtInfo, + IGetFeeEstimatesResponse, + IOnchainFees, + TGetAddressHistory, +} from 'beignet'; import lm, { ldk, defaultUserConfig, @@ -25,11 +31,11 @@ import lm, { TCloseChannelReq, TCreatePaymentReq, TGetFees, - THeader, TInvoice, TPaymentReq, TTransactionData, TTransactionPosition, + TGetBestBlock, } from '@synonymdev/react-native-ldk'; import { @@ -42,7 +48,6 @@ import { getBip39Passphrase, getCurrentAddressIndex, getMnemonicPhrase, - getOnChainWalletDataAsync, getOnChainWalletElectrumAsync, getSelectedNetwork, getSelectedWallet, @@ -55,9 +60,7 @@ import { getFeesStore, getLightningStore, getStore, - getWalletStore, } from '../../store/helpers'; -import { defaultHeader } from '../../store/shapes/wallet'; import { updateBackupState } from '../../store/slices/lightning'; import { moveMetaIncPaymentTags, @@ -93,7 +96,10 @@ import { EChannelClosureReason, } from '../../store/types/lightning'; import { getBlocktankInfo, isGeoBlocked, logToBlocktank } from '../blocktank'; -import { refreshOnchainFeeEstimates } from '../../store/utils/fees'; +import { + refreshOnchainFeeEstimates, + updateOnchainFeeEstimates, +} from '../../store/utils/fees'; import { __BACKUPS_SERVER_HOST__, __BACKUPS_SERVER_PUBKEY__, @@ -104,6 +110,7 @@ import { setKeychainValue } from '../keychain'; import i18n from '../i18n'; import { bitkitLedger, syncLedger } from '../ledger'; import { sendNavigation } from '../../navigation/bottom-sheet/SendNavigation'; +import { initialFeesState } from '../../store/slices/fees'; const PAYMENT_TIMEOUT = 8 * 1000; // 8 seconds @@ -234,8 +241,98 @@ const getScriptPubKeyHistory = async ( return await electrum.getScriptPubKeyHistory(scriptPubKey); }; -const getFees: TGetFees = async () => { - const fees = getFeesStore().onchain; +/** + * Fetch fees from mempool.space and blocktank.to, prioritizing mempool.space. + * Multiple attempts are made to fetch the fees from each provider + * Timeout after 10 seconds + */ +export const getFees: TGetFees = async () => { + const throwTimeout = (t: number): Promise => { + return new Promise((_, rej) => { + setTimeout(() => rej(new Error('timeout')), t); + }); + }; + + const fetchMp = async (): Promise => { + const f1 = await fetch('https://mempool.space/api/v1/fees/recommended'); + const j: IGetFeeEstimatesResponse = await f1.json(); + if ( + !f1.ok || + !( + j.fastestFee > 0 && + j.halfHourFee > 0 && + j.hourFee > 0 && + j.minimumFee > 0 + ) + ) { + throw new Error('Failed to fetch mempool.space fees'); + } + return { + fast: j.fastestFee, + normal: j.halfHourFee, + slow: j.hourFee, + minimum: j.minimumFee, + timestamp: Date.now(), + }; + }; + + const fetchBt = async (): Promise => { + const f2 = await fetch('https://api1.blocktank.to/api/info'); + const j: IBtInfo = await f2.json(); + if ( + !f2.ok || + !( + j?.onchain?.feeRates?.fast > 0 && + j?.onchain?.feeRates?.mid > 0 && + j?.onchain?.feeRates?.slow > 0 + ) + ) { + throw new Error('Failed to fetch blocktank fees'); + } + const { fast, mid, slow } = j.onchain.feeRates; + return { + fast, + normal: mid, + slow, + minimum: slow, + timestamp: Date.now(), + }; + }; + + let fees: IOnchainFees; + if (getFeesStore().override) { + fees = getFeesStore().onchain; + } else if (getSelectedNetwork() !== 'bitcoin') { + fees = initialFeesState.onchain; + } else { + fees = await new Promise((resolve, reject) => { + // try twice + const mpPromise = Promise.race([ + fetchMp().catch(fetchMp), + throwTimeout(10000), + ]); + const btPromise = Promise.race([ + fetchBt().catch(fetchBt), + throwTimeout(10000), + ]).catch(() => null); // Prevent unhandled rejection + + // prioritize mempool.space over blocktank + mpPromise.then(resolve).catch(() => { + btPromise + .then((btFees) => { + if (btFees !== null) { + resolve(btFees); + } else { + reject(new Error('Failed to fetch fees')); + } + }) + .catch(reject); + }); + }); + + updateOnchainFeeEstimates({ feeEstimates: fees }); + } + return { //https://github.com/lightningdevkit/rust-lightning/blob/main/CHANGELOG.md#api-updates onChainSweep: fees.fast, @@ -336,6 +433,20 @@ export const setupLdk = async ({ return err(backupRes.error); } + // check if getFees is working + try { + await getFees(); + } catch (e) { + return err(e); + } + + // check if getBestBlock is working + try { + await getBestBlock(); + } catch (e) { + return err(e); + } + const lmStart = await lm.start({ account: account.value, getFees, @@ -676,6 +787,11 @@ export const refreshLdk = async ({ shouldPreemptivelyStopLdk: false, }); if (setupResponse.isErr()) { + showToast({ + type: 'error', + title: i18n.t('wallet:ldk_start_error_title'), + description: setupResponse.error.message, + }); return handleRefreshError(setupResponse.error.message); } keepLdkSynced({ selectedNetwork }).then(); @@ -894,24 +1010,20 @@ export const getSha256 = (str: string): string => { }; /** - * Returns last known header information from storage. + * Returns the last known block header when Electrum is connected. * @returns {Promise} */ -export const getBestBlock = async ( - selectedNetwork: EAvailableNetwork = getSelectedNetwork(), -): Promise => { - try { - const beignetHeader = (await getOnChainWalletDataAsync()).header; - const storageHeader = getWalletStore().header[selectedNetwork]; - const header = - beignetHeader.height > storageHeader.height - ? beignetHeader - : storageHeader; - return header?.height ? header : defaultHeader; - } catch (e) { - console.log(e); - return defaultHeader; +const getBestBlock: TGetBestBlock = async () => { + const el = await getOnChainWalletElectrumAsync(); + let i = 0; + while (!(await el.isConnected()) || el.getBlockHeader().hex === '') { + await sleep(100); + // timeout 10 seconds + if (i++ > 100) { + throw new Error('Unable to connect to Electrum server'); + } } + return el.getBlockHeader(); }; /** diff --git a/src/utils/startup/index.ts b/src/utils/startup/index.ts index 0b1a658d6..44a875f39 100644 --- a/src/utils/startup/index.ts +++ b/src/utils/startup/index.ts @@ -26,6 +26,7 @@ import { TWalletName } from '../../store/types/wallet'; import { runChecks } from '../wallet/checks'; import { setupLedger, syncLedger } from '../ledger'; import i18n from '../i18n'; +import { showToast } from '../notifications'; /** * Creates a new wallet from scratch @@ -153,6 +154,12 @@ export const startWalletServices = async ({ }); if (setupResponse.isOk()) { keepLdkSynced({ selectedNetwork }).then(); + } else { + showToast({ + type: 'error', + title: i18n.t('wallet:ldk_start_error_title'), + description: setupResponse.error.message, + }); } } diff --git a/src/utils/wallet/index.ts b/src/utils/wallet/index.ts index d2d779df8..1e4adecb4 100644 --- a/src/utils/wallet/index.ts +++ b/src/utils/wallet/index.ts @@ -1102,6 +1102,15 @@ export const setupOnChainWallet = async ({ addressTypesToMonitor?: EAddressType[]; gapLimitOptions?: TGapLimitOptions; }): Promise> => { + // Disconnect from Electrum before setting up a new wallet + if (globalWallet) { + // If wallet refresh is in progress, wait for it to complete + if (globalWallet.isRefreshing) { + await globalWallet.refreshWallet(); + } + await globalWallet.electrum?.disconnect(); + } + if (!mnemonic) { const mnemonicRes = await getMnemonicPhrase(name); if (mnemonicRes.isErr()) { diff --git a/src/utils/wallet/transactions.ts b/src/utils/wallet/transactions.ts index dfa0d1322..e99abc489 100644 --- a/src/utils/wallet/transactions.ts +++ b/src/utils/wallet/transactions.ts @@ -887,6 +887,7 @@ export const broadcastBoost = async ({ */ export const getFeeEstimates = async ( selectedNetwork: EAvailableNetwork = getSelectedNetwork(), + forceUpdate: boolean, ): Promise> => { try { if (__E2E__) { @@ -904,11 +905,11 @@ export const getFeeEstimates = async ( } const wallet = await getOnChainWalletAsync(); - const feeRes = await wallet.getFeeEstimates(); - if (!feeRes) { - return err('Unable to get fee estimates.'); + const feeRes = await wallet.updateFeeEstimates(forceUpdate); + if (feeRes.isErr()) { + return err(feeRes.error); } - return ok(feeRes); + return ok(feeRes.value); } catch (e) { return err(e); } From cda3ed90e7111d7ab05bb6f9248d46a4a0047d14 Mon Sep 17 00:00:00 2001 From: Ivan Vershigora Date: Wed, 27 Nov 2024 15:18:53 +0000 Subject: [PATCH 2/2] fix: refactor getFeeEstimates, add comments to getFees --- src/utils/lightning/index.ts | 2 ++ src/utils/wallet/transactions.ts | 6 +----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/utils/lightning/index.ts b/src/utils/lightning/index.ts index 6fadd60ae..666fce5db 100644 --- a/src/utils/lightning/index.ts +++ b/src/utils/lightning/index.ts @@ -253,6 +253,7 @@ export const getFees: TGetFees = async () => { }); }; + // fetch, validate and map fees from mempool.space to IOnchainFees const fetchMp = async (): Promise => { const f1 = await fetch('https://mempool.space/api/v1/fees/recommended'); const j: IGetFeeEstimatesResponse = await f1.json(); @@ -276,6 +277,7 @@ export const getFees: TGetFees = async () => { }; }; + // fetch, validate and map fees from Blocktank to IOnchainFees const fetchBt = async (): Promise => { const f2 = await fetch('https://api1.blocktank.to/api/info'); const j: IBtInfo = await f2.json(); diff --git a/src/utils/wallet/transactions.ts b/src/utils/wallet/transactions.ts index e99abc489..25ae4e702 100644 --- a/src/utils/wallet/transactions.ts +++ b/src/utils/wallet/transactions.ts @@ -905,11 +905,7 @@ export const getFeeEstimates = async ( } const wallet = await getOnChainWalletAsync(); - const feeRes = await wallet.updateFeeEstimates(forceUpdate); - if (feeRes.isErr()) { - return err(feeRes.error); - } - return ok(feeRes.value); + return wallet.updateFeeEstimates(forceUpdate); } catch (e) { return err(e); }