From 4ebecd72c9c6ce21ec11c5f33a9a3aab2a00c28f Mon Sep 17 00:00:00 2001 From: myxmaster Date: Tue, 3 Dec 2024 20:42:54 +0100 Subject: [PATCH] general refactoring mainly regarding MobX actions + fixed error state --- lndmobile/channel.ts | 25 +- stores/ActivityStore.ts | 78 +-- stores/BalanceStore.ts | 142 +++--- stores/ChannelBackupStore.ts | 386 ++++++--------- stores/ChannelsStore.ts | 359 +++++++------- stores/FeeStore.ts | 162 +++--- stores/FiatStore.ts | 34 +- stores/InventoryStore.ts | 31 +- stores/InvoicesStore.ts | 230 +++++---- stores/LSPStore.ts | 234 +++++---- stores/LightningAddressStore.ts | 853 ++++++++++++-------------------- stores/LnurlPayStore.ts | 78 +-- stores/NodeInfoStore.ts | 51 +- stores/NotesStore.ts | 16 +- stores/OffersStore.ts | 39 +- stores/PaymentsStore.ts | 30 +- stores/PosStore.ts | 106 ++-- stores/SettingsStore.ts | 278 ++++++----- stores/SyncStore.ts | 52 +- stores/TransactionsStore.ts | 276 ++++++----- stores/UTXOsStore.ts | 95 ++-- stores/UnitsStore.ts | 11 +- views/Wallet/Wallet.tsx | 5 + 23 files changed, 1703 insertions(+), 1868 deletions(-) diff --git a/lndmobile/channel.ts b/lndmobile/channel.ts index 9cc7912bc..ec78711be 100644 --- a/lndmobile/channel.ts +++ b/lndmobile/channel.ts @@ -337,20 +337,17 @@ export const subscribeChannelEvents = async (): Promise => { /** * @throws */ -export const exportAllChannelBackups = - async (): Promise => { - const response = await sendCommand< - lnrpc.IChanBackupExportRequest, - lnrpc.ChanBackupExportRequest, - lnrpc.ChanBackupSnapshot - >({ - request: lnrpc.ChanBackupExportRequest, - response: lnrpc.ChanBackupSnapshot, - method: 'ExportAllChannelBackups', - options: {} - }); - return response; - }; +export const exportAllChannelBackups = (): Promise => + sendCommand< + lnrpc.IChanBackupExportRequest, + lnrpc.ChanBackupExportRequest, + lnrpc.ChanBackupSnapshot + >({ + request: lnrpc.ChanBackupExportRequest, + response: lnrpc.ChanBackupSnapshot, + method: 'ExportAllChannelBackups', + options: {} + }); /** * @throws diff --git a/stores/ActivityStore.ts b/stores/ActivityStore.ts index 03a83c1f5..3c9c136a4 100644 --- a/stores/ActivityStore.ts +++ b/stores/ActivityStore.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, runInAction } from 'mobx'; import EncryptedStorage from 'react-native-encrypted-storage'; // LN @@ -75,7 +75,6 @@ export default class ActivityStore { this.invoicesStore = invoicesStore; } - @action public resetFilters = async () => { this.filters = DEFAULT_FILTERS; await EncryptedStorage.setItem( @@ -85,7 +84,6 @@ export default class ActivityStore { this.setFilters(this.filters); }; - @action public setFiltersPos = async () => { this.filters = { lightning: true, @@ -137,7 +135,7 @@ export default class ActivityStore { this.setFilters(this.filters); }; - getSortedActivity = () => { + private getSortedActivity = () => { const activity: any[] = []; const payments = this.paymentsStore.payments; const transactions = this.transactionsStore.transactions; @@ -158,47 +156,53 @@ export default class ActivityStore { return sortedActivity; }; - @action - public getActivity = async () => { - this.loading = true; - this.activity = []; + private getActivity = async () => { + runInAction(() => { + this.loading = true; + this.activity = []; + }); + await this.paymentsStore.getPayments(); if (BackendUtils.supportsOnchainSends()) await this.transactionsStore.getTransactions(); await this.invoicesStore.getInvoices(); - this.activity = this.getSortedActivity(); - this.filteredActivity = this.activity; - - this.loading = false; + runInAction(() => { + this.activity = this.getSortedActivity(); + this.filteredActivity = this.activity; + this.loading = false; + }); }; - @action public updateInvoices = async (locale: string | undefined) => { await this.invoicesStore.getInvoices(); - this.activity = this.getSortedActivity(); - await this.setFilters(this.filters, locale); + await runInAction(async () => { + this.activity = this.getSortedActivity(); + await this.setFilters(this.filters, locale); + }); }; - @action public updateTransactions = async (locale: string | undefined) => { if (BackendUtils.supportsOnchainSends()) await this.transactionsStore.getTransactions(); - this.activity = this.getSortedActivity(); - await this.setFilters(this.filters, locale); + await runInAction(async () => { + this.activity = this.getSortedActivity(); + await this.setFilters(this.filters, locale); + }); }; - @action public async getFilters() { this.loading = true; try { const filters = await EncryptedStorage.getItem(STORAGE_KEY); if (filters) { - this.filters = JSON.parse(filters, (key, value) => - (key === 'startDate' || key === 'endDate') && value - ? new Date(value) - : value - ); + runInAction(() => { + this.filters = JSON.parse(filters, (key, value) => + (key === 'startDate' || key === 'endDate') && value + ? new Date(value) + : value + ); + }); } else { console.log('No activity filters stored'); } @@ -211,24 +215,26 @@ export default class ActivityStore { return this.filters; } - @action public setFilters = async (filters: Filter, locale?: string) => { - this.loading = true; - this.filters = filters; - this.filteredActivity = ActivityFilterUtils.filterActivities( - this.activity, - filters - ); - this.filteredActivity.forEach((activity) => { - if (activity instanceof Invoice) { - activity.determineFormattedRemainingTimeUntilExpiry(locale); - } + runInAction(() => { + this.loading = true; + this.filters = filters; + this.filteredActivity = ActivityFilterUtils.filterActivities( + this.activity, + filters + ); + this.filteredActivity.forEach((activity) => { + if (activity instanceof Invoice) { + activity.determineFormattedRemainingTimeUntilExpiry(locale); + } + }); }); + await EncryptedStorage.setItem(STORAGE_KEY, JSON.stringify(filters)); + this.loading = false; }; - @action public getActivityAndFilter = async ( locale: string | undefined, filters: Filter = this.filters diff --git a/stores/BalanceStore.ts b/stores/BalanceStore.ts index 87e237873..e9e5e1e7b 100644 --- a/stores/BalanceStore.ts +++ b/stores/BalanceStore.ts @@ -1,4 +1,4 @@ -import { action, reaction, observable } from 'mobx'; +import { action, reaction, observable, runInAction } from 'mobx'; import BigNumber from 'bignumber.js'; import SettingsStore from './SettingsStore'; @@ -32,7 +32,8 @@ export default class BalanceStore { ); } - reset = () => { + @action + public reset = () => { this.resetLightningBalance(); this.resetBlockchainBalance(); this.error = false; @@ -47,49 +48,51 @@ export default class BalanceStore { this.loadingBlockchainBalance = false; }; - resetLightningBalance = () => { + private resetLightningBalance = () => { this.pendingOpenBalance = 0; this.lightningBalance = 0; this.loadingLightningBalance = false; }; - balanceError = () => { + @action + private balanceError = () => { this.error = true; this.loadingBlockchainBalance = false; this.loadingLightningBalance = false; }; @action - public getBlockchainBalance = (set: boolean, reset: boolean) => { - this.loadingBlockchainBalance = true; + public getBlockchainBalance = async (set: boolean, reset: boolean) => { if (reset) this.resetBlockchainBalance(); - return BackendUtils.getBlockchainBalance({}) - .then((data: any) => { - // process external accounts - const accounts = data?.account_balance; - - const unconfirmedBlockchainBalance = Number( - accounts?.default - ? accounts.default.unconfirmed_balance || 0 - : data.unconfirmed_balance || 0 - ); - - const confirmedBlockchainBalance = Number( - accounts?.default - ? accounts?.default.confirmed_balance || 0 - : data.confirmed_balance || 0 - ); - - const totalBlockchainBalance = new BigNumber( - unconfirmedBlockchainBalance - ) - .plus(confirmedBlockchainBalance) - .toNumber(); - - const totalBlockchainBalanceAccounts = Number( - data.total_balance || 0 - ); - + this.loadingBlockchainBalance = true; + try { + const data = await BackendUtils.getBlockchainBalance({}); + // process external accounts + const accounts = data?.account_balance; + + const unconfirmedBlockchainBalance = Number( + accounts?.default + ? accounts.default.unconfirmed_balance || 0 + : data.unconfirmed_balance || 0 + ); + + const confirmedBlockchainBalance = Number( + accounts?.default + ? accounts?.default.confirmed_balance || 0 + : data.confirmed_balance || 0 + ); + + const totalBlockchainBalance = new BigNumber( + unconfirmedBlockchainBalance + ) + .plus(confirmedBlockchainBalance) + .toNumber(); + + const totalBlockchainBalanceAccounts = Number( + data.total_balance || 0 + ); + + runInAction(() => { if (set) { if (accounts && accounts.default && data.confirmed_balance) delete accounts.default; @@ -104,44 +107,43 @@ export default class BalanceStore { totalBlockchainBalanceAccounts; } this.loadingBlockchainBalance = false; - return { - unconfirmedBlockchainBalance, - confirmedBlockchainBalance, - totalBlockchainBalance, - accounts - }; - }) - .catch(() => { - this.balanceError(); }); + return { + unconfirmedBlockchainBalance, + confirmedBlockchainBalance, + totalBlockchainBalance, + accounts + }; + } catch { + this.balanceError(); + } }; @action - public getLightningBalance = (set: boolean, reset?: boolean) => { - this.loadingLightningBalance = true; + public getLightningBalance = async (set: boolean, reset?: boolean) => { if (reset) this.resetLightningBalance(); - return BackendUtils.getLightningBalance() - .then((data: any) => { - const pendingOpenBalance = Number( - data.pending_open_balance || 0 - ); - const lightningBalance = Number(data.balance || 0); + this.loadingLightningBalance = true; + try { + const data = await BackendUtils.getLightningBalance(); + const pendingOpenBalance = Number(data.pending_open_balance || 0); + const lightningBalance = Number(data.balance || 0); + runInAction(() => { if (set) { this.pendingOpenBalance = pendingOpenBalance; this.lightningBalance = lightningBalance; } this.loadingLightningBalance = false; - - return { - pendingOpenBalance, - lightningBalance - }; - }) - .catch(() => { - this.balanceError(); }); + + return { + pendingOpenBalance, + lightningBalance + }; + } catch { + this.balanceError(); + } }; @action @@ -150,18 +152,18 @@ export default class BalanceStore { const lightning = await this.getLightningBalance(false); const onChain = await this.getBlockchainBalance(false, false); - // LN - this.pendingOpenBalance = - (lightning && lightning.pendingOpenBalance) || 0; - this.lightningBalance = (lightning && lightning.lightningBalance) || 0; - // on-chain - this.otherAccounts = (onChain && onChain.accounts) || []; - this.unconfirmedBlockchainBalance = - (onChain && onChain.unconfirmedBlockchainBalance) || 0; - this.confirmedBlockchainBalance = - (onChain && onChain.confirmedBlockchainBalance) || 0; - this.totalBlockchainBalance = - (onChain && onChain.totalBlockchainBalance) || 0; + runInAction(() => { + // LN + this.pendingOpenBalance = lightning?.pendingOpenBalance || 0; + this.lightningBalance = lightning?.lightningBalance || 0; + // on-chain + this.otherAccounts = onChain?.accounts || []; + this.unconfirmedBlockchainBalance = + onChain?.unconfirmedBlockchainBalance || 0; + this.confirmedBlockchainBalance = + onChain?.confirmedBlockchainBalance || 0; + this.totalBlockchainBalance = onChain?.totalBlockchainBalance || 0; + }); return { onChain, diff --git a/stores/ChannelBackupStore.ts b/stores/ChannelBackupStore.ts index 8f2ae72a9..7fab49c80 100644 --- a/stores/ChannelBackupStore.ts +++ b/stores/ChannelBackupStore.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, runInAction } from 'mobx'; import ReactNativeBlobUtil from 'react-native-blob-util'; import EncryptedStorage from 'react-native-encrypted-storage'; import * as CryptoJS from 'crypto-js'; @@ -43,7 +43,7 @@ export default class ChannelBackupStore { this.loading = false; }; - logBackupStatus = async (status: string) => { + private logBackupStatus = async (status: string) => { await EncryptedStorage.setItem('LAST_CHANNEL_BACKUP_STATUS', status); await EncryptedStorage.setItem( 'LAST_CHANNEL_BACKUP_TIME', @@ -51,152 +51,104 @@ export default class ChannelBackupStore { ); }; - @action public backupChannels = async () => { - return new Promise(async (resolve, reject) => { - const backup: any = await exportAllChannelBackups(); - if (backup) { - if (backup?.multi_chan_backup?.multi_chan_backup) { - const multi = backup.multi_chan_backup.multi_chan_backup; - const multiString = Base64Utils.bytesToBase64(multi); + const backup = await exportAllChannelBackups(); + if (!backup?.multi_chan_backup?.multi_chan_backup) { + throw new Error(); + } + const multi = backup.multi_chan_backup.multi_chan_backup; + const multiString = Base64Utils.bytesToBase64(multi); - const encryptedBackup = CryptoJS.AES.encrypt( - multiString, - this.settingsStore.seedPhrase.toString() - ).toString(); + const encryptedBackup = CryptoJS.AES.encrypt( + multiString, + this.settingsStore.seedPhrase.toString() + ).toString(); - ReactNativeBlobUtil.fetch( - 'POST', - `${BACKUPS_HOST}/api/auth`, - { - 'Content-Type': 'application/json' - }, - JSON.stringify({ - pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey - }) - ) - .then((response: any) => { - const status = response.info().status; - if (status == 200) { - const data = response.json(); - const { verification } = data; + try { + const authResponse = await ReactNativeBlobUtil.fetch( + 'POST', + `${BACKUPS_HOST}/api/auth`, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey + }) + ); - BackendUtils.signMessage(verification) - .then((data: any) => { - const signature = - data.zbase || data.signature; - ReactNativeBlobUtil.fetch( - 'POST', - `${BACKUPS_HOST}/api/backup`, - { - 'Content-Type': - 'application/json' - }, - JSON.stringify({ - pubkey: this.nodeInfoStore - .nodeInfo.identity_pubkey, - message: verification, - signature, - backup: encryptedBackup - }) - ) - .then((response: any) => { - const data = response.json(); - if ( - status === 200 && - data.success - ) { - // log success timestamp - this.logBackupStatus( - 'SUCCESS' - ); - resolve(true); - } else { - this.logBackupStatus( - 'ERROR' - ); - reject(); - } - }) - .catch(() => { - this.logBackupStatus('ERROR'); - reject(); - }); - }) - .catch(() => { - this.logBackupStatus('ERROR'); - reject(); - }); - } - }) - .catch(() => { - this.logBackupStatus('ERROR'); - reject(); - }); - } + if (authResponse.info().status !== 200) { + this.logBackupStatus('ERROR'); + throw new Error(); } - }); - }; - @action - public recoverStaticChannelBackup = async () => { - return new Promise((resolve, reject) => { - ReactNativeBlobUtil.fetch( + const { verification } = authResponse.json(); + + const messageSignData = await BackendUtils.signMessage( + verification + ); + const signature = + messageSignData.zbase || messageSignData.signature; + const backupResponse = await ReactNativeBlobUtil.fetch( 'POST', - `${BACKUPS_HOST}/api/auth`, - { - 'Content-Type': 'application/json' - }, + `${BACKUPS_HOST}/api/backup`, + { 'Content-Type': 'application/json' }, JSON.stringify({ - pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey + pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey, + message: verification, + signature, + backup: encryptedBackup }) - ).then((response: any) => { - const status = response.info().status; - if (status == 200) { - const data = response.json(); - const { verification } = data; + ); + if ( + backupResponse.info().status === 200 && + backupResponse.json().success + ) { + // log success timestamp + this.logBackupStatus('SUCCESS'); + return true; + } - BackendUtils.signMessage(verification) - .then((data: any) => { - const signature = data.zbase || data.signature; - ReactNativeBlobUtil.fetch( - 'POST', - `${BACKUPS_HOST}/api/recover`, - { - 'Content-Type': 'application/json' - }, - JSON.stringify({ - pubkey: this.nodeInfoStore.nodeInfo - .identity_pubkey, - message: verification, - signature - }) - ) - .then(async (response: any) => { - const data = response.json(); - const { backup, created_at, success } = - data; - if (status === 200 && success && backup) { - await this.triggerRecovery(backup); + this.logBackupStatus('ERROR'); + throw new Error(); + } catch { + this.logBackupStatus('ERROR'); + throw new Error(); + } + }; - resolve({ - backup, - created_at - }); - } else { - reject(data.error); - } - }) - .catch((error: any) => { - reject(error); - }); - }) - .catch((error: any) => { - reject(error); - }); - } - }); - }); + public recoverStaticChannelBackup = async () => { + const authResponse = await ReactNativeBlobUtil.fetch( + 'POST', + `${BACKUPS_HOST}/api/auth`, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey + }) + ); + const authResponseStatus = authResponse.info().status; + if (authResponseStatus !== 200) { + throw new Error(`Auth response status code: ${authResponseStatus}`); + } + const { verification } = authResponse.json(); + + const messageSignData = await BackendUtils.signMessage(verification); + const signature = messageSignData.zbase || messageSignData.signature; + const recoverResponse = await ReactNativeBlobUtil.fetch( + 'POST', + `${BACKUPS_HOST}/api/recover`, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey, + message: verification, + signature + }) + ); + const data = recoverResponse.json(); + const { backup, created_at, success } = data; + if (recoverResponse.info().status === 200 && success && backup) { + await this.triggerRecovery(backup); + return { backup, created_at }; + } else { + throw new Error(data.error); + } }; @action @@ -204,29 +156,27 @@ export default class ChannelBackupStore { this.error_msg = ''; this.loading = true; - return await new Promise(async (resolve, reject) => { - try { - const decryptedBytes = CryptoJS.AES.decrypt( - backup, - this.settingsStore.seedPhrase.toString() - ); - const decryptedString = decryptedBytes.toString( - CryptoJS.enc.Utf8 - ); + try { + const decryptedBytes = CryptoJS.AES.decrypt( + backup, + this.settingsStore.seedPhrase.toString() + ); + const decryptedString = decryptedBytes.toString(CryptoJS.enc.Utf8); - await restoreChannelBackups(decryptedString); + await restoreChannelBackups(decryptedString); + runInAction(() => { this.error_msg = ''; this.loading = false; - - resolve(); - } catch (e: any) { + }); + } catch (e: any) { + runInAction(() => { this.error_msg = errorToUserFriendly(e); this.loading = false; + }); - reject(new Error(this.error_msg)); - } - }); + throw new Error(this.error_msg); + } }; @action @@ -234,86 +184,58 @@ export default class ChannelBackupStore { this.loading = true; this.error = false; this.backups = []; - return new Promise((resolve, reject) => { - ReactNativeBlobUtil.fetch( + try { + const authResponse = await ReactNativeBlobUtil.fetch( 'POST', `${BACKUPS_HOST}/api/auth`, - { - 'Content-Type': 'application/json' - }, + { 'Content-Type': 'application/json' }, JSON.stringify({ pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey }) - ) - .then((response: any) => { - const status = response.info().status; - if (status == 200) { - const data = response.json(); - const { verification } = data; + ); + const authResponseStatus = authResponse.info().status; + if (authResponseStatus !== 200) { + throw new Error( + `Auth response status code: ${authResponseStatus}` + ); + } + const { verification } = authResponse.json(); - BackendUtils.signMessage(verification) - .then((data: any) => { - const signature = data.zbase || data.signature; - ReactNativeBlobUtil.fetch( - 'POST', - `${BACKUPS_HOST}/api/recoverAdvanced`, - { - 'Content-Type': 'application/json' - }, - JSON.stringify({ - pubkey: this.nodeInfoStore.nodeInfo - .identity_pubkey, - message: verification, - signature - }) - ) - .then(async (response: any) => { - const data = response.json(); - const { backups } = data; - if (status === 200 && backups) { - this.backups = backups; - this.loading = false; - resolve({ - backups - }); - } else { - this.error = true; - this.loading = false; - reject(data.error); - } - }) - .catch((error: any) => { - this.backups = []; - this.error = true; - this.loading = false; - reject(error); - }); - }) - .catch((error: any) => { - this.backups = []; - this.error = true; - this.loading = false; - reject(error); - }); - } else { - this.backups = []; - this.error = true; - this.loading = false; - } + const messageSignData = await BackendUtils.signMessage( + verification + ); + const signature = + messageSignData.zbase || messageSignData.signature; + const recoverResponse = await ReactNativeBlobUtil.fetch( + 'POST', + `${BACKUPS_HOST}/api/recoverAdvanced`, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey, + message: verification, + signature }) - .catch(() => { - this.backups = []; - this.error = true; + ); + const data = recoverResponse.json(); + const { backups } = data; + if (recoverResponse.info().status === 200 && backups) { + runInAction(() => { + this.backups = backups; this.loading = false; }); - }).catch(() => { - this.backups = []; - this.error = true; - this.loading = false; - }); + return backups; + } + + throw new Error(data.error); + } catch (error) { + runInAction(() => { + this.error = true; + this.loading = false; + }); + throw error; + } }; - @action public initSubscribeChannelEvents = async () => { // Check if latest channel backup status is success // or if it's over three days ago and trigger backup @@ -321,15 +243,13 @@ export default class ChannelBackupStore { 'LAST_CHANNEL_BACKUP_STATUS' ); const time = await EncryptedStorage.getItem('LAST_CHANNEL_BACKUP_TIME'); - if (status && status === 'ERROR') this.backupChannels(); - if (time) { - const ONE_HOUR = 60 * 60 * 1000; /* ms */ - const THREE_DAYS = 36 * ONE_HOUR; - const olderThanThreeDays = - Number(new Date()) - Number(new Date(time)) > THREE_DAYS; - if (olderThanThreeDays) this.backupChannels(); + if ( + (status && status === 'ERROR') || + (time && this.isOlderThanThreeDays(time)) || + (!time && !status) + ) { + this.backupChannels(); } - if (!time && !status) this.backupChannels(); if (this.channelEventsSubscription?.remove) this.channelEventsSubscription.remove(); this.channelEventsSubscription = LndMobileEventEmitter.addListener( @@ -353,4 +273,10 @@ export default class ChannelBackupStore { await channel.subscribeChannelEvents(); }; + + private isOlderThanThreeDays(time: string) { + const ONE_HOUR = 60 * 60 * 1000; /* ms */ + const THREE_DAYS = 36 * ONE_HOUR; + return Number(new Date()) - Number(new Date(time)) > THREE_DAYS; + } } diff --git a/stores/ChannelsStore.ts b/stores/ChannelsStore.ts index 3af77ba5b..5cbe940a9 100644 --- a/stores/ChannelsStore.ts +++ b/stores/ChannelsStore.ts @@ -1,4 +1,4 @@ -import { action, observable, reaction } from 'mobx'; +import { action, observable, reaction, runInAction } from 'mobx'; import BigNumber from 'bignumber.js'; import _ from 'lodash'; import { randomBytes } from 'react-native-randombytes'; @@ -152,7 +152,7 @@ export default class ChannelsStore { } @action - resetOpenChannel = (silent?: boolean) => { + public resetOpenChannel = (silent?: boolean) => { this.loading = false; this.error = false; if (!silent) { @@ -172,13 +172,12 @@ export default class ChannelsStore { this.pending_chan_ids = []; }; - @action - clearCloseChannelErr = () => { + public clearCloseChannelErr = () => { this.closeChannelErr = null; }; @action - reset = () => { + public reset = () => { this.resetOpenChannel(); this.nodes = {}; this.channels = []; @@ -199,7 +198,7 @@ export default class ChannelsStore { }; @action - setSearch = (query: string) => { + public setSearch = (query: string) => { this.search = query; this.filterChannels(); this.filterPendingChannels(); @@ -207,7 +206,7 @@ export default class ChannelsStore { }; @action - setSort = (value: any) => { + public setSort = (value: any) => { this.sort = value; this.filterChannels(); this.filterPendingChannels(); @@ -215,15 +214,14 @@ export default class ChannelsStore { }; @action - setFilterOptions = (options: string[]) => { + public setFilterOptions = (options: string[]) => { this.filterOptions = options; this.filterChannels(); this.filterPendingChannels(); this.filterClosedChannels(); }; - @action - filter = (channels: Array) => { + private filter = (channels: Array) => { const query = this.search; const filtered = channels ?.filter( @@ -271,36 +269,29 @@ export default class ChannelsStore { return this.sort.dir === 'DESC' ? sorted : sorted?.reverse(); }; - @action - filterChannels = () => { + private filterChannels = () => { this.filteredChannels = this.filter(this.enrichedChannels); }; - @action - filterPendingChannels = () => { + private filterPendingChannels = () => { this.filteredPendingChannels = this.filter( this.enrichedPendingChannels ); }; - @action - filterClosedChannels = () => { + private filterClosedChannels = () => { this.filteredClosedChannels = this.filter(this.enrichedClosedChannels); }; - @action - setLoading = (state: boolean) => { - this.loading = state; - }; - - @action - getNodeInfo = (pubkey: string) => { + public getNodeInfo = (pubkey: string) => { this.loading = true; return BackendUtils.getNodeInfo([pubkey]) .then((data: any) => { - this.loading = false; - if (data?.node?.alias) - this.aliasMap.set(pubkey, data.node.alias); + runInAction(() => { + this.loading = false; + if (data?.node?.alias) + this.aliasMap.set(pubkey, data.node.alias); + }); return data.node; }) .catch(() => { @@ -308,8 +299,7 @@ export default class ChannelsStore { }); }; - @action - enrichChannels = async ( + private enrichChannels = async ( channels: Array, setPendingHtlcs?: boolean ): Promise => { @@ -345,44 +335,48 @@ export default class ChannelsStore { ) ); - for (const channel of channelsWithMissingAliases) { - const nodeInfo = this.nodes[channel.remotePubkey]; - const alias = nodeInfo?.alias || fixedAliases[channel.remotePubkey]; - if (alias) this.aliasesById[channel.channelId!] = alias; - } + runInAction(() => { + for (const channel of channelsWithMissingAliases) { + const nodeInfo = this.nodes[channel.remotePubkey]; + const alias = + nodeInfo?.alias || fixedAliases[channel.remotePubkey]; + if (alias) this.aliasesById[channel.channelId!] = alias; + } - if (setPendingHtlcs) this.pendingHTLCs = []; + if (setPendingHtlcs) this.pendingHTLCs = []; - for (const channel of channels) { - if (channel.alias == null) { - channel.alias = - this.nodes[channel.remotePubkey]?.alias || - this.aliasesById[channel.channelId!]; - } - channel.displayName = - channel.alias || - channel.remotePubkey || - channel.channelId || - localeString('models.Channel.unknownId'); - - if (BackendUtils.isLNDBased() && setPendingHtlcs) { - channel.pending_htlcs?.forEach((htlc: any) => { - htlc.channelDisplayName = channel.displayName; - }); + for (const channel of channels) { + if (channel.alias == null) { + channel.alias = + this.nodes[channel.remotePubkey]?.alias || + this.aliasesById[channel.channelId!]; + } + channel.displayName = + channel.alias || + channel.remotePubkey || + channel.channelId || + localeString('models.Channel.unknownId'); + + if (BackendUtils.isLNDBased() && setPendingHtlcs) { + channel.pending_htlcs?.forEach((htlc: any) => { + htlc.channelDisplayName = channel.displayName; + }); - this.pendingHTLCs.push(...channel.pending_htlcs); + this.pendingHTLCs.push(...channel.pending_htlcs); + } } - } - if (this.pendingHTLCs.length > 0) { - console.log('Pending HTLCs', this.pendingHTLCs); - } + if (this.pendingHTLCs.length > 0) { + console.log('Pending HTLCs', this.pendingHTLCs); + } - this.loading = false; + this.loading = false; + }); return channels; }; - getChannelsError = () => { + @action + private getChannelsError = () => { this.channels = []; this.error = true; this.loading = false; @@ -482,8 +476,10 @@ export default class ChannelsStore { return Promise.all(loadPromises) .then(() => { - this.loading = false; - this.error = false; + runInAction(() => { + this.loading = false; + this.error = false; + }); return; }) .catch(() => this.getChannelsError()); @@ -608,48 +604,53 @@ export default class ChannelsStore { perm }) .then(async () => { - if (!silent) { - this.errorPeerConnect = false; - this.connectingToPeer = false; - this.errorMsgPeer = null; - this.peerSuccess = true; - } - if (!connectPeerOnly) this.channelRequest = request; + runInAction(() => { + if (!silent) { + this.errorPeerConnect = false; + this.connectingToPeer = false; + this.errorMsgPeer = null; + this.peerSuccess = true; + } + if (!connectPeerOnly) this.channelRequest = request; + }); resolve(true); }) .catch((error: Error) => { - if (!silent) { - this.connectingToPeer = false; - this.peerSuccess = false; - } - this.channelSuccess = false; - // handle error - if ( - error && - error.toString() && - error.toString().includes('already') - ) { - if (!connectPeerOnly) { - this.channelRequest = request; + runInAction(() => { + if (!silent) { + this.connectingToPeer = false; + this.peerSuccess = false; + } + this.channelSuccess = false; + // handle error + if ( + error && + error.toString() && + error.toString().includes('already') + ) { + if (!connectPeerOnly) { + this.channelRequest = request; + } else { + if (!silent) { + this.errorMsgPeer = + errorToUserFriendly(error); + this.errorPeerConnect = true; + } + } + resolve(true); } else { if (!silent) { this.errorMsgPeer = errorToUserFriendly(error); this.errorPeerConnect = true; } + reject(this.errorMsgPeer); } - resolve(true); - } else { - if (!silent) { - this.errorMsgPeer = errorToUserFriendly(error); - this.errorPeerConnect = true; - } - reject(this.errorMsgPeer); - } + }); }); }); }; - handleChannelOpen = (request: any, outputs?: any) => { + private handleChannelOpen = (request: any, outputs?: any) => { const { account, sat_per_vbyte, utxos } = request; const inputs: any = []; @@ -720,16 +721,18 @@ export default class ChannelsStore { }) .then((data: any) => { if (data.publish_error) { - this.errorMsgChannel = errorToUserFriendly( - data.publish_error - ); - this.output_index = null; - this.funding_txid_str = null; - this.errorOpenChannel = true; - this.openingChannel = false; - this.channelRequest = null; - this.peerSuccess = false; - this.channelSuccess = false; + runInAction(() => { + this.errorMsgChannel = errorToUserFriendly( + data.publish_error + ); + this.output_index = null; + this.funding_txid_str = null; + this.errorOpenChannel = true; + this.openingChannel = false; + this.channelRequest = null; + this.peerSuccess = false; + this.channelSuccess = false; + }); } else { const formattedPsbt = new FundedPsbt( funded_psbt @@ -746,7 +749,28 @@ export default class ChannelsStore { } }) .then((data: any) => { - if (data.publish_error) { + runInAction(() => { + if (data.publish_error) { + this.funded_psbt = formattedPsbt; + this.output_index = null; + this.funding_txid_str = null; + this.errorOpenChannel = true; + this.openingChannel = false; + this.channelRequest = null; + this.peerSuccess = false; + this.channelSuccess = false; + } else { + // success case + this.errorOpenChannel = false; + this.openingChannel = false; + this.errorMsgChannel = null; + this.channelRequest = null; + this.channelSuccess = true; + } + }); + }) + .catch(() => + runInAction(() => { this.funded_psbt = formattedPsbt; this.output_index = null; this.funding_txid_str = null; @@ -755,54 +779,39 @@ export default class ChannelsStore { this.channelRequest = null; this.peerSuccess = false; this.channelSuccess = false; - } else { - // success case - this.errorOpenChannel = false; - this.openingChannel = false; - this.errorMsgChannel = null; - this.channelRequest = null; - this.channelSuccess = true; - } - }) - .catch(() => { - // handle error - this.funded_psbt = formattedPsbt; - this.output_index = null; - this.funding_txid_str = null; - this.errorOpenChannel = true; - this.openingChannel = false; - this.channelRequest = null; - this.peerSuccess = false; - this.channelSuccess = false; - }); + }) + ); } }) - .catch((error: any) => { - // handle error - this.errorMsgChannel = errorToUserFriendly(error); - this.output_index = null; - this.funding_txid_str = null; - this.errorOpenChannel = true; - this.openingChannel = false; - this.channelRequest = null; - this.peerSuccess = false; - this.channelSuccess = false; - }); + .catch((error: any) => + runInAction(() => { + this.errorMsgChannel = errorToUserFriendly(error); + this.output_index = null; + this.funding_txid_str = null; + this.errorOpenChannel = true; + this.openingChannel = false; + this.channelRequest = null; + this.peerSuccess = false; + this.channelSuccess = false; + }) + ); }) - .catch((error: any) => { - // handle error - this.errorMsgChannel = errorToUserFriendly(error); - this.output_index = null; - this.funding_txid_str = null; - this.errorOpenChannel = true; - this.openingChannel = false; - this.channelRequest = null; - this.peerSuccess = false; - this.channelSuccess = false; - }); + .catch((error: any) => + runInAction(() => { + this.errorMsgChannel = errorToUserFriendly(error); + this.output_index = null; + this.funding_txid_str = null; + this.errorOpenChannel = true; + this.openingChannel = false; + this.channelRequest = null; + this.peerSuccess = false; + this.channelSuccess = false; + }) + ); }; - handleChannelOpenError = (error: Error) => { + @action + private handleChannelOpenError = (error: Error) => { this.errorMsgChannel = errorToUserFriendly(error); this.output_index = null; this.funding_txid_str = null; @@ -813,7 +822,8 @@ export default class ChannelsStore { this.channelSuccess = false; }; - openChannel = (request: OpenChannelRequest) => { + @action + private openChannel = (request: OpenChannelRequest) => { const multipleChans = request?.additionalChannels && request.additionalChannels?.length > 0; @@ -950,25 +960,29 @@ export default class ChannelsStore { } } else { BackendUtils.openChannelSync(request) - .then((data: any) => { - this.output_index = data.output_index; - this.funding_txid_str = data.funding_txid_str; - this.errorOpenChannel = false; - this.openingChannel = false; - this.errorMsgChannel = null; - this.channelRequest = null; - this.channelSuccess = true; - }) - .catch((error: Error) => { - this.errorMsgChannel = errorToUserFriendly(error); - this.output_index = null; - this.funding_txid_str = null; - this.errorOpenChannel = true; - this.openingChannel = false; - this.channelRequest = null; - this.peerSuccess = false; - this.channelSuccess = false; - }); + .then((data: any) => + runInAction(() => { + this.output_index = data.output_index; + this.funding_txid_str = data.funding_txid_str; + this.errorOpenChannel = false; + this.openingChannel = false; + this.errorMsgChannel = null; + this.channelRequest = null; + this.channelSuccess = true; + }) + ) + .catch((error: Error) => + runInAction(() => { + this.errorMsgChannel = errorToUserFriendly(error); + this.output_index = null; + this.funding_txid_str = null; + this.errorOpenChannel = true; + this.openingChannel = false; + this.channelRequest = null; + this.peerSuccess = false; + this.channelSuccess = false; + }) + ); } }; @@ -981,24 +995,25 @@ export default class ChannelsStore { } BackendUtils.getChannelInfo(chanId) - .then((data: any) => { - this.chanInfo[chanId] = new ChannelInfo(data); - this.loading = false; - }) + .then((data: any) => + runInAction(() => { + this.chanInfo[chanId] = new ChannelInfo(data); + this.loading = false; + }) + ) .catch((error: any) => { - // handle error - this.errorMsgPeer = error.toString(); - if (this.chanInfo[chanId]) delete this.chanInfo[chanId]; - this.loading = false; + runInAction(() => { + this.errorMsgPeer = error.toString(); + if (this.chanInfo[chanId]) delete this.chanInfo[chanId]; + this.loading = false; + }); }); }; - @action public setChannelsType = (type: ChannelsType) => { this.channelsType = type; }; - @action public toggleSearch = () => { this.showSearch = !this.showSearch; }; diff --git a/stores/FeeStore.ts b/stores/FeeStore.ts index 13843862d..c8a21be6c 100644 --- a/stores/FeeStore.ts +++ b/stores/FeeStore.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, runInAction } from 'mobx'; import ReactNativeBlobUtil from 'react-native-blob-util'; import BigNumber from 'bignumber.js'; @@ -61,24 +61,31 @@ export default class FeeStore { .then((response: any) => { const status = response.info().status; if (status == 200) { - this.loading = false; - this.recommendedFees = response.json(); + runInAction(() => { + this.loading = false; + this.recommendedFees = response.json(); + }); return this.recommendedFees; } else { - this.recommendedFees = {}; - this.loading = false; - this.error = true; + runInAction(() => { + this.recommendedFees = {}; + this.loading = false; + this.error = true; + }); } }) .catch((error: any) => { console.error('Error fetching fees:', error); - this.recommendedFees = {}; - this.loading = false; - this.error = true; + runInAction(() => { + this.recommendedFees = {}; + this.loading = false; + this.error = true; + }); }); }; - resetFees = () => { + @action + public resetFees = () => { this.fees = {}; this.loadingFees = false; this.bumpFeeSuccess = false; @@ -90,22 +97,24 @@ export default class FeeStore { this.loadingFees = true; BackendUtils.getFees() .then((data: any) => { - if (data.channel_fees) { - const channelFees: any = {}; - data.channel_fees.forEach((channelFee: any) => { - channelFees[channelFee.chan_point] = channelFee; - }); + runInAction(() => { + if (data.channel_fees) { + const channelFees: any = {}; + data.channel_fees.forEach((channelFee: any) => { + channelFees[channelFee.chan_point] = channelFee; + }); - this.channelFees = channelFees; - } + this.channelFees = channelFees; + } - this.dayEarned = data.day_fee_sum || 0; - this.weekEarned = data.week_fee_sum || 0; - this.monthEarned = data.month_fee_sum || 0; - // Deprecated in LND - // Used in c-lightning-REST - this.totalEarned = data.total_fee_sum || 0; - this.loadingFees = false; + this.dayEarned = data.day_fee_sum || 0; + this.weekEarned = data.week_fee_sum || 0; + this.monthEarned = data.month_fee_sum || 0; + // Deprecated in LND + // Used in c-lightning-REST + this.totalEarned = data.total_fee_sum || 0; + this.loadingFees = false; + }); }) .catch((err: any) => { console.log('error getting fee report', err); @@ -169,13 +178,17 @@ export default class FeeStore { return BackendUtils.setFees(data) .then(() => { - this.loading = false; - this.setFeesSuccess = true; + runInAction(() => { + this.loading = false; + this.setFeesSuccess = true; + }); }) .catch((err: any) => { - this.setFeesErrorMsg = errorToUserFriendly(err); - this.loading = false; - this.setFeesError = true; + runInAction(() => { + this.setFeesErrorMsg = errorToUserFriendly(err); + this.loading = false; + this.setFeesError = true; + }); }); }; @@ -183,7 +196,8 @@ export default class FeeStore { this.tempFee = fee; }; - forwardingError = () => { + @action + private forwardingError = () => { this.forwardingEvents = []; this.forwardingHistoryError = true; this.loading = false; @@ -198,50 +212,26 @@ export default class FeeStore { this.earnedDuringTimeframe = new BigNumber(0); BackendUtils.getForwardingHistory(params) .then((data: any) => { - this.forwardingEvents = data.forwarding_events - .map((event: any) => new ForwardEvent(event)) - .reverse(); - - // Add up fees earned for this timeframe - // Uses BigNumber to prevent rounding errors in the add operation - this.forwardingEvents.map( - (event: ForwardEvent) => - (this.earnedDuringTimeframe = - this.earnedDuringTimeframe.plus( - Number(event.fee_msat) / 1000 - )) - ); + runInAction(() => { + this.forwardingEvents = data.forwarding_events + .map((event: any) => new ForwardEvent(event)) + .reverse(); - this.lastOffsetIndex = data.last_offset_index; - this.loading = false; - }) - .catch(() => { - this.forwardingError(); - }); - }; + // Add up fees earned for this timeframe + // Uses BigNumber to prevent rounding errors in the add operation + this.forwardingEvents.forEach( + (event: ForwardEvent) => + (this.earnedDuringTimeframe = + this.earnedDuringTimeframe.plus( + Number(event.fee_msat) / 1000 + )) + ); - @action - public bumpFee = (params?: any) => { - this.loading = true; - this.bumpFeeSuccess = false; - this.bumpFeeError = false; - const [txid_str, output_index] = params.outpoint.split(':'); - BackendUtils.bumpFee({ - ...params, - outpoint: { - txid_str, - output_index: Number(output_index) || 0 - } - }) - .then(() => { - this.bumpFeeSuccess = true; - this.loading = false; + this.lastOffsetIndex = data.last_offset_index; + this.loading = false; + }); }) - .catch((err: Error) => { - this.bumpFeeError = true; - this.bumpFeeErrorMsg = errorToUserFriendly(err); - this.loading = false; - }); + .catch(() => this.forwardingError()); }; @action @@ -258,8 +248,10 @@ export default class FeeStore { } }) .then(() => { - this.bumpFeeSuccess = true; - this.loading = false; + runInAction(() => { + this.bumpFeeSuccess = true; + this.loading = false; + }); }) .catch((err: Error) => { // if output isn't correct (it'll be index 0 or 1), try alternate input @@ -278,19 +270,25 @@ export default class FeeStore { } }) .then(() => { - this.bumpFeeError = false; - this.bumpFeeSuccess = true; - this.loading = false; + runInAction(() => { + this.bumpFeeError = false; + this.bumpFeeSuccess = true; + this.loading = false; + }); }) .catch((err: Error) => { - this.bumpFeeError = true; - this.bumpFeeErrorMsg = errorToUserFriendly(err); - this.loading = false; + runInAction(() => { + this.bumpFeeError = true; + this.bumpFeeErrorMsg = errorToUserFriendly(err); + this.loading = false; + }); }); } else { - this.bumpFeeError = true; - this.bumpFeeErrorMsg = errorToUserFriendly(err); - this.loading = false; + runInAction(() => { + this.bumpFeeError = true; + this.bumpFeeErrorMsg = errorToUserFriendly(err); + this.loading = false; + }); } }); }; diff --git a/stores/FiatStore.ts b/stores/FiatStore.ts index b144a987a..99cdb338a 100644 --- a/stores/FiatStore.ts +++ b/stores/FiatStore.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, runInAction } from 'mobx'; import ReactNativeBlobUtil from 'react-native-blob-util'; import BigNumber from 'bignumber.js'; @@ -543,7 +543,7 @@ export default class FiatStore { } }; - @action getSymbol = () => { + public getSymbol = () => { const { settings } = this.settingsStore; const { fiat } = settings; if (fiat) { @@ -558,7 +558,6 @@ export default class FiatStore { } }; - @action public getRate = (sats: boolean = false) => { const { settings } = this.settingsStore; const { fiat } = settings; @@ -724,16 +723,18 @@ export default class FiatStore { settings.fiat ); - if (this.fiatRates) { - this.fiatRates = this.fiatRates.filter( - (r) => r.code !== settings.fiat - ); - if (rate != null) { - this.fiatRates = this.fiatRates.concat([rate]); + runInAction(() => { + if (this.fiatRates) { + this.fiatRates = this.fiatRates.filter( + (r) => r.code !== settings.fiat + ); + if (rate != null) { + this.fiatRates = this.fiatRates.concat([rate]); + } + } else if (rate) { + this.fiatRates = [rate]; } - } else if (rate) { - this.fiatRates = [rate]; - } + }); } this.sourceOfCurrentFiatRates = settings.fiatRatesSource; @@ -742,7 +743,6 @@ export default class FiatStore { } }; - @action public formatAmountForDisplay = (input: string | number) => { const { symbol, space, rtl, separatorSwap } = this.getSymbol(); const amount = separatorSwap @@ -768,7 +768,9 @@ export default class FiatStore { currencyPair: `BTC_${code}` }; } - } catch {} + } catch (error) { + console.error('Error fetching fiat rates from yadio', error); + } return undefined; }; @@ -783,7 +785,9 @@ export default class FiatStore { if (status == 200) { return response.json(); } - } catch {} + } catch (error) { + console.error('Error fetching fiat rates from zeus', error); + } return undefined; }; diff --git a/stores/InventoryStore.ts b/stores/InventoryStore.ts index 43710824f..552a2cce0 100644 --- a/stores/InventoryStore.ts +++ b/stores/InventoryStore.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { observable, runInAction } from 'mobx'; import Product from '../models/Product'; import ProductCategory from '../models/ProductCategory'; import EncryptedStorage from 'react-native-encrypted-storage'; @@ -11,20 +11,21 @@ export default class InventoryStore { @observable products: Array = []; @observable public loading = false; - @action public async getInventory() { this.loading = true; try { - // Retrieve the categories + // Retrieve categories and products const categories = await EncryptedStorage.getItem(CATEGORY_KEY); - if (categories) { - this.categories = JSON.parse(categories) || []; - } - // Retrieve the products const products = await EncryptedStorage.getItem(PRODUCT_KEY); - if (products) { - this.products = JSON.parse(products) || []; - } + + runInAction(() => { + if (categories) { + this.categories = JSON.parse(categories) || []; + } + if (products) { + this.products = JSON.parse(products) || []; + } + }); } catch (error) { console.error('Could not load inventory', error); } finally { @@ -37,15 +38,13 @@ export default class InventoryStore { }; } - @action - public async setCategories(categories: string) { + private async setCategories(categories: string) { this.loading = true; await EncryptedStorage.setItem(CATEGORY_KEY, categories); this.loading = false; return categories; } - @action public updateCategories = async (newCategory: ProductCategory) => { const { categories: existingCategories } = await this.getInventory(); @@ -73,7 +72,6 @@ export default class InventoryStore { return categories; }; - @action public deleteCategory = async (categoryId: string) => { const { categories: existingCategories } = await this.getInventory(); @@ -87,15 +85,13 @@ export default class InventoryStore { } }; - @action - public async setProducts(products: string) { + private async setProducts(products: string) { this.loading = true; await EncryptedStorage.setItem(PRODUCT_KEY, products); this.loading = false; return products; } - @action public updateProducts = async (newProducts: Product[]) => { const { products: existingProducts } = await this.getInventory(); @@ -119,7 +115,6 @@ export default class InventoryStore { return products; }; - @action public deleteProduct = async (productIds: string[]) => { const { products: existingProducts } = await this.getInventory(); diff --git a/stores/InvoicesStore.ts b/stores/InvoicesStore.ts index beb6c51df..993a55f41 100644 --- a/stores/InvoicesStore.ts +++ b/stores/InvoicesStore.ts @@ -1,5 +1,5 @@ import url from 'url'; -import { action, observable, reaction } from 'mobx'; +import { action, observable, reaction, runInAction } from 'mobx'; import BigNumber from 'bignumber.js'; import ReactNativeBlobUtil from 'react-native-blob-util'; import { Alert } from 'react-native'; @@ -73,7 +73,8 @@ export default class InvoicesStore { ); } - reset = () => { + @action + public reset = () => { this.paymentRequest = ''; this.onChainAddress = ''; this.loading = false; @@ -94,7 +95,8 @@ export default class InvoicesStore { this.watchedInvoicePaidAmt = null; }; - resetInvoices = () => { + @action + private resetInvoices = () => { this.invoices = []; this.invoicesCount = 0; this.loading = false; @@ -105,18 +107,17 @@ export default class InvoicesStore { this.loading = true; await BackendUtils.getInvoices() .then((data: any) => { - this.invoices = data.invoices; - this.invoices = this.invoices.map( - (invoice) => new Invoice(invoice) - ); - this.invoices = this.invoices.slice().reverse(); - this.invoicesCount = - data.last_index_offset || this.invoices.length; - this.loading = false; + runInAction(() => { + this.invoices = data.invoices + .map((invoice: any) => new Invoice(invoice)) + .slice() + .reverse(); + this.invoicesCount = + data.last_index_offset || this.invoices.length; + this.loading = false; + }); }) - .catch(() => { - this.resetInvoices(); - }); + .catch(() => this.resetInvoices()); }; @action @@ -173,19 +174,25 @@ export default class InvoicesStore { : { unified: true } ) .then((onChainAddress: string) => { - this.onChainAddress = onChainAddress; - this.payment_request = paymentRequest; - this.loading = false; - this.creatingInvoice = false; + runInAction(() => { + this.onChainAddress = onChainAddress; + this.payment_request = paymentRequest; + this.loading = false; + this.creatingInvoice = false; + }); return { rHash, onChainAddress }; }) .catch(() => { - this.loading = false; - this.creatingInvoice = false; + runInAction(() => { + this.loading = false; + this.creatingInvoice = false; + }); }); } else { - this.payment_request = paymentRequest; - this.creatingInvoice = false; + runInAction(() => { + this.payment_request = paymentRequest; + this.creatingInvoice = false; + }); return { rHash }; } } @@ -193,7 +200,7 @@ export default class InvoicesStore { }; @action - public createInvoice = async ({ + private createInvoice = async ({ memo, value, expiry = '3600', @@ -249,13 +256,15 @@ export default class InvoicesStore { ) ); } catch (error: any) { - this.creatingInvoiceError = true; - this.creatingInvoice = false; - this.error_msg = - error.toString() || - localeString( - 'stores.InvoicesStore.errorCreatingInvoice' - ); + runInAction(() => { + this.creatingInvoiceError = true; + this.creatingInvoice = false; + this.error_msg = + error.toString() || + localeString( + 'stores.InvoicesStore.errorCreatingInvoice' + ); + }); return; } } @@ -330,27 +339,33 @@ export default class InvoicesStore { return BackendUtils.createInvoice(req) .then(async (data: any) => { if (data.error) { - this.creatingInvoiceError = true; - if (!unified) this.creatingInvoice = false; - const errString = - data.message.toString() || data.error.toString(); - this.error_msg = - errString === 'Bad arguments' && - this.settingsStore.implementation === 'lndhub' && - req.value === '0' - ? localeString( - 'stores.InvoicesStore.zeroAmountLndhub' - ) - : errString || - localeString( - 'stores.InvoicesStore.errorCreatingInvoice' - ); + runInAction(() => { + this.creatingInvoiceError = true; + if (!unified) this.creatingInvoice = false; + const errString = + data.message.toString() || data.error.toString(); + this.error_msg = + errString === 'Bad arguments' && + this.settingsStore.implementation === 'lndhub' && + req.value === '0' + ? localeString( + 'stores.InvoicesStore.zeroAmountLndhub' + ) + : errString || + localeString( + 'stores.InvoicesStore.errorCreatingInvoice' + ); + }); } const invoice = new Invoice(data); - if (!unified) this.payment_request = invoice.getPaymentRequest; - this.payment_request_amt = value; - if (!unified) this.creatingInvoice = false; + runInAction(() => { + if (!unified) + this.payment_request = invoice.getPaymentRequest; + this.payment_request_amt = value; + + if (!unified) this.creatingInvoice = false; + }); let jit_bolt11: string = ''; if ( @@ -415,12 +430,15 @@ export default class InvoicesStore { }; }) .catch((error: any) => { - // handle error - this.creatingInvoiceError = true; - this.creatingInvoice = false; - this.error_msg = - error.toString() || - localeString('stores.InvoicesStore.errorCreatingInvoice'); + runInAction(() => { + this.creatingInvoiceError = true; + this.creatingInvoice = false; + this.error_msg = + error.toString() || + localeString( + 'stores.InvoicesStore.errorCreatingInvoice' + ); + }); }); }; @@ -446,16 +464,21 @@ export default class InvoicesStore { .then((data: any) => { const address = data.address || data.bech32 || (data[0] && data[0].address); - if (!params.unified) this.onChainAddress = address; - if (!params.unified) this.creatingInvoice = false; + runInAction(() => { + if (!params.unified) this.onChainAddress = address; + if (!params.unified) this.creatingInvoice = false; + }); return address; }) .catch((error: any) => { - // handle error - this.error_msg = - error.toString() || - localeString('stores.InvoicesStore.errorGeneratingAddress'); - this.creatingInvoice = false; + runInAction(() => { + this.error_msg = + error.toString() || + localeString( + 'stores.InvoicesStore.errorGeneratingAddress' + ); + this.creatingInvoice = false; + }); }); }; @@ -472,24 +495,27 @@ export default class InvoicesStore { return BackendUtils.getNewChangeAddress(params) .then((data: any) => { const address = data.addr; - if (!params.unified) this.onChainAddress = address; - if (!params.unified) this.creatingInvoice = false; + runInAction(() => { + if (!params.unified) this.onChainAddress = address; + if (!params.unified) this.creatingInvoice = false; + }); return address; }) .catch((error: any) => { - // handle error - this.error_msg = - error.toString() || - localeString('stores.InvoicesStore.errorGeneratingAddress'); - this.creatingInvoice = false; + runInAction(() => { + this.error_msg = + error.toString() || + localeString( + 'stores.InvoicesStore.errorGeneratingAddress' + ); + this.creatingInvoice = false; + }); }); }; - @action public clearAddress = () => (this.onChainAddress = null); - @action - public clearPaymentRequest = () => (this.payment_request = null); + private clearPaymentRequest = () => (this.payment_request = null); @action public clearPayReq = () => { @@ -514,54 +540,58 @@ export default class InvoicesStore { return BackendUtils.decodePaymentRequest([paymentRequest]) .then((data: any) => { - this.pay_req = new Invoice(data); - this.getPayReqError = null; - this.loading = false; + runInAction(() => { + this.pay_req = new Invoice(data); + this.getPayReqError = null; + this.loading = false; + }); return; }) .catch((error: Error) => { - // handle error - this.pay_req = null; - this.getPayReqError = errorToUserFriendly(error); - this.loading = false; + runInAction(() => { + this.pay_req = null; + this.getPayReqError = errorToUserFriendly(error); + this.loading = false; + }); }); }; - getRoutesError = () => { + @action + private getRoutesError = () => { this.loadingFeeEstimate = false; this.feeEstimate = null; this.successProbability = null; }; @action - public getRoutes = (destination: string, amount: string | number) => { + private getRoutes = (destination: string, amount: string | number) => { this.loadingFeeEstimate = true; this.feeEstimate = null; this.successProbability = null; return BackendUtils.getRoutes([destination, amount]) .then((data: any) => { - this.loadingFeeEstimate = false; - this.successProbability = data.success_prob - ? data.success_prob * 100 - : 0; - - const routes = data.routes; - if (routes) { - routes.forEach((route: any) => { - // expect lnd to pick the cheapest route - if (this.feeEstimate) { - if (route.total_fees < this.feeEstimate) { - this.feeEstimate = route.total_fees; + runInAction(() => { + this.loadingFeeEstimate = false; + this.successProbability = data.success_prob + ? data.success_prob * 100 + : 0; + + const routes = data.routes; + if (routes) { + routes.forEach((route: any) => { + // expect lnd to pick the cheapest route + if (this.feeEstimate) { + if (route.total_fees < this.feeEstimate) { + this.feeEstimate = route.total_fees; + } + } else { + this.feeEstimate = route.total_fees || 0; } - } else { - this.feeEstimate = route.total_fees || 0; - } - }); - } + }); + } + }); }) - .catch(() => { - this.getRoutesError(); - }); + .catch(() => this.getRoutesError()); }; } diff --git a/stores/LSPStore.ts b/stores/LSPStore.ts index 93664726e..0faed3454 100644 --- a/stores/LSPStore.ts +++ b/stores/LSPStore.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, runInAction } from 'mobx'; import ReactNativeBlobUtil from 'react-native-blob-util'; import { v4 as uuidv4 } from 'uuid'; @@ -66,9 +66,7 @@ export default class LSPStore { }; @action - public resetFee = () => { - this.zeroConfFee = undefined; - }; + public resetFee = () => (this.zeroConfFee = undefined); @action public resetLSPS1Data = () => { @@ -79,7 +77,7 @@ export default class LSPStore { this.error_msg = ''; }; - isOlympus = () => { + public isOlympus = () => { const olympusREST = this.nodeInfoStore!.nodeInfo.isTestNet ? DEFAULT_LSPS1_REST_TESTNET : DEFAULT_LSPS1_REST_MAINNET; @@ -101,37 +99,35 @@ export default class LSPStore { return false; }; - getLSPHost = () => + private getLSPHost = () => this.nodeInfoStore!.nodeInfo.isTestNet ? this.settingsStore.settings.lspTestnet : this.settingsStore.settings.lspMainnet; - getLSPS1Pubkey = () => + public getLSPS1Pubkey = () => this.nodeInfoStore!.nodeInfo.isTestNet ? this.settingsStore.settings.lsps1PubkeyTestnet : this.settingsStore.settings.lsps1PubkeyMainnet; - getLSPS1Host = () => + public getLSPS1Host = () => this.nodeInfoStore!.nodeInfo.isTestNet ? this.settingsStore.settings.lsps1HostTestnet : this.settingsStore.settings.lsps1HostMainnet; - getLSPS1Rest = () => + public getLSPS1Rest = () => this.nodeInfoStore!.nodeInfo.isTestNet ? this.settingsStore.settings.lsps1RestTestnet : this.settingsStore.settings.lsps1RestMainnet; - encodeMesage = (n: any) => Buffer.from(JSON.stringify(n)).toString('hex'); + private encodeMesage = (n: any) => + Buffer.from(JSON.stringify(n)).toString('hex'); - @action public getLSPInfo = () => { return new Promise((resolve, reject) => { ReactNativeBlobUtil.fetch( 'get', `${this.getLSPHost()}/api/v1/info`, - { - 'Content-Type': 'application/json' - } + { 'Content-Type': 'application/json' } ) .then(async (response: any) => { const status = response.info().status; @@ -157,25 +153,29 @@ export default class LSPStore { ); } catch (e) {} } else { - this.error = true; - this.error_msg = errorToUserFriendly(data.message); - // handle LSP geoblocking :( - if ( - this.error_msg.includes( - 'unavailable in your country' - ) - ) { - this.showLspSettings = true; - } + runInAction(() => { + this.error = true; + this.error_msg = errorToUserFriendly(data.message); + // handle LSP geoblocking :( + if ( + this.error_msg.includes( + 'unavailable in your country' + ) + ) { + this.showLspSettings = true; + } + }); reject(); } }) .catch(() => { - this.error = true; - this.error_msg = localeString( - 'stores.LSPStore.connectionError' - ); - this.showLspSettings = true; + runInAction(() => { + this.error = true; + this.error_msg = localeString( + 'stores.LSPStore.connectionError' + ); + this.showLspSettings = true; + }); reject(); }); }); @@ -194,9 +194,7 @@ export default class LSPStore { 'Content-Type': 'application/json', 'x-auth-token': settings.lspAccessKey } - : { - 'Content-Type': 'application/json' - }, + : { 'Content-Type': 'application/json' }, JSON.stringify({ amount_msat, pubkey: this.nodeInfoStore.nodeInfo.nodeId @@ -206,16 +204,19 @@ export default class LSPStore { const status = response.info().status; const data = response.json(); if (status == 200) { - this.zeroConfFee = - data.fee_amount_msat !== undefined - ? Number.parseInt( - ( - Number(data.fee_amount_msat) / 1000 - ).toString() - ) - : undefined; - this.feeId = data.id; - this.error = false; + runInAction(() => { + this.zeroConfFee = + data.fee_amount_msat !== undefined + ? Number.parseInt( + ( + Number(data.fee_amount_msat) / + 1000 + ).toString() + ) + : undefined; + this.feeId = data.id; + this.error = false; + }); resolve(this.zeroConfFee); } else { this.error = true; @@ -229,7 +230,7 @@ export default class LSPStore { }); }; - handleChannelAcceptorEvent = async (channelAcceptRequest: any) => { + private handleChannelAcceptorEvent = async (channelAcceptRequest: any) => { try { const requestPubkey = Base64Utils.bytesToHex( channelAcceptRequest.node_pubkey @@ -253,7 +254,6 @@ export default class LSPStore { } }; - @action public initChannelAcceptor = async () => { if (this.channelAcceptor) return; if (this.settingsStore.implementation === 'embedded-lnd') { @@ -303,9 +303,7 @@ export default class LSPStore { 'Content-Type': 'application/json', 'x-auth-token': settings.lspAccessKey } - : { - 'Content-Type': 'application/json' - }, + : { 'Content-Type': 'application/json' }, JSON.stringify({ bolt11, fee_id: this.feeId, @@ -318,16 +316,18 @@ export default class LSPStore { if (status == 200 || status == 201) { resolve(data.jit_bolt11); } else { - this.error = true; - this.error_msg = `${localeString( - 'stores.LSPStore.error' - )}: ${data.message}`; - if ( - data.message && - data.message.includes('access key') - ) { - this.showLspSettings = true; - } + runInAction(() => { + this.error = true; + this.error_msg = `${localeString( + 'stores.LSPStore.error' + )}: ${data.message}`; + if ( + data.message && + data.message.includes('access key') + ) { + this.showLspSettings = true; + } + }); reject(); } }) @@ -359,8 +359,10 @@ export default class LSPStore { resolve(response); }) .catch((error: any) => { - this.error = true; - this.error_msg = errorToUserFriendly(error); + runInAction(() => { + this.error = true; + this.error_msg = errorToUserFriendly(error); + }); reject(error); }); }); @@ -408,9 +410,11 @@ export default class LSPStore { let timer = 7000; const timeoutId = setTimeout(() => { if (!this.resolvedCustomMessage) { - this.error = true; - this.error_msg = localeString('views.LSPS1.timeoutError'); - this.loading = false; + runInAction(() => { + this.error = true; + this.error_msg = localeString('views.LSPS1.timeoutError'); + this.loading = false; + }); } }, timer); @@ -420,8 +424,10 @@ export default class LSPStore { async (event: any) => { try { const decoded = index.decodeCustomMessage(event.data); - this.handleCustomMessages(decoded); - this.resolvedCustomMessage = true; + runInAction(() => { + this.handleCustomMessages(decoded); + this.resolvedCustomMessage = true; + }); clearTimeout(timeoutId); } catch (error: any) { console.error( @@ -436,8 +442,10 @@ export default class LSPStore { BackendUtils.subscribeCustomMessages( (response: any) => { const decoded = response.result; - this.handleCustomMessages(decoded); - this.resolvedCustomMessage = true; + runInAction(() => { + this.handleCustomMessages(decoded); + this.resolvedCustomMessage = true; + }); clearTimeout(timeoutId); }, (error: any) => { @@ -449,7 +457,6 @@ export default class LSPStore { } }; - @action public getInfoREST = () => { const endpoint = `${this.getLSPS1Rest()}/api/v1/get_info`; @@ -457,25 +464,29 @@ export default class LSPStore { return ReactNativeBlobUtil.fetch('GET', endpoint) .then((response) => { - if (response.info().status === 200) { - const responseData = JSON.parse(response.data); - this.getInfoData = responseData; - try { - const uri = responseData.uris[0]; - const pubkey = uri.split('@')[0]; - this.pubkey = pubkey; - } catch (e) {} - this.loading = false; - } else { + runInAction(() => { + if (response.info().status === 200) { + const responseData = JSON.parse(response.data); + this.getInfoData = responseData; + try { + const uri = responseData.uris[0]; + const pubkey = uri.split('@')[0]; + this.pubkey = pubkey; + } catch (e) {} + this.loading = false; + } else { + this.error = true; + this.error_msg = 'Error fetching get_info data'; + this.loading = false; + } + }); + }) + .catch(() => { + runInAction(() => { this.error = true; this.error_msg = 'Error fetching get_info data'; this.loading = false; - } - }) - .catch(() => { - this.error = true; - this.error_msg = 'Error fetching get_info data'; - this.loading = false; + }); }); }; @@ -505,35 +516,38 @@ export default class LSPStore { return ReactNativeBlobUtil.fetch( 'POST', endpoint, - { - 'Content-Type': 'application/json' - }, + { 'Content-Type': 'application/json' }, data ) .then((response) => { const responseData = JSON.parse(response.data); - if (responseData.error) { - this.error = true; - this.error_msg = errorToUserFriendly(responseData.message); - this.loading = false; - } else { - this.createOrderResponse = responseData; - this.loading = false; - console.log('Response received:', responseData); - } + runInAction(() => { + if (responseData.error) { + this.error = true; + this.error_msg = errorToUserFriendly( + responseData.message + ); + this.loading = false; + } else { + this.createOrderResponse = responseData; + this.loading = false; + console.log('Response received:', responseData); + } + }); }) .catch((error) => { console.error( 'Error sending (create_order) custom message:', error ); - this.error = true; - this.error_msg = errorToUserFriendly(error); - this.loading = false; + runInAction(() => { + this.error = true; + this.error_msg = errorToUserFriendly(error); + this.loading = false; + }); }); }; - @action public getOrderREST(id: string, RESTHost: string) { this.loading = true; const endpoint = `${RESTHost}/api/v1/get_order?order_id=${id}`; @@ -546,18 +560,22 @@ export default class LSPStore { .then((response) => { const responseData = JSON.parse(response.data); console.log('Response received:', responseData); - if (responseData.error) { - this.error = true; - this.error_msg = responseData.message; - } else { - this.getOrderResponse = responseData; - } + runInAction(() => { + if (responseData.error) { + this.error = true; + this.error_msg = responseData.message; + } else { + this.getOrderResponse = responseData; + } + }); }) .catch((error) => { console.error('Error sending custom message:', error); - this.error = true; - this.error_msg = errorToUserFriendly(error); - this.loading = false; + runInAction(() => { + this.error = true; + this.error_msg = errorToUserFriendly(error); + this.loading = false; + }); }); } diff --git a/stores/LightningAddressStore.ts b/stores/LightningAddressStore.ts index 1882c302a..5b9916414 100644 --- a/stores/LightningAddressStore.ts +++ b/stores/LightningAddressStore.ts @@ -1,5 +1,5 @@ import { Platform } from 'react-native'; -import { action, observable } from 'mobx'; +import { action, observable, runInAction } from 'mobx'; import ReactNativeBlobUtil from 'react-native-blob-util'; import EncryptedStorage from 'react-native-encrypted-storage'; import { Notifications } from 'react-native-notifications'; @@ -41,7 +41,7 @@ export default class LightningAddressStore { @observable public redeeming: boolean = false; @observable public redeemingAll: boolean = false; @observable public error: boolean = false; - @observable public error_msg: string = ''; + @observable public error_msg: string | undefined; @observable public availableHashes: number = 0; // on server @observable public localHashes: number = 0; // on device @observable public paid: any = []; @@ -62,7 +62,6 @@ export default class LightningAddressStore { this.settingsStore = settingsStore; } - @action public deleteAndGenerateNewPreimages = async () => { this.loading = true; await EncryptedStorage.setItem( @@ -72,7 +71,6 @@ export default class LightningAddressStore { this.generatePreimages(true); }; - @action public DEV_deleteLocalHashes = async () => { this.loading = true; await EncryptedStorage.setItem( @@ -83,45 +81,32 @@ export default class LightningAddressStore { this.loading = false; }; - @action - public getPreimageMap = async () => { + private getPreimageMap = async () => { this.loading = true; const map = await EncryptedStorage.getItem(HASHES_STORAGE_STRING); - if (map) { - this.preimageMap = JSON.parse(map); - this.localHashes = Object.keys(this.preimageMap).length; - } - - this.loading = false; - return this.preimageMap; - }; - - @action - public getLightningAddressActivated = async () => { - this.loading = true; - const lightningAddressActivated = await EncryptedStorage.getItem( - ADDRESS_ACTIVATED_STRING - ); + runInAction(() => { + if (map) { + this.preimageMap = JSON.parse(map); + this.localHashes = Object.keys(this.preimageMap).length; + } - if (lightningAddressActivated) { - this.lightningAddressActivated = Boolean(lightningAddressActivated); - this.loading = false; - return this.lightningAddressActivated; - } else { this.loading = false; - } + }); + return this.preimageMap; }; - setLightningAddress = async (handle: string, domain: string) => { + private setLightningAddress = async (handle: string, domain: string) => { await EncryptedStorage.setItem(ADDRESS_ACTIVATED_STRING, 'true'); - this.lightningAddressActivated = true; - this.lightningAddressHandle = handle; - this.lightningAddressDomain = domain; - this.lightningAddress = `${handle}@${domain}`; + runInAction(() => { + this.lightningAddressActivated = true; + this.lightningAddressHandle = handle; + this.lightningAddressDomain = domain; + this.lightningAddress = `${handle}@${domain}`; + }); }; - deleteHash = async (hash: string) => { + private deleteHash = async (hash: string) => { const hashesString = (await EncryptedStorage.getItem(HASHES_STORAGE_STRING)) || '{}'; @@ -136,7 +121,7 @@ export default class LightningAddressStore { }; @action - public generatePreimages = async (newDevice?: boolean) => { + private generatePreimages = async (newDevice?: boolean) => { this.error = false; this.error_msg = ''; this.loading = true; @@ -194,8 +179,8 @@ export default class LightningAddressStore { JSON.stringify(newHashes) ); - return new Promise((resolve, reject) => { - ReactNativeBlobUtil.fetch( + try { + const authResponse = await ReactNativeBlobUtil.fetch( 'POST', `${LNURL_HOST}/lnurl/auth`, { @@ -204,89 +189,47 @@ export default class LightningAddressStore { JSON.stringify({ pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey }) - ) - .then((response: any) => { - const status = response.info().status; - const data = response.json(); - if (status == 200) { - const { verification } = data; - BackendUtils.signMessage(verification) - .then((data: any) => { - const signature = data.zbase || data.signature; - ReactNativeBlobUtil.fetch( - 'POST', - `${LNURL_HOST}/lnurl/submitHashes`, - { - 'Content-Type': 'application/json' - }, - JSON.stringify( - nostrSignatures.length > 0 - ? { - pubkey: this.nodeInfoStore - .nodeInfo.identity_pubkey, - message: verification, - signature, - hashes, - nostrSignatures, - newDevice - } - : { - pubkey: this.nodeInfoStore - .nodeInfo.identity_pubkey, - message: verification, - signature, - hashes, - newDevice - } - ) - ) - .then(async (response: any) => { - const data = response.json(); - const { created_at, success } = data; - - if (status === 200 && success) { - this.loading = false; - resolve({ - created_at - }); - - this.status(); - } else { - this.loading = false; - this.error = true; - this.error_msg = - data?.error?.toString(); - reject(data.error); - } - }) - .catch((error: any) => { - this.loading = false; - this.error = true; - this.error_msg = - error && error.toString(); - reject(error); - }); - }) - .catch((error: any) => { - this.loading = false; - this.error = true; - this.error_msg = error && error.toString(); - reject(error); - }); - } else { - this.loading = false; - this.error = true; - this.error_msg = data?.error?.toString(); - reject(data.error); - } - }) - .catch((error: any) => { - this.loading = false; - this.error = true; - this.error_msg = error && error.toString(); - reject(error); - }); - }); + ); + + const authData = authResponse.json(); + if (authResponse.info().status !== 200) throw authData.error; + + const { verification } = authData; + const signData = await BackendUtils.signMessage(verification); + const signature = signData.zbase || signData.signature; + + const payload = { + pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey, + message: verification, + signature, + hashes, + newDevice, + ...(nostrSignatures.length > 0 && { nostrSignatures }) + }; + + const submitResponse = await ReactNativeBlobUtil.fetch( + 'POST', + `${LNURL_HOST}/lnurl/submitHashes`, + { + 'Content-Type': 'application/json' + }, + JSON.stringify(payload) + ); + + const submitData = submitResponse.json(); + if (!submitData.success) throw submitData.error; + + this.loading = false; + await this.status(); + return { created_at: submitData.created_at }; + } catch (error) { + runInAction(() => { + this.loading = false; + this.error = true; + this.error_msg = error?.toString(); + }); + throw error; + } }; @action @@ -299,233 +242,151 @@ export default class LightningAddressStore { this.error = false; this.error_msg = ''; this.loading = true; - return new Promise((resolve, reject) => { - ReactNativeBlobUtil.fetch( + + try { + const authResponse = await ReactNativeBlobUtil.fetch( 'POST', `${LNURL_HOST}/lnurl/auth`, - { - 'Content-Type': 'application/json' - }, + { 'Content-Type': 'application/json' }, JSON.stringify({ pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey }) - ) - .then((response: any) => { - const status = response.info().status; - const data = response.json(); - if (status == 200) { - const { verification } = data; - const relays_sig = bytesToHex( - schnorr.sign( - hashjs - .sha256() - .update(JSON.stringify(relays)) - .digest('hex'), - nostrPrivateKey - ) - ); + ); - BackendUtils.signMessage(verification) - .then((data: any) => { - const signature = data.zbase || data.signature; - ReactNativeBlobUtil.fetch( - 'POST', - `${LNURL_HOST}/lnurl/create`, - { - 'Content-Type': 'application/json' - }, - JSON.stringify({ - pubkey: this.nodeInfoStore.nodeInfo - .identity_pubkey, - message: verification, - signature, - handle, - domain: 'zeuspay.com', - nostr_pk, - relays, - relays_sig, - request_channels: false // deprecated - }) - ) - .then(async (response: any) => { - const data = response.json(); - const status = response.info().status; - const { - handle, - domain, - created_at, - success - } = data; - - if (status === 200 && success) { - if (handle) { - this.setLightningAddress( - handle, - domain - ); - } - - await this.settingsStore.updateSettings( - { - lightningAddress: { - enabled: true, - automaticallyAccept: - true, - automaticallyRequestOlympusChannels: - false, // deprecated - allowComments: true, - nostrPrivateKey, - nostrRelays: relays, - notifications: 1 - } - } - ); - - // ensure push credentials are in place - // right after creation - this.updatePushCredentials(); - - this.loading = false; - resolve({ - created_at - }); - } else { - this.loading = false; - this.error = true; - this.error_msg = - data?.error?.toString(); - reject(data.error); - } - }) - .catch((error: any) => { - this.loading = false; - this.error = true; - this.error_msg = - error && error.toString(); - reject(error); - }); - }) - .catch((error: any) => { - this.loading = false; - this.error = true; - this.error_msg = error && error.toString(); - reject(error); - }); - } else { - this.loading = false; - this.error = true; - this.error_msg = data?.error?.toString(); - reject(data.error); - } + const authData = authResponse.json(); + if (authResponse.info().status !== 200) throw authData.error; + + const { verification } = authData; + const relays_sig = bytesToHex( + schnorr.sign( + hashjs + .sha256() + .update(JSON.stringify(relays)) + .digest('hex'), + nostrPrivateKey + ) + ); + + const signData = await BackendUtils.signMessage(verification); + const signature = signData.zbase || signData.signature; + + const createResponse = await ReactNativeBlobUtil.fetch( + 'POST', + `${LNURL_HOST}/lnurl/create`, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey, + message: verification, + signature, + handle, + domain: 'zeuspay.com', + nostr_pk, + relays, + relays_sig, + request_channels: false // deprecated }) - .catch((error: any) => { - this.loading = false; - this.error = true; - this.error_msg = error && error.toString(); - reject(error); - }); - }); + ); + + const createData = createResponse.json(); + if (createResponse.info().status !== 200 || !createData.success) { + throw createData.error; + } + + const { handle: responseHandle, domain, created_at } = createData; + + if (responseHandle) { + this.setLightningAddress(responseHandle, domain); + } + + await this.settingsStore.updateSettings({ + lightningAddress: { + enabled: true, + automaticallyAccept: true, + automaticallyRequestOlympusChannels: false, // deprecated + allowComments: true, + nostrPrivateKey, + nostrRelays: relays, + notifications: 1 + } + }); + + runInAction(() => { + // ensure push credentials are in place + // right after creation + this.updatePushCredentials(); + this.loading = false; + }); + + return { created_at }; + } catch (error) { + runInAction(() => { + this.loading = false; + this.error = true; + this.error_msg = error?.toString(); + }); + throw error; + } }; @action - public update = (updates: any) => { + public update = async (updates: any) => { this.error = false; this.error_msg = ''; this.loading = true; - return new Promise((resolve, reject) => { - ReactNativeBlobUtil.fetch( + + try { + const authResponse = await ReactNativeBlobUtil.fetch( 'POST', `${LNURL_HOST}/lnurl/auth`, - { - 'Content-Type': 'application/json' - }, + { 'Content-Type': 'application/json' }, JSON.stringify({ pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey }) - ) - .then((response: any) => { - const status = response.info().status; - const data = response.json(); - if (status == 200) { - const { verification } = data; - BackendUtils.signMessage(verification) - .then((data: any) => { - const signature = data.zbase || data.signature; - ReactNativeBlobUtil.fetch( - 'POST', - `${LNURL_HOST}/lnurl/update`, - { - 'Content-Type': 'application/json' - }, - JSON.stringify({ - pubkey: this.nodeInfoStore.nodeInfo - .identity_pubkey, - message: verification, - signature, - updates - }) - ) - .then((response: any) => { - const data = response.json(); - const status = response.info().status; - const { - handle, - domain, - created_at, - success - } = data; - - if (status === 200 && success) { - if (handle) { - this.setLightningAddress( - handle, - domain || 'zeuspay.com' - ); - } - - this.loading = false; - resolve({ - created_at - }); - } else { - this.loading = false; - this.error = true; - this.error_msg = - data?.error?.toString(); - reject(data.error); - } - }) - .catch((error: any) => { - this.loading = false; - this.error = true; - this.error_msg = - error && error.toString(); - reject(error); - }); - }) - .catch((error: any) => { - this.loading = false; - this.error = true; - this.error_msg = error && error.toString(); - reject(error); - }); - } else { - this.loading = false; - this.error = true; - this.error_msg = data?.error?.toString(); - reject(data.error); - } + ); + + const authData = authResponse.json(); + if (authResponse.info().status !== 200) throw authData.error; + + const { verification } = authData; + const signData = await BackendUtils.signMessage(verification); + const signature = signData.zbase || signData.signature; + + const updateResponse = await ReactNativeBlobUtil.fetch( + 'POST', + `${LNURL_HOST}/lnurl/update`, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey, + message: verification, + signature, + updates }) - .catch((error: any) => { - this.loading = false; - this.error = true; - this.error_msg = error && error.toString(); - reject(error); - }); - }); + ); + + const updateData = updateResponse.json(); + if (updateResponse.info().status !== 200 || !updateData.success) { + throw updateData.error; + } + + const { handle, domain, created_at } = updateData; + + if (handle) { + this.setLightningAddress(handle, domain || 'zeuspay.com'); + } + + this.loading = false; + return { created_at }; + } catch (error) { + runInAction(() => { + this.loading = false; + this.error = true; + this.error_msg = error?.toString(); + }); + throw error; + } }; - enhanceWithFee = (paymentArray: Array) => + private enhanceWithFee = (paymentArray: Array) => paymentArray.map((item: any) => { let fee; try { @@ -543,126 +404,87 @@ export default class LightningAddressStore { @action public status = async (isRedeem?: boolean) => { this.loading = true; - return new Promise((resolve, reject) => { - ReactNativeBlobUtil.fetch( + + try { + const authResponse = await ReactNativeBlobUtil.fetch( 'POST', `${LNURL_HOST}/lnurl/auth`, - { - 'Content-Type': 'application/json' - }, + { 'Content-Type': 'application/json' }, JSON.stringify({ pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey }) - ) - .then((response: any) => { - const status = response.info().status; - const data = response.json(); - if (status == 200) { - const { verification } = data; - BackendUtils.signMessage(verification) - .then((data: any) => { - const signature = data.zbase || data.signature; - ReactNativeBlobUtil.fetch( - 'POST', - `${LNURL_HOST}/lnurl/status`, - { - 'Content-Type': 'application/json' - }, - JSON.stringify({ - pubkey: this.nodeInfoStore.nodeInfo - .identity_pubkey, - message: verification, - signature - }) - ) - .then(async (response: any) => { - const data = response.json(); - const { - results, - success, - paid, - fees, - minimumSats, - handle, - domain - } = data; - - if (status === 200 && success) { - if (!isRedeem) { - this.error = false; - this.error_msg = ''; - } - this.loading = false; - this.availableHashes = results || 0; - await this.getPreimageMap(); - this.paid = - this.enhanceWithFee(paid); - this.fees = fees; - this.minimumSats = minimumSats; - this.lightningAddressHandle = - handle; - this.lightningAddressDomain = - domain; - if (handle && domain) { - this.lightningAddress = `${handle}@${domain}`; - } - - if ( - this.lightningAddress && - this.localHashes === 0 - ) { - this.generatePreimages(true); - } else if ( - this.lightningAddress && - new BigNumber( - this.availableHashes - ).lt(50) - ) { - this.generatePreimages(); - } - resolve({ - results - }); - } else { - this.loading = false; - this.error = true; - this.error_msg = - data?.error?.toString(); - reject(data.error); - } - }) - .catch((error: any) => { - this.loading = false; - this.error = true; - this.error_msg = - error && error.toString(); - reject(error); - }); - }) - .catch((error: any) => { - this.loading = false; - this.error = true; - this.error_msg = error && error.toString(); - reject(error); - }); - } else { - this.loading = false; - this.error = true; - this.error_msg = data?.error?.toString(); - reject(data.error); - } + ); + + const authData = authResponse.json(); + if (authResponse.info().status !== 200) throw authData.error; + + const { verification } = authData; + const signData = await BackendUtils.signMessage(verification); + const signature = signData.zbase || signData.signature; + + const statusResponse = await ReactNativeBlobUtil.fetch( + 'POST', + `${LNURL_HOST}/lnurl/status`, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey, + message: verification, + signature }) - .catch((error: any) => { - this.loading = false; - this.error = true; - this.error_msg = error && error.toString(); - reject(error); - }); - }); + ); + + const statusData = statusResponse.json(); + if (statusResponse.info().status !== 200 || !statusData.success) { + throw statusData.error; + } + + const { results, paid, fees, minimumSats, handle, domain } = + statusData; + + runInAction(() => { + if (!isRedeem) { + this.error = false; + this.error_msg = ''; + } + this.loading = false; + this.availableHashes = results || 0; + }); + + await this.getPreimageMap(); + + runInAction(() => { + this.paid = this.enhanceWithFee(paid); + this.fees = fees; + this.minimumSats = minimumSats; + this.lightningAddressHandle = handle; + this.lightningAddressDomain = domain; + if (handle && domain) { + this.lightningAddress = `${handle}@${domain}`; + } + + if (this.lightningAddress && this.localHashes === 0) { + this.generatePreimages(true); + } else if ( + this.lightningAddress && + new BigNumber(this.availableHashes).lt(50) + ) { + this.generatePreimages(); + } + }); + + return { results }; + } catch (error) { + runInAction(() => { + this.loading = false; + this.error = true; + this.error_msg = error?.toString(); + }); + throw error; + } }; @action - public redeem = async ( + private redeem = async ( hash: string, payReq?: string, preimageNotFound?: boolean @@ -674,90 +496,57 @@ export default class LightningAddressStore { ); return; } + this.error = false; this.error_msg = ''; this.redeeming = true; - return await new Promise((resolve, reject) => { - ReactNativeBlobUtil.fetch( + + try { + const authResponse = await ReactNativeBlobUtil.fetch( 'POST', `${LNURL_HOST}/lnurl/auth`, - { - 'Content-Type': 'application/json' - }, + { 'Content-Type': 'application/json' }, JSON.stringify({ pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey }) - ) - .then((response: any) => { - const status = response.info().status; - const data = response.json(); - if (status == 200) { - const { verification } = data; - - BackendUtils.signMessage(verification) - .then((data: any) => { - const signature = data.zbase || data.signature; - ReactNativeBlobUtil.fetch( - 'POST', - `${LNURL_HOST}/lnurl/redeem`, - { - 'Content-Type': 'application/json' - }, - JSON.stringify({ - pubkey: this.nodeInfoStore.nodeInfo - .identity_pubkey, - message: verification, - signature, - hash, - payReq - }) - ) - .then(async (response: any) => { - const data = response.json(); - const { success } = data; - - if (status === 200 && success) { - this.redeeming = false; - await this.deleteHash(hash); - resolve({ - success - }); - } else { - this.redeeming = false; - this.error = true; - this.error_msg = - data?.error?.toString(); - reject(data.error); - } - }) - .catch((error: any) => { - this.redeeming = false; - this.error = true; - this.error_msg = - error && error.toString(); - reject(error); - }); - }) - .catch((error: any) => { - this.redeeming = false; - this.error = true; - this.error_msg = error && error.toString(); - reject(error); - }); - } else { - this.redeeming = false; - this.error = true; - this.error_msg = data?.error?.toString(); - reject(data.error); - } + ); + + const authData = authResponse.json(); + if (authResponse.info().status !== 200) throw authData.error; + + const { verification } = authData; + const signData = await BackendUtils.signMessage(verification); + const signature = signData.zbase || signData.signature; + + const redeemResponse = await ReactNativeBlobUtil.fetch( + 'POST', + `${LNURL_HOST}/lnurl/redeem`, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + pubkey: this.nodeInfoStore.nodeInfo.identity_pubkey, + message: verification, + signature, + hash, + payReq }) - .catch((error: any) => { - this.redeeming = false; - this.error = true; - this.error_msg = error && error.toString(); - reject(error); - }); - }); + ); + + const redeemData = redeemResponse.json(); + if (redeemResponse.info().status !== 200 || !redeemData.success) { + throw redeemData.error; + } + + this.redeeming = false; + await this.deleteHash(hash); + return { success: redeemData.success }; + } catch (error) { + runInAction(() => { + this.redeeming = false; + this.error = true; + this.error_msg = error?.toString(); + }); + throw error; + } }; @action @@ -828,7 +617,7 @@ export default class LightningAddressStore { }; }; - calculateFeeMsat = (amountMsat: string | number) => { + private calculateFeeMsat = (amountMsat: string | number) => { let feeMsat; for (let i = this.fees.length - 1; i >= 0; i--) { const feeItem = this.fees[i]; @@ -864,7 +653,7 @@ export default class LightningAddressStore { } }; - analyzeAttestation = ( + private analyzeAttestation = ( attestation: any, hash: string, amountMsat: string | number @@ -923,12 +712,8 @@ export default class LightningAddressStore { return attestation; }; - @action - public setDeviceToken = (token: string) => { - this.deviceToken = token; - }; + public setDeviceToken = (token: string) => (this.deviceToken = token); - @action public updatePushCredentials = async () => { const DEVICE_TOKEN_KEY = 'zeus-notification-device-token'; const token = await EncryptedStorage.getItem(DEVICE_TOKEN_KEY); @@ -1001,7 +786,8 @@ export default class LightningAddressStore { result.payment_request, preimageNotFound ).then((success) => { - if (success === true) fireLocalNotification(); + if (success?.success === true) + fireLocalNotification(); if (!skipStatus) this.status(true); return; }); @@ -1019,7 +805,7 @@ export default class LightningAddressStore { result.payment_request, preimageNotFound ).then((success) => { - if (success === true) + if (success?.success === true) fireLocalNotification(); if (!skipStatus) this.status(true); return; @@ -1033,7 +819,8 @@ export default class LightningAddressStore { undefined, preimageNotFound ).then((success) => { - if (success === true) fireLocalNotification(); + if (success?.success === true) + fireLocalNotification(); if (!skipStatus) this.status(true); return; }); @@ -1082,12 +869,13 @@ export default class LightningAddressStore { }); } } - this.status(true); - this.redeemingAll = false; + runInAction(() => { + this.status(true); + this.redeemingAll = false; + }); }; - @action - public subscribeUpdates = () => { + private subscribeUpdates = () => { if (this.socket) return; ReactNativeBlobUtil.fetch( 'POST', @@ -1148,12 +936,12 @@ export default class LightningAddressStore { comment ); }) - .catch((e) => { + .catch((e) => console.log( 'Error looking up attestation', e - ); - }); + ) + ); } }); }); @@ -1161,7 +949,6 @@ export default class LightningAddressStore { }); }; - @action public prepareToAutomaticallyAccept = async () => { this.prepareToAutomaticallyAcceptStart = true; @@ -1169,9 +956,11 @@ export default class LightningAddressStore { const isReady = await this.nodeInfoStore.isLightningReadyToReceive(); if (isReady) { - this.readyToAutomaticallyAccept = true; - this.redeemAllOpenPayments(); - this.subscribeUpdates(); + runInAction(() => { + this.readyToAutomaticallyAccept = true; + this.redeemAllOpenPayments(); + this.subscribeUpdates(); + }); } await sleep(3000); } diff --git a/stores/LnurlPayStore.ts b/stores/LnurlPayStore.ts index 79345621d..a1b7ce3ad 100644 --- a/stores/LnurlPayStore.ts +++ b/stores/LnurlPayStore.ts @@ -1,4 +1,4 @@ -import { action } from 'mobx'; +import { action, runInAction } from 'mobx'; import { LNURLPaySuccessAction } from 'js-lnurl'; import EncryptedStorage from 'react-native-encrypted-storage'; import { schnorr } from '@noble/curves/secp256k1'; @@ -57,7 +57,8 @@ export default class LnurlPayStore { this.nodeInfoStore = nodeInfoStore; } - reset = () => { + @action + public reset = () => { this.paymentHash = undefined; this.domain = undefined; this.successAction = undefined; @@ -69,7 +70,6 @@ export default class LnurlPayStore { this.paymentRequest = undefined; }; - @action public load = async (paymentHash: string): Promise => { let lnurlpaytx: any = await EncryptedStorage.getItem( 'lnurlpay:' + paymentHash @@ -87,7 +87,6 @@ export default class LnurlPayStore { return lnurlpaytx; }; - @action public keep = async ( paymentHash: string, domain: string, @@ -127,44 +126,45 @@ export default class LnurlPayStore { JSON.stringify(metadataEntry) ); - this.paymentHash = paymentHash; - this.successAction = successAction; - this.domain = domain; - - if (pr) this.paymentRequest = pr; - - // Zaplocker - if (user_pubkey) { - this.isZaplocker = true; - try { - this.zaplockerNpub = nip19.npubEncode(user_pubkey); - } catch (e) {} - - if (pmthash_sig) { - const pmtHashBytes = hexToBytes(pmthash_sig); - this.isPmtHashSigValid = schnorr.verify( - pmtHashBytes, - paymentHash, - user_pubkey - ); + runInAction(() => { + this.paymentHash = paymentHash; + this.successAction = successAction; + this.domain = domain; + + if (pr) this.paymentRequest = pr; + + // Zaplocker + if (user_pubkey) { + this.isZaplocker = true; + try { + this.zaplockerNpub = nip19.npubEncode(user_pubkey); + } catch (e) {} + + if (pmthash_sig) { + const pmtHashBytes = hexToBytes(pmthash_sig); + this.isPmtHashSigValid = schnorr.verify( + pmtHashBytes, + paymentHash, + user_pubkey + ); + } + + if (relays && relays_sig) { + this.relays = relays; + const relaysBytes = hexToBytes(relays_sig); + this.isRelaysSigValid = schnorr.verify( + relaysBytes, + hashjs + .sha256() + .update(JSON.stringify(relays)) + .digest('hex'), + user_pubkey + ); + } } - - if (relays && relays_sig) { - this.relays = relays; - const relaysBytes = hexToBytes(relays_sig); - this.isRelaysSigValid = schnorr.verify( - relaysBytes, - hashjs - .sha256() - .update(JSON.stringify(relays)) - .digest('hex'), - user_pubkey - ); - } - } + }); }; - @action public broadcastAttestation = async () => { const hash = this.paymentHash; const invoice = this.paymentRequest; diff --git a/stores/NodeInfoStore.ts b/stores/NodeInfoStore.ts index c8b62a84b..2de873526 100644 --- a/stores/NodeInfoStore.ts +++ b/stores/NodeInfoStore.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, runInAction } from 'mobx'; import NetworkInfo from '../models/NetworkInfo'; import NodeInfo from '../models/NodeInfo'; import ChannelsStore from './ChannelsStore'; @@ -24,7 +24,8 @@ export default class NodeInfoStore { this.settingsStore = settingsStore; } - reset = () => { + @action + public reset = () => { this.error = false; this.loading = false; this.nodeInfo = {}; @@ -34,24 +35,19 @@ export default class NodeInfoStore { }; @action - getNodeInfoError = () => { + private getNodeInfoError = () => { this.error = true; this.loading = false; this.nodeInfo = {}; }; @action - getNetworkInfoError = () => { + private getNetworkInfoError = () => { this.error = true; this.loading = false; this.networkInfo = {}; }; - @action - setLoading = () => { - this.loading = true; - }; - private currentRequest: any; @action @@ -66,11 +62,13 @@ export default class NodeInfoStore { return; } const nodeInfo = new NodeInfo(data); - this.nodeInfo = nodeInfo; - this.testnet = nodeInfo.isTestNet; - this.regtest = nodeInfo.isRegTest; - this.loading = false; - this.error = false; + runInAction(() => { + this.nodeInfo = nodeInfo; + this.testnet = nodeInfo.isTestNet; + this.regtest = nodeInfo.isRegTest; + this.loading = false; + this.error = false; + }); resolve(nodeInfo); }) .catch((error: any) => { @@ -78,9 +76,10 @@ export default class NodeInfoStore { resolve('Old getNodeInfo call'); return; } - // handle error - this.errorMsg = errorToUserFriendly(error.toString()); - this.getNodeInfoError(); + runInAction(() => { + this.errorMsg = errorToUserFriendly(error.toString()); + this.getNodeInfoError(); + }); resolve(error); }); }); @@ -92,19 +91,21 @@ export default class NodeInfoStore { this.loading = true; return BackendUtils.getNetworkInfo() .then((data: any) => { - this.networkInfo = new NetworkInfo(data); - this.loading = false; - this.error = false; + runInAction(() => { + this.networkInfo = new NetworkInfo(data); + this.loading = false; + this.error = false; + }); return this.networkInfo; }) .catch((error: any) => { - // handle error - this.errorMsg = errorToUserFriendly(error.toString()); - this.getNetworkInfoError(); + runInAction(() => { + this.errorMsg = errorToUserFriendly(error.toString()); + this.getNetworkInfoError(); + }); }); }; - @action public isLightningReadyToSend = async () => { await this.channelsStore.getChannels(); await this.getNodeInfo(); @@ -118,7 +119,6 @@ export default class NodeInfoStore { ); }; - @action public isLightningReadyToReceive = async () => { await this.channelsStore.getChannels(); let syncedToChain = this.nodeInfo?.synced_to_chain; @@ -135,7 +135,6 @@ export default class NodeInfoStore { ); }; - @action public lspNotConfigured = () => { const { implementation, certVerification } = this.settingsStore; diff --git a/stores/NotesStore.ts b/stores/NotesStore.ts index f88236050..46a7afd21 100644 --- a/stores/NotesStore.ts +++ b/stores/NotesStore.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, runInAction } from 'mobx'; import EncryptedStorage from 'react-native-encrypted-storage'; const NOTES_KEY = 'note-Keys'; @@ -33,7 +33,6 @@ export default class NotesStore { } }; - @action public async loadNoteKeys() { console.log('Loading notes...'); try { @@ -41,14 +40,17 @@ export default class NotesStore { if (storedKeys) { this.noteKeys = JSON.parse(storedKeys); // Load all notes - await Promise.all( + const loadedNotes = await Promise.all( this.noteKeys.map(async (key) => { const note = await EncryptedStorage.getItem(key); - if (note) { - this.notes[key] = note; - } + return { key, note }; }) ); + runInAction(() => { + loadedNotes + .filter((n) => n.note) + .forEach(({ key, note }) => (this.notes[key] = note!)); + }); } } catch (error) { console.error( @@ -58,7 +60,7 @@ export default class NotesStore { } } - writeNoteKeysToLocalStorage = async () => { + private writeNoteKeysToLocalStorage = async () => { try { await EncryptedStorage.setItem( NOTES_KEY, diff --git a/stores/OffersStore.ts b/stores/OffersStore.ts index 95a49470e..e099b5de9 100644 --- a/stores/OffersStore.ts +++ b/stores/OffersStore.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, runInAction } from 'mobx'; import BackendUtils from '../utils/BackendUtils'; import { errorToUserFriendly } from '../utils/ErrorUtils'; @@ -9,13 +9,6 @@ export default class OffersStore { @observable public error: boolean = false; @observable public error_msg: string; - @action - public reset = () => { - this.offers = []; - this.loading = false; - this.error = false; - }; - @action public listOffers = async () => { this.loading = true; @@ -23,13 +16,17 @@ export default class OffersStore { await BackendUtils.listOffers() .then((data: any) => { - this.offers = data.offers; - this.loading = false; + runInAction(() => { + this.offers = data.offers; + this.loading = false; + }); }) .catch(() => { - this.offers = []; - this.error = true; - this.loading = false; + runInAction(() => { + this.offers = []; + this.error = true; + this.loading = false; + }); }); }; @@ -45,9 +42,11 @@ export default class OffersStore { return data; }) .catch((e: any) => { - this.error = true; - this.error_msg = errorToUserFriendly(e); - this.loading = false; + runInAction(() => { + this.error = true; + this.error_msg = errorToUserFriendly(e); + this.loading = false; + }); }); }; @@ -63,9 +62,11 @@ export default class OffersStore { return data; }) .catch((e: any) => { - this.error = true; - this.error_msg = errorToUserFriendly(e); - this.loading = false; + runInAction(() => { + this.error = true; + this.error_msg = errorToUserFriendly(e); + this.loading = false; + }); }); }; } diff --git a/stores/PaymentsStore.ts b/stores/PaymentsStore.ts index 5c13c4bfa..334780bb4 100644 --- a/stores/PaymentsStore.ts +++ b/stores/PaymentsStore.ts @@ -1,5 +1,5 @@ //PaymentStore.tsx -import { action, observable } from 'mobx'; +import { action, observable, runInAction } from 'mobx'; import Payment from './../models/Payment'; import SettingsStore from './SettingsStore'; import ChannelsStore from './ChannelsStore'; @@ -18,31 +18,27 @@ export default class PaymentsStore { this.channelsStore = channelsStore; } - reset = () => { - this.resetPayments(); - this.error = false; - this.error_msg = ''; - }; - - resetPayments = () => { + @action + private resetPayments = () => { this.payments = []; this.loading = false; }; - @action public getPayments = async () => { this.loading = true; try { const data = await BackendUtils.getPayments(); const payments = data.payments; - this.payments = payments - .slice() - .reverse() - .map( - (payment: any) => - new Payment(payment, this.channelsStore.nodes) - ); - this.loading = false; + runInAction(() => { + this.payments = payments + .slice() + .reverse() + .map( + (payment: any) => + new Payment(payment, this.channelsStore.nodes) + ); + this.loading = false; + }); return this.payments; } catch (error) { this.resetPayments(); diff --git a/stores/PosStore.ts b/stores/PosStore.ts index e149f0431..a9eea7818 100644 --- a/stores/PosStore.ts +++ b/stores/PosStore.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, runInAction } from 'mobx'; import EncryptedStorage from 'react-native-encrypted-storage'; import ReactNativeBlobUtil from 'react-native-blob-util'; import BigNumber from 'bignumber.js'; @@ -74,7 +74,6 @@ export default class PosStore { ); }; - @action public recordPayment = ({ orderId, orderTotal, @@ -99,29 +98,20 @@ export default class PosStore { }) ); - @action public clearCurrentOrder = () => (this.currentOrder = null); - @action public createCurrentOrder = (currency: string) => { this.currentOrder = new Order({ id: uuidv4(), created_at: new Date(Date.now()).toISOString(), updated_at: new Date(Date.now()).toISOString(), line_items: [], - total_tax_money: { - amount: 0, - currency - }, - total_money: { - amount: 0, - currency - } + total_tax_money: { amount: 0, currency }, + total_money: { amount: 0, currency } }); }; - @action - calcFiatAmountFromSats = (amount: string | number) => { + private calcFiatAmountFromSats = (amount: string | number) => { const { fiatRates } = this.fiatStore; const { settings } = this.settingsStore; const { fiat } = settings; @@ -148,7 +138,7 @@ export default class PosStore { return fiatAmount; }; - calcSatsAmountFromFiat = (amount: string | number) => { + private calcSatsAmountFromFiat = (amount: string | number) => { const { fiatRates } = this.fiatStore; const { settings } = this.settingsStore; const { fiat } = settings; @@ -174,7 +164,8 @@ export default class PosStore { return satsAmount; }; - @action recalculateCurrentOrder = () => { + @action + public recalculateCurrentOrder = () => { if (this.currentOrder) { let totalFiat = new BigNumber(0); let totalSats = new BigNumber(0); @@ -223,7 +214,6 @@ export default class PosStore { } }; - @action public saveStandaloneOrder = async (updateOrder: Order) => { const order = this.openOrders.find((o) => o.id === updateOrder.id); @@ -243,16 +233,13 @@ export default class PosStore { this.clearCurrentOrder(); }; - @action public getOrders = async () => { switch (this.settingsStore.settings.pos.posEnabled) { case PosEnabled.Square: - this.getSquareOrders(); - break; + return this.getSquareOrders(); case PosEnabled.Standalone: - this.getStandaloneOrders(); - break; + return this.getStandaloneOrders(); default: this.resetOrders(); @@ -261,7 +248,7 @@ export default class PosStore { }; @action - public getStandaloneOrders = async () => { + private getStandaloneOrders = async () => { this.loading = true; this.error = false; @@ -297,16 +284,18 @@ export default class PosStore { return order.payment; }); - this.openOrders = openOrders; - this.filteredOpenOrders = openOrders; - this.paidOrders = paidOrders; - this.filteredPaidOrders = paidOrders; + runInAction(() => { + this.openOrders = openOrders; + this.filteredOpenOrders = openOrders; + this.paidOrders = paidOrders; + this.filteredPaidOrders = paidOrders; - this.loading = false; + this.loading = false; + }); }; @action - public getSquareOrders = async () => { + private getSquareOrders = async () => { const { squareAccessToken, squareLocationId, squareDevMode } = this.settingsStore.settings.pos; this.loading = true; @@ -382,22 +371,29 @@ export default class PosStore { return order.payment; }); - this.openOrders = openOrders; - this.filteredOpenOrders = openOrders; - this.paidOrders = paidOrders; - this.filteredPaidOrders = paidOrders; + runInAction(() => { + this.openOrders = openOrders; + this.filteredOpenOrders = openOrders; + this.paidOrders = paidOrders; + this.filteredPaidOrders = paidOrders; + }); } else { - this.openOrders = []; - this.paidOrders = []; - this.loading = false; - this.error = true; + runInAction(() => { + this.openOrders = []; + this.paidOrders = []; + this.loading = false; + this.error = true; + }); } }) .catch((err) => { console.error('POS get orders err', err); - this.openOrders = []; - this.paidOrders = []; - this.loading = false; + runInAction(() => { + this.openOrders = []; + this.paidOrders = []; + this.loading = false; + this.error = true; + }); }); }; @@ -526,21 +522,28 @@ export default class PosStore { }) ); - this.completedOrders = enrichedOrders; - this.reconTotal = total.toFixed(2); - this.reconTax = tax.toFixed(2); - this.reconTips = tips.toFixed(2); - this.reconExport = exportString; + runInAction(() => { + this.completedOrders = enrichedOrders; + this.reconTotal = total.toFixed(2); + this.reconTax = tax.toFixed(2); + this.reconTips = tips.toFixed(2); + this.reconExport = exportString; + }); } else { - this.completedOrders = []; - this.loading = false; - this.error = true; + runInAction(() => { + this.completedOrders = []; + this.loading = false; + this.error = true; + }); } }) .catch((err) => { console.error('POS get historical orders err', err); - this.completedOrders = []; - this.loading = false; + runInAction(() => { + this.completedOrders = []; + this.loading = false; + this.error = true; + }); }); }; @@ -555,7 +558,8 @@ export default class PosStore { } }; - resetOrders = () => { + @action + private resetOrders = () => { this.openOrders = []; this.paidOrders = []; this.loading = false; diff --git a/stores/SettingsStore.ts b/stores/SettingsStore.ts index e66d23ba9..71cd6c736 100644 --- a/stores/SettingsStore.ts +++ b/stores/SettingsStore.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, runInAction } from 'mobx'; import { BiometryType } from 'react-native-biometrics'; import ReactNativeBlobUtil from 'react-native-blob-util'; import EncryptedStorage from 'react-native-encrypted-storage'; @@ -1251,28 +1251,18 @@ export default class SettingsStore { @observable public embeddedLndNetwork: string; @observable public initialStart: boolean = true; - @action public setInitialStart = (status: boolean) => { this.initialStart = status; }; - @action - public changeLocale = (locale: string) => { - this.settings.locale = locale; - }; - - @action public fetchBTCPayConfig = (data: string) => { const configRoute = data.split('config=')[1]; this.btcPayError = null; if (configRoute.includes('.onion')) { return doTorRequest(configRoute, RequestMethod.GET) - .then((response: any) => { - return this.parseBTCPayConfig(response); - }) + .then((response: any) => this.parseBTCPayConfig(response)) .catch((err: any) => { - // handle error this.btcPayError = `${localeString( 'stores.SettingsStore.btcPayFetchConfigError' )}: ${err.toString()}`; @@ -1291,7 +1281,6 @@ export default class SettingsStore { } }) .catch((err: any) => { - // handle error this.btcPayError = `${localeString( 'stores.SettingsStore.btcPayFetchConfigError' )}: ${err.toString()}`; @@ -1311,55 +1300,61 @@ export default class SettingsStore { if (this.enableTor) { return doTorRequest(olympiansRoute, RequestMethod.GET) .then((response: any) => { - this.olympians = response.olympians; - this.gods = response.gods; - this.mortals = response.mortals; - this.loading = false; + runInAction(() => { + this.olympians = response.olympians; + this.gods = response.gods; + this.mortals = response.mortals; + this.loading = false; + }); }) .catch((err: any) => { - // handle error - this.olympians = []; - this.gods = []; - this.mortals = []; - this.loading = false; - this.sponsorsError = `${localeString( - 'stores.SettingsStore.olympianFetchError' - )}: ${err.toString()}`; + runInAction(() => { + this.olympians = []; + this.gods = []; + this.mortals = []; + this.loading = false; + this.sponsorsError = `${localeString( + 'stores.SettingsStore.olympianFetchError' + )}: ${err.toString()}`; + }); }); } else { return ReactNativeBlobUtil.fetch('get', olympiansRoute) .then((response: any) => { const status = response.info().status; - if (status == 200) { - const data = response.json(); - this.olympians = data.olympians; - this.gods = data.gods; - this.mortals = data.mortals; - this.loading = false; - } else { + runInAction(() => { + if (status == 200) { + const data = response.json(); + this.olympians = data.olympians; + this.gods = data.gods; + this.mortals = data.mortals; + this.loading = false; + } else { + this.olympians = []; + this.gods = []; + this.mortals = []; + this.loading = false; + this.sponsorsError = localeString( + 'stores.SettingsStore.olympianFetchError' + ); + } + }); + }) + .catch((err: any) => { + runInAction(() => { this.olympians = []; this.gods = []; this.mortals = []; this.loading = false; - this.sponsorsError = localeString( + this.sponsorsError = `${localeString( 'stores.SettingsStore.olympianFetchError' - ); - } - }) - .catch((err: any) => { - // handle error - this.olympians = []; - this.gods = []; - this.mortals = []; - this.loading = false; - this.sponsorsError = `${localeString( - 'stores.SettingsStore.olympianFetchError' - )}: ${err.toString()}`; + )}: ${err.toString()}`; + }); }); } }; - parseBTCPayConfig(data: any) { + private parseBTCPayConfig(data: any) { const configuration = data.configurations[0]; const { adminMacaroon, macaroon, type, uri } = configuration; @@ -1379,11 +1374,10 @@ export default class SettingsStore { } } - hasCredentials() { + public hasCredentials() { return this.macaroonHex || this.accessKey ? true : false; } - @action public async getSettings(silentUpdate: boolean = false) { if (!silentUpdate) this.loading = true; try { @@ -1606,33 +1600,36 @@ export default class SettingsStore { this.settings = newSettings; } - const node: any = - newSettings.nodes?.length && - newSettings.nodes[newSettings.selectedNode || 0]; - if (node) { - this.host = node.host; - this.port = node.port; - this.url = node.url; - this.username = node.username; - this.password = node.password; - this.lndhubUrl = node.lndhubUrl; - this.macaroonHex = node.macaroonHex; - this.rune = node.rune; - this.accessKey = node.accessKey; - this.dismissCustodialWarning = node.dismissCustodialWarning; - this.implementation = node.implementation || 'lnd'; - this.certVerification = node.certVerification || false; - this.enableTor = node.enableTor; - // LNC - this.pairingPhrase = node.pairingPhrase; - this.mailboxServer = node.mailboxServer; - this.customMailboxServer = node.customMailboxServer; - // Embeded lnd - this.seedPhrase = node.seedPhrase; - this.walletPassword = node.walletPassword; - this.adminMacaroon = node.adminMacaroon; - this.embeddedLndNetwork = node.embeddedLndNetwork; - } + runInAction(() => { + const node: any = + newSettings.nodes?.length && + newSettings.nodes[newSettings.selectedNode || 0]; + if (node) { + this.host = node.host; + this.port = node.port; + this.url = node.url; + this.username = node.username; + this.password = node.password; + this.lndhubUrl = node.lndhubUrl; + this.macaroonHex = node.macaroonHex; + this.rune = node.rune; + this.accessKey = node.accessKey; + this.dismissCustodialWarning = + node.dismissCustodialWarning; + this.implementation = node.implementation || 'lnd'; + this.certVerification = node.certVerification || false; + this.enableTor = node.enableTor; + // LNC + this.pairingPhrase = node.pairingPhrase; + this.mailboxServer = node.mailboxServer; + this.customMailboxServer = node.customMailboxServer; + // Embeded lnd + this.seedPhrase = node.seedPhrase; + this.walletPassword = node.walletPassword; + this.adminMacaroon = node.adminMacaroon; + this.embeddedLndNetwork = node.embeddedLndNetwork; + } + }); } else { console.log('No settings stored'); } @@ -1645,15 +1642,13 @@ export default class SettingsStore { return this.settings; } - @action - public async setSettings(settings: string) { + private async setSettings(settings: string) { this.loading = true; await EncryptedStorage.setItem(STORAGE_KEY, settings); this.loading = false; return settings; } - @action public updateSettings = async (newSetting: any) => { const existingSettings = await this.getSettings(); const newSettings = { @@ -1686,25 +1681,30 @@ export default class SettingsStore { if (enableTor) { return doTorRequest(url, RequestMethod.POST) .then((response: any) => { - this.loading = false; - if (response.error) { - this.createAccountError = - response.message || - localeString('stores.SettingsStore.lndhubError'); - } else { - this.createAccountSuccess = localeString( - 'stores.SettingsStore.lndhubSuccess' - ); - } + runInAction(() => { + this.loading = false; + if (response.error) { + this.createAccountError = + response.message || + localeString( + 'stores.SettingsStore.lndhubError' + ); + } else { + this.createAccountSuccess = localeString( + 'stores.SettingsStore.lndhubSuccess' + ); + } + }); return response; }) .catch((err: any) => { - // handle error const errorString = err.error || err.toString(); - this.loading = false; - this.createAccountError = `${localeString( - 'stores.SettingsStore.lndhubError' - )}: ${errorString}`; + runInAction(() => { + this.loading = false; + this.createAccountError = `${localeString( + 'stores.SettingsStore.lndhubError' + )}: ${errorString}`; + }); }); } else { return ReactNativeBlobUtil.config({ @@ -1715,35 +1715,39 @@ export default class SettingsStore { const status = response.info().status; if (status == 200) { const data = response.json(); - this.loading = false; - if (data.error) { - this.createAccountError = - data.message || - localeString( - 'stores.SettingsStore.lndhubError' + runInAction(() => { + this.loading = false; + if (data.error) { + this.createAccountError = + data.message || + localeString( + 'stores.SettingsStore.lndhubError' + ); + } else { + this.createAccountSuccess = localeString( + 'stores.SettingsStore.lndhubSuccess' ); - } else { - this.createAccountSuccess = localeString( - 'stores.SettingsStore.lndhubSuccess' - ); - } + } + }); return data; } else { - // handle error - this.loading = false; - this.createAccountError = localeString( - 'stores.SettingsStore.lndhubError' - ); + runInAction(() => { + this.loading = false; + this.createAccountError = localeString( + 'stores.SettingsStore.lndhubError' + ); + }); } }) .catch((err: any) => { - // handle error const errorString = err.error || err.toString(); - this.loading = false; - this.createAccountError = `${localeString( - 'stores.SettingsStore.lndhubError' - )}: ${errorString}`; + runInAction(() => { + this.loading = false; + this.createAccountError = `${localeString( + 'stores.SettingsStore.lndhubError' + )}: ${errorString}`; + }); }); } }; @@ -1762,25 +1766,27 @@ export default class SettingsStore { password: request.password }) .then((data: any) => { - this.loading = false; - this.accessToken = data.access_token; - this.refreshToken = data.refresh_token; + runInAction(() => { + this.loading = false; + this.accessToken = data.access_token; + this.refreshToken = data.refresh_token; + }); resolve(data); }) .catch(() => { - // handle error - this.loading = false; - this.error = true; - this.errorMsg = localeString( - 'stores.SettingsStore.lndhubLoginError' - ); + runInAction(() => { + this.loading = false; + this.error = true; + this.errorMsg = localeString( + 'stores.SettingsStore.lndhubLoginError' + ); + }); resolve(); }); }); }; // LNC - @action public connect = async () => { this.loading = true; @@ -1788,8 +1794,10 @@ export default class SettingsStore { const error = await BackendUtils.connect(); if (error) { - this.error = true; - this.errorMsg = error; + runInAction(() => { + this.error = true; + this.errorMsg = error; + }); return error; } @@ -1805,11 +1813,13 @@ export default class SettingsStore { resolve(); } else if (counter > 20) { clearInterval(interval); - this.error = true; - this.errorMsg = localeString( - 'stores.SettingsStore.lncConnectError' - ); - this.loading = false; + runInAction(() => { + this.error = true; + this.errorMsg = localeString( + 'stores.SettingsStore.lncConnectError' + ); + this.loading = false; + }); resolve(this.errorMsg); } }, 500); @@ -1842,10 +1852,7 @@ export default class SettingsStore { this.settings.isBiometryEnabled && this.settings.supportedBiometryType !== undefined; - @action - public setLoginStatus = (status = false) => { - this.loggedIn = status; - }; + public setLoginStatus = (status = false) => (this.loggedIn = status); @action public setConnectingStatus = (status = false) => { @@ -1870,7 +1877,6 @@ export default class SettingsStore { }, 3000); }; - @action public setPosStatus = (setting: string) => { this.posStatus = setting; return this.posStatus; diff --git a/stores/SyncStore.ts b/stores/SyncStore.ts index f62da1b0f..483597a8a 100644 --- a/stores/SyncStore.ts +++ b/stores/SyncStore.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, runInAction } from 'mobx'; import ReactNativeBlobUtil from 'react-native-blob-util'; import BackendUtils from '../utils/BackendUtils'; @@ -33,11 +33,10 @@ export default class SyncStore { this.error = false; }; - setExpressGraphSyncStatus = (syncing: boolean) => { - this.isInExpressGraphSync = syncing; - }; + public setExpressGraphSyncStatus = (syncing: boolean) => + (this.isInExpressGraphSync = syncing); - setSyncInfo = async () => { + private setSyncInfo = async () => { const nodeInfo = this.nodeInfo; if (this.currentBlockHeight !== nodeInfo?.block_height) { @@ -60,22 +59,20 @@ export default class SyncStore { return; }; - getNodeInfo = () => { - return BackendUtils.getMyNodeInfo().then((data: any) => { - const nodeInfo = new NodeInfo(data); - this.nodeInfo = nodeInfo; - return nodeInfo; - }); - }; + private getNodeInfo = () => + BackendUtils.getMyNodeInfo().then( + (data: any) => (this.nodeInfo = new NodeInfo(data)) + ); - updateProgress = () => { + @action + private updateProgress = () => { this.currentProgress = (this.currentBlockHeight ?? 0) - (this.bestBlockHeight ?? 0); this.numBlocksUntilSynced = (this.bestBlockHeight ?? 0) - this.currentBlockHeight; }; - getBestBlockHeight = async () => { + private getBestBlockHeight = async () => { await new Promise((resolve, reject) => { ReactNativeBlobUtil.fetch( 'get', @@ -141,7 +138,6 @@ export default class SyncStore { } }; - @action public checkRecoveryStatus = () => { BackendUtils.getRecoveryInfo().then((data: any) => { if (data.recovery_mode && !data.recovery_finished) { @@ -150,27 +146,27 @@ export default class SyncStore { }); }; - @action - public getRecoveryStatus = async () => { + private getRecoveryStatus = async () => { await BackendUtils.getRecoveryInfo().then((data: any) => { - if (data.recovery_mode) { - if (data.progress) { - this.recoveryProgress = data.progress; - } - if (data.recovery_finished) { + runInAction(() => { + if (data.recovery_mode) { + if (data.progress) { + this.recoveryProgress = data.progress; + } + if (data.recovery_finished) { + this.isRecovering = false; + this.recoveryProgress = null; + } + } else { this.isRecovering = false; this.recoveryProgress = null; } - } else { - this.isRecovering = false; - this.recoveryProgress = null; - } + }); return data; }); }; - @action - public startRecovering = async () => { + private startRecovering = async () => { this.isRecovering = true; while (this.isRecovering) { diff --git a/stores/TransactionsStore.ts b/stores/TransactionsStore.ts index e9ad568f9..2bcc966f3 100644 --- a/stores/TransactionsStore.ts +++ b/stores/TransactionsStore.ts @@ -1,6 +1,6 @@ const bitcoin = require('bitcoinjs-lib'); -import { action, reaction, observable } from 'mobx'; +import { action, reaction, observable, runInAction } from 'mobx'; import { randomBytes } from 'react-native-randombytes'; import { sha256 } from 'js-sha256'; import ReactNativeBlobUtil from 'react-native-blob-util'; @@ -87,7 +87,8 @@ export default class TransactionsStore { ); } - reset = () => { + @action + public reset = () => { this.loading = false; this.error = false; this.error_msg = null; @@ -107,21 +108,23 @@ export default class TransactionsStore { this.funded_psbt = ''; }; - @action public getTransactions = async () => { this.loading = true; await BackendUtils.getTransactions() .then((data: any) => { - this.transactions = data.transactions - .slice() - .reverse() - .map((tx: any) => new Transaction(tx)); - this.loading = false; + runInAction(() => { + this.transactions = data.transactions + .slice() + .reverse() + .map((tx: any) => new Transaction(tx)); + this.loading = false; + }); }) .catch(() => { - // handle error - this.transactions = []; - this.loading = false; + runInAction(() => { + this.transactions = []; + this.loading = false; + }); }); }; @@ -145,24 +148,29 @@ export default class TransactionsStore { tx_hex }) .then((data: any) => { - if (data.publish_error) { - this.error_msg = errorToUserFriendly(data.publish_error); - this.error = true; - this.loading = false; - } else { - this.txid = txid; - this.publishSuccess = true; - this.loading = false; - this.channelsStore.resetOpenChannel(); - } + runInAction(() => { + if (data.publish_error) { + this.error_msg = errorToUserFriendly( + data.publish_error + ); + this.error = true; + this.loading = false; + } else { + this.txid = txid; + this.publishSuccess = true; + this.loading = false; + this.channelsStore.resetOpenChannel(); + } + }); }) .catch((error: any) => { - // handle error - this.error_msg = errorToUserFriendly( - error.publish_error || error.message - ); - this.error = true; - this.loading = false; + runInAction(() => { + this.error_msg = errorToUserFriendly( + error.publish_error || error.message + ); + this.error = true; + this.loading = false; + }); }); }; @@ -176,16 +184,13 @@ export default class TransactionsStore { if (defaultAccount) { return BackendUtils.finalizePsbt({ funded_psbt }) - .then((data: any) => { - const raw_final_tx = data.raw_final_tx; - - this.broadcast(raw_final_tx); - }) + .then((data: any) => this.broadcast(data.raw_final_tx)) .catch((error: any) => { - // handle error - this.error_msg = errorToUserFriendly(error.message); - this.error = true; - this.loading = false; + runInAction(() => { + this.error_msg = errorToUserFriendly(error.message); + this.error = true; + this.loading = false; + }); }); } else { return new Promise((resolve) => { @@ -209,12 +214,13 @@ export default class TransactionsStore { resolve(true); } catch (error: any) { - // handle error - this.error_msg = errorToUserFriendly( - error?.message || error - ); - this.error = true; - this.loading = false; + runInAction(() => { + this.error_msg = errorToUserFriendly( + error?.message || error + ); + this.error = true; + this.loading = false; + }); resolve(true); } @@ -222,7 +228,6 @@ export default class TransactionsStore { } }; - @action public finalizePsbtAndBroadcastChannel = async ( signed_psbt: string, pending_chan_ids: Array @@ -236,36 +241,40 @@ export default class TransactionsStore { } }) .then((data: any) => { - if (data.publish_error) { - this.error_msg = errorToUserFriendly(data.publish_error); - this.error = true; - this.loading = false; - } else { - try { - // Parse the PSBT - const psbt = bitcoin.Psbt.fromBase64(signed_psbt); - - // Extract the finalized transaction from the PSBT - const finalizedTx = psbt.extractTransaction(); - - // Serialize the transaction and calculate its hash to obtain the txid - const txid = finalizedTx.getId(); - this.txid = txid; - } catch (e) {} - this.publishSuccess = true; - this.loading = false; - this.channelsStore.resetOpenChannel(); - } + runInAction(() => { + if (data.publish_error) { + this.error_msg = errorToUserFriendly( + data.publish_error + ); + this.error = true; + this.loading = false; + } else { + try { + // Parse the PSBT + const psbt = bitcoin.Psbt.fromBase64(signed_psbt); + + // Extract the finalized transaction from the PSBT + const finalizedTx = psbt.extractTransaction(); + + // Serialize the transaction and calculate its hash to obtain the txid + const txid = finalizedTx.getId(); + this.txid = txid; + } catch (e) {} + this.publishSuccess = true; + this.loading = false; + this.channelsStore.resetOpenChannel(); + } + }); }) .catch((error: any) => { - // handle error - this.error_msg = errorToUserFriendly(error.message); - this.error = true; - this.loading = false; + runInAction(() => { + this.error_msg = errorToUserFriendly(error.message); + this.error = true; + this.loading = false; + }); }); }; - @action public finalizeTxHexAndBroadcastChannel = async ( tx_hex: string, pending_chan_ids: Array @@ -279,33 +288,38 @@ export default class TransactionsStore { } }) .then((data: any) => { - if (data.publish_error) { - this.error_msg = errorToUserFriendly(data.publish_error); - this.error = true; - this.loading = false; - } else { - try { - // Parse the tx - const tx = bitcoin.Transaction.fromHex(tx_hex); - - // Serialize the transaction and calculate its hash - const txid = tx.getId(); - this.txid = txid; - } catch (e) {} - this.publishSuccess = true; - this.loading = false; - this.channelsStore.resetOpenChannel(); - } + runInAction(() => { + if (data.publish_error) { + this.error_msg = errorToUserFriendly( + data.publish_error + ); + this.error = true; + this.loading = false; + } else { + try { + // Parse the tx + const tx = bitcoin.Transaction.fromHex(tx_hex); + + // Serialize the transaction and calculate its hash + const txid = tx.getId(); + this.txid = txid; + } catch (e) {} + this.publishSuccess = true; + this.loading = false; + this.channelsStore.resetOpenChannel(); + } + }); }) .catch((error: any) => { - // handle error - this.error_msg = errorToUserFriendly(error.message); - this.error = true; - this.loading = false; + runInAction(() => { + this.error_msg = errorToUserFriendly(error.message); + this.error = true; + this.loading = false; + }); }); }; - public sendCoinsLNDCoinControl = ( + private sendCoinsLNDCoinControl = ( transactionRequest: TransactionRequest, defaultAccount?: boolean ) => { @@ -349,24 +363,30 @@ export default class TransactionsStore { BackendUtils.fundPsbt(fundPsbtRequest) .then((data: any) => { - this.crafting = false; - const funded_psbt: string = new FundedPsbt( - data.funded_psbt - ).getFormatted(); - - if (account !== 'default') { - this.funded_psbt = funded_psbt; - this.loading = false; - } else { - this.finalizePsbtAndBroadcast(funded_psbt, defaultAccount); - } + runInAction(() => { + this.crafting = false; + const funded_psbt: string = new FundedPsbt( + data.funded_psbt + ).getFormatted(); + + if (account !== 'default') { + this.funded_psbt = funded_psbt; + this.loading = false; + } else { + this.finalizePsbtAndBroadcast( + funded_psbt, + defaultAccount + ); + } + }); }) .catch((error: any) => { - // handle error - this.error_msg = errorToUserFriendly(error.message); - this.error = true; - this.crafting = false; - this.loading = false; + runInAction(() => { + this.error_msg = errorToUserFriendly(error.message); + this.error = true; + this.crafting = false; + this.loading = false; + }); }); }; @@ -414,15 +434,18 @@ export default class TransactionsStore { BackendUtils.sendCoins(transactionRequest) .then((data: any) => { - this.txid = data.txid; - this.publishSuccess = true; - this.loading = false; + runInAction(() => { + this.txid = data.txid; + this.publishSuccess = true; + this.loading = false; + }); }) .catch((error: Error) => { - // handle error - this.error_msg = errorToUserFriendly(error); - this.error = true; - this.loading = false; + runInAction(() => { + this.error_msg = errorToUserFriendly(error); + this.error = true; + this.loading = false; + }); }); }; @@ -603,14 +626,14 @@ export default class TransactionsStore { errorToUserFriendly(err) || localeString('error.sendingPayment'); }; - @action resetBroadcast = () => { + @action + public resetBroadcast = () => { this.error = true; this.loading = false; this.broadcast_txid = ''; this.broadcast_err = null; }; - @action public broadcastRawTxToMempoolSpace = (raw_tx_hex: string) => { this.resetBroadcast(); const headers = { @@ -627,21 +650,26 @@ export default class TransactionsStore { ) .then((response: any) => { const status = response.info().status; + const data = response.data; if (status == 200) { - const data = response.data; - this.loading = false; - this.broadcast_txid = data; + runInAction(() => { + this.loading = false; + this.broadcast_txid = data; + }); return data; } else { - const data = response.data; - this.broadcast_err = data; - this.loading = false; - this.error = true; + runInAction(() => { + this.broadcast_err = data; + this.loading = false; + this.error = true; + }); } }) .catch((err) => { - this.broadcast_err = err.error || err.toString(); - this.loading = false; + runInAction(() => { + this.broadcast_err = err.error || err.toString(); + this.loading = false; + }); }); }; } diff --git a/stores/UTXOsStore.ts b/stores/UTXOsStore.ts index 010fe5eb2..10652108d 100644 --- a/stores/UTXOsStore.ts +++ b/stores/UTXOsStore.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, runInAction } from 'mobx'; import EncryptedStorage from 'react-native-encrypted-storage'; import SettingsStore from './SettingsStore'; @@ -56,7 +56,8 @@ export default class UTXOsStore { this.loadingAddressesError = ''; }; - getUtxosError = () => { + @action + private getUtxosError = () => { this.error = true; this.loading = false; this.utxos = []; @@ -78,15 +79,18 @@ export default class UTXOsStore { BackendUtils.getUTXOs(data) .then((data: any) => { - this.loading = false; - const utxos = data.utxos || data.outputs; - this.utxos = utxos.map((utxo: any) => new Utxo(utxo)); - this.error = false; + runInAction(() => { + this.loading = false; + const utxos = data.utxos || data.outputs; + this.utxos = utxos.map((utxo: any) => new Utxo(utxo)); + this.error = false; + }); }) .catch((error: any) => { - // handle error - this.errorMsg = error.toString(); - this.getUtxosError(); + runInAction(() => { + this.errorMsg = error.toString(); + this.getUtxosError(); + }); }); }; @@ -149,8 +153,11 @@ export default class UTXOsStore { console.log('Error loading hidden account list:', error); } - this.errorMsg = ''; - this.loadingAccounts = true; + runInAction(() => { + this.errorMsg = ''; + this.loadingAccounts = true; + }); + return BackendUtils.listAccounts(data) .then(async (data: any) => { const accounts: any = []; @@ -180,15 +187,18 @@ export default class UTXOsStore { }); } } - this.accounts = accounts; - this.loadingAccounts = false; - this.error = false; + runInAction(() => { + this.accounts = accounts; + this.loadingAccounts = false; + this.error = false; + }); return this.accounts; }) .catch((error: any) => { - // handle error - this.errorMsg = error.toString(); - this.getUtxosError(); + runInAction(() => { + this.errorMsg = error.toString(); + this.getUtxosError(); + }); }); }; @@ -265,25 +275,30 @@ export default class UTXOsStore { }); } - this.importingAccount = false; - this.error = false; - this.success = true; + runInAction(() => { + this.importingAccount = false; + this.error = false; + this.success = true; + }); return; } else { - this.importingAccount = false; - this.error = false; - this.accountToImport = response; + runInAction(() => { + this.importingAccount = false; + this.error = false; + this.accountToImport = response; + }); return this.accountToImport; } }) .catch((error: any) => { - // handle error - this.errorMsg = error.toString(); - this.success = false; - this.accountToImport = null; - this.importingAccount = false; - this.start_height = undefined; - this.getUtxosError(); + runInAction(() => { + this.errorMsg = error.toString(); + this.success = false; + this.accountToImport = null; + this.importingAccount = false; + this.start_height = undefined; + this.getUtxosError(); + }); }); }; @@ -302,8 +317,10 @@ export default class UTXOsStore { }) .catch((err: Error) => { console.log('rescan err', err); - this.attemptingRescan = false; - this.rescanErrorMsg = err.toString(); + runInAction(() => { + this.attemptingRescan = false; + this.rescanErrorMsg = err.toString(); + }); return; }); }; @@ -317,14 +334,18 @@ export default class UTXOsStore { return await new Promise((resolve, reject) => { BackendUtils.listAddresses() .then((response: any) => { - this.accountsWithAddresses = - response.account_with_addresses; - this.loadingAddresses = false; + runInAction(() => { + this.accountsWithAddresses = + response.account_with_addresses; + this.loadingAddresses = false; + }); resolve(this.accountsWithAddresses); }) .catch((err: Error) => { - this.loadingAddressesError = err.toString(); - this.loadingAddresses = false; + runInAction(() => { + this.loadingAddressesError = err.toString(); + this.loadingAddresses = false; + }); reject(); }); }); diff --git a/stores/UnitsStore.ts b/stores/UnitsStore.ts index 5cfca5ec6..79b47661a 100644 --- a/stores/UnitsStore.ts +++ b/stores/UnitsStore.ts @@ -37,12 +37,11 @@ export default class UnitsStore { this.getUnits(); } - getUnits = async () => { + private getUnits = async () => { const units = await EncryptedStorage.getItem(UNIT_KEY); if (units) this.units = units; }; - @action public changeUnits = async () => { this.units = this.getNextUnit(); await EncryptedStorage.setItem(UNIT_KEY, this.units); @@ -66,12 +65,10 @@ export default class UnitsStore { } }; - @action - public resetUnits = () => { - this.units = 'sats'; - }; + public resetUnits = () => (this.units = 'sats'); - @action getUnformattedAmount = ( + @action + public getUnformattedAmount = ( value: string | number = 0, fixedUnits?: string ): ValueDisplayProps => { diff --git a/views/Wallet/Wallet.tsx b/views/Wallet/Wallet.tsx index bb145ba10..b70529150 100644 --- a/views/Wallet/Wallet.tsx +++ b/views/Wallet/Wallet.tsx @@ -55,6 +55,7 @@ import AlertStore from '../../stores/AlertStore'; import BalanceStore from '../../stores/BalanceStore'; import ChannelBackupStore from '../../stores/ChannelBackupStore'; import ChannelsStore from '../../stores/ChannelsStore'; +import TransactionsStore from '../../stores/TransactionsStore'; import FiatStore from '../../stores/FiatStore'; import LightningAddressStore from '../../stores/LightningAddressStore'; import LnurlPayStore from '../../stores/LnurlPayStore'; @@ -89,6 +90,7 @@ interface WalletProps { AlertStore: AlertStore; BalanceStore: BalanceStore; ChannelsStore: ChannelsStore; + TransactionsStore: TransactionsStore; NodeInfoStore: NodeInfoStore; SettingsStore: SettingsStore; UnitsStore: UnitsStore; @@ -114,6 +116,7 @@ interface WalletState { 'AlertStore', 'BalanceStore', 'ChannelsStore', + 'TransactionsStore', 'NodeInfoStore', 'SettingsStore', 'UnitsStore', @@ -300,6 +303,7 @@ export default class Wallet extends React.Component { NodeInfoStore, BalanceStore, ChannelsStore, + TransactionsStore, UTXOsStore, ContactStore, SettingsStore, @@ -347,6 +351,7 @@ export default class Wallet extends React.Component { NodeInfoStore.reset(); BalanceStore.reset(); ChannelsStore.reset(); + TransactionsStore.reset(); SyncStore.reset(); LightningAddressStore.reset(); LSPStore.reset();