From bec42fe42d08833871dc61cce8f3766547ca6e0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Rodriguez=20V=C3=A9lez?= Date: Sun, 28 Jan 2024 11:11:08 -0400 Subject: [PATCH] REF: Currency module to TS (#6052) --- App.js | 4 +- BlueApp.js | 4 +- blue_modules/currency.js | 261 -------------------- blue_modules/currency.ts | 230 +++++++++++++++++ blue_modules/storage-context.js | 4 +- components/AmountInput.js | 34 +-- components/TransactionsNavigationHeader.tsx | 10 +- loc/index.ts | 8 +- screen/lnd/ldkOpenChannel.tsx | 10 +- screen/lnd/lndCreateInvoice.js | 8 +- screen/lnd/lnurlPay.js | 10 +- screen/lnd/scanLndInvoice.js | 6 +- screen/receive/details.js | 12 +- screen/send/confirm.js | 8 +- screen/send/create.js | 4 +- screen/send/details.js | 22 +- screen/send/psbtMultisig.js | 8 +- screen/settings/Currency.tsx | 27 +- tests/integration/Currency.test.js | 49 ++-- tests/unit/addresses.test.js | 2 +- tests/unit/currency.test.js | 51 ++-- tests/unit/loc.test.js | 22 +- 22 files changed, 392 insertions(+), 402 deletions(-) delete mode 100644 blue_modules/currency.js create mode 100644 blue_modules/currency.ts diff --git a/App.js b/App.js index 52bce5062..5e69409b2 100644 --- a/App.js +++ b/App.js @@ -33,8 +33,8 @@ import HandoffComponent from './components/handoff'; import Privacy from './blue_modules/Privacy'; import triggerHapticFeedback, { HapticFeedbackTypes } from './blue_modules/hapticFeedback'; import MenuElements from './components/MenuElements'; +import { updateExchangeRate } from './blue_modules/currency'; const A = require('./blue_modules/analytics'); -const currency = require('./blue_modules/currency'); const eventEmitter = Platform.OS === 'ios' ? new NativeEventEmitter(NativeModules.EventEmitter) : undefined; const { EventEmitter, SplashScreen } = NativeModules; @@ -203,7 +203,7 @@ const App = () => { if (wallets.length === 0) return; if ((appState.current.match(/background/) && nextAppState === 'active') || nextAppState === undefined) { setTimeout(() => A(A.ENUM.APP_UNSUSPENDED), 2000); - currency.updateExchangeRate(); + updateExchangeRate(); const processed = await processPushNotifications(); if (processed) return; const clipboard = await BlueClipboard().getClipboardContent(); diff --git a/BlueApp.js b/BlueApp.js index c0ee48a83..d7d0577cf 100644 --- a/BlueApp.js +++ b/BlueApp.js @@ -25,6 +25,7 @@ import { } from './class/'; import { randomBytes } from './class/rng'; import alert from './components/Alert'; +import { initCurrencyDaemon } from './blue_modules/currency'; const encryption = require('./blue_modules/encryption'); const Realm = require('realm'); @@ -32,7 +33,6 @@ const createHash = require('create-hash'); let usedBucketNum = false; let savingInProgress = 0; // its both a flag and a counter of attempts to write to disk const prompt = require('./helpers/prompt'); -const currency = require('./blue_modules/currency'); const BlueElectrum = require('./blue_modules/BlueElectrum'); BlueElectrum.connectMain(); @@ -939,6 +939,6 @@ const startAndDecrypt = async retry => { BlueApp.startAndDecrypt = startAndDecrypt; BlueApp.AppStorage = AppStorage; -currency.init(); +initCurrencyDaemon(); module.exports = BlueApp; diff --git a/blue_modules/currency.js b/blue_modules/currency.js deleted file mode 100644 index 037669619..000000000 --- a/blue_modules/currency.js +++ /dev/null @@ -1,261 +0,0 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; -import DefaultPreference from 'react-native-default-preference'; -import * as RNLocalize from 'react-native-localize'; -import BigNumber from 'bignumber.js'; -import { FiatUnit, getFiatRate } from '../models/fiatUnit'; -import WidgetCommunication from './WidgetCommunication'; - -const PREFERRED_CURRENCY_STORAGE_KEY = 'preferredCurrency'; -const EXCHANGE_RATES_STORAGE_KEY = 'currency'; - -let preferredFiatCurrency = FiatUnit.USD; -let exchangeRates = { LAST_UPDATED_ERROR: false }; -let lastTimeUpdateExchangeRateWasCalled = 0; -let skipUpdateExchangeRate = false; - -const LAST_UPDATED = 'LAST_UPDATED'; - -/** - * Saves to storage preferred currency, whole object - * from `./models/fiatUnit` - * - * @param item {Object} one of the values in `./models/fiatUnit` - * @returns {Promise} - */ -async function setPrefferedCurrency(item) { - await AsyncStorage.setItem(PREFERRED_CURRENCY_STORAGE_KEY, JSON.stringify(item)); - await DefaultPreference.setName('group.io.bluewallet.bluewallet'); - await DefaultPreference.set('preferredCurrency', item.endPointKey); - await DefaultPreference.set('preferredCurrencyLocale', item.locale.replace('-', '_')); - WidgetCommunication.reloadAllTimelines(); -} - -async function getPreferredCurrency() { - const preferredCurrency = await JSON.parse(await AsyncStorage.getItem(PREFERRED_CURRENCY_STORAGE_KEY)); - await DefaultPreference.setName('group.io.bluewallet.bluewallet'); - await DefaultPreference.set('preferredCurrency', preferredCurrency.endPointKey); - await DefaultPreference.set('preferredCurrencyLocale', preferredCurrency.locale.replace('-', '_')); - return preferredCurrency; -} - -async function _restoreSavedExchangeRatesFromStorage() { - try { - exchangeRates = JSON.parse(await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY)); - if (!exchangeRates) exchangeRates = { LAST_UPDATED_ERROR: false }; - } catch (_) { - exchangeRates = { LAST_UPDATED_ERROR: false }; - } -} - -async function _restoreSavedPreferredFiatCurrencyFromStorage() { - try { - preferredFiatCurrency = JSON.parse(await AsyncStorage.getItem(PREFERRED_CURRENCY_STORAGE_KEY)); - if (preferredFiatCurrency === null) { - throw Error('No Preferred Fiat selected'); - } - - preferredFiatCurrency = FiatUnit[preferredFiatCurrency.endPointKey] || preferredFiatCurrency; - // ^^^ in case configuration in json file changed (and is different from what we stored) we reload it - } catch (_) { - const deviceCurrencies = RNLocalize.getCurrencies(); - if (Object.keys(FiatUnit).some(unit => unit === deviceCurrencies[0])) { - preferredFiatCurrency = FiatUnit[deviceCurrencies[0]]; - } else { - preferredFiatCurrency = FiatUnit.USD; - } - } -} - -/** - * actual function to reach api and get fresh currency exchange rate. checks LAST_UPDATED time and skips entirely - * if called too soon (30min); saves exchange rate (with LAST_UPDATED info) to storage. - * should be called when app thinks its a good time to refresh exchange rate - * - * @return {Promise} - */ -async function updateExchangeRate() { - if (skipUpdateExchangeRate) return; - if (+new Date() - lastTimeUpdateExchangeRateWasCalled <= 10 * 1000) { - // simple debounce so theres no race conditions - return; - } - lastTimeUpdateExchangeRateWasCalled = +new Date(); - - if (+new Date() - exchangeRates[LAST_UPDATED] <= 30 * 60 * 1000) { - // not updating too often - return; - } - console.log('updating exchange rate...'); - - let rate; - try { - rate = await getFiatRate(preferredFiatCurrency.endPointKey); - exchangeRates[LAST_UPDATED] = +new Date(); - exchangeRates['BTC_' + preferredFiatCurrency.endPointKey] = rate; - exchangeRates.LAST_UPDATED_ERROR = false; - await AsyncStorage.setItem(EXCHANGE_RATES_STORAGE_KEY, JSON.stringify(exchangeRates)); - } catch (Err) { - console.log('Error encountered when attempting to update exchange rate...'); - console.warn(Err.message); - rate = JSON.parse(await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY)); - rate = rate || {}; // init if its null - rate.LAST_UPDATED_ERROR = true; - exchangeRates.LAST_UPDATED_ERROR = true; - await AsyncStorage.setItem(EXCHANGE_RATES_STORAGE_KEY, JSON.stringify(rate)); - throw Err; - } -} - -async function isRateOutdated() { - try { - const rate = JSON.parse(await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY)); - return rate.LAST_UPDATED_ERROR || +new Date() - rate.LAST_UPDATED >= 31 * 60 * 1000; - } catch { - return true; - } -} - -/** - * this function reads storage and restores current preferred fiat currency & last saved exchange rate, then calls - * updateExchangeRate() to update rates. - * should be called when the app starts and when user changes preferred fiat (with TRUE argument so underlying - * `updateExchangeRate()` would actually update rates via api). - * - * @param clearLastUpdatedTime {boolean} set to TRUE for the underlying - * - * @return {Promise} - */ -async function init(clearLastUpdatedTime = false) { - await _restoreSavedExchangeRatesFromStorage(); - await _restoreSavedPreferredFiatCurrencyFromStorage(); - - if (clearLastUpdatedTime) { - exchangeRates[LAST_UPDATED] = 0; - lastTimeUpdateExchangeRateWasCalled = 0; - } - - return updateExchangeRate(); -} - -function satoshiToLocalCurrency(satoshi, format = true) { - if (!exchangeRates['BTC_' + preferredFiatCurrency.endPointKey]) { - updateExchangeRate(); - return '...'; - } - - let b = new BigNumber(satoshi).dividedBy(100000000).multipliedBy(exchangeRates['BTC_' + preferredFiatCurrency.endPointKey]); - - if (b.isGreaterThanOrEqualTo(0.005) || b.isLessThanOrEqualTo(-0.005)) { - b = b.toFixed(2); - } else { - b = b.toPrecision(2); - } - - if (format === false) return b; - - let formatter; - try { - formatter = new Intl.NumberFormat(preferredFiatCurrency.locale, { - style: 'currency', - currency: preferredFiatCurrency.endPointKey, - minimumFractionDigits: 2, - maximumFractionDigits: 8, - }); - } catch (error) { - console.warn(error); - console.log(error); - formatter = new Intl.NumberFormat(FiatUnit.USD.locale, { - style: 'currency', - currency: preferredFiatCurrency.endPointKey, - minimumFractionDigits: 2, - maximumFractionDigits: 8, - }); - } - - return formatter.format(b); -} - -function BTCToLocalCurrency(bitcoin) { - let sat = new BigNumber(bitcoin); - sat = sat.multipliedBy(100000000).toNumber(); - - return satoshiToLocalCurrency(sat); -} - -async function mostRecentFetchedRate() { - const currencyInformation = JSON.parse(await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY)); - - const formatter = new Intl.NumberFormat(preferredFiatCurrency.locale, { - style: 'currency', - currency: preferredFiatCurrency.endPointKey, - }); - return { - LastUpdated: currencyInformation[LAST_UPDATED], - Rate: formatter.format(currencyInformation[`BTC_${preferredFiatCurrency.endPointKey}`]), - }; -} - -function satoshiToBTC(satoshi) { - let b = new BigNumber(satoshi); - b = b.dividedBy(100000000); - return b.toString(10); -} - -function btcToSatoshi(btc) { - return new BigNumber(btc).multipliedBy(100000000).toNumber(); -} - -function fiatToBTC(fiatFloat) { - let b = new BigNumber(fiatFloat); - b = b.dividedBy(exchangeRates['BTC_' + preferredFiatCurrency.endPointKey]).toFixed(8); - return b; -} - -function getCurrencySymbol() { - return preferredFiatCurrency.symbol; -} - -/** - * Used to mock data in tests - * - * @param {object} currency, one of FiatUnit.* - */ -function _setPreferredFiatCurrency(currency) { - preferredFiatCurrency = currency; -} - -/** - * Used to mock data in tests - * - * @param {string} pair as expected by rest of this module, e.g 'BTC_JPY' or 'BTC_USD' - * @param {number} rate exchange rate - */ -function _setExchangeRate(pair, rate) { - exchangeRates[pair] = rate; -} - -/** - * Used in unit tests, so the `currency` module wont launch actual http request - */ -function _setSkipUpdateExchangeRate() { - skipUpdateExchangeRate = true; -} - -module.exports.updateExchangeRate = updateExchangeRate; -module.exports.init = init; -module.exports.satoshiToLocalCurrency = satoshiToLocalCurrency; -module.exports.fiatToBTC = fiatToBTC; -module.exports.satoshiToBTC = satoshiToBTC; -module.exports.BTCToLocalCurrency = BTCToLocalCurrency; -module.exports.setPrefferedCurrency = setPrefferedCurrency; -module.exports.getPreferredCurrency = getPreferredCurrency; -module.exports.btcToSatoshi = btcToSatoshi; -module.exports.getCurrencySymbol = getCurrencySymbol; -module.exports._setPreferredFiatCurrency = _setPreferredFiatCurrency; // export it to mock data in tests -module.exports._setExchangeRate = _setExchangeRate; // export it to mock data in tests -module.exports._setSkipUpdateExchangeRate = _setSkipUpdateExchangeRate; // export it to mock data in tests -module.exports.PREFERRED_CURRENCY = PREFERRED_CURRENCY_STORAGE_KEY; -module.exports.EXCHANGE_RATES = EXCHANGE_RATES_STORAGE_KEY; -module.exports.LAST_UPDATED = LAST_UPDATED; -module.exports.mostRecentFetchedRate = mostRecentFetchedRate; -module.exports.isRateOutdated = isRateOutdated; diff --git a/blue_modules/currency.ts b/blue_modules/currency.ts new file mode 100644 index 000000000..aa7e4d101 --- /dev/null +++ b/blue_modules/currency.ts @@ -0,0 +1,230 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import DefaultPreference from 'react-native-default-preference'; +import * as RNLocalize from 'react-native-localize'; +import BigNumber from 'bignumber.js'; +import { FiatUnit, FiatUnitType, getFiatRate } from '../models/fiatUnit'; +import WidgetCommunication from './WidgetCommunication'; + +const PREFERRED_CURRENCY_STORAGE_KEY = 'preferredCurrency'; +const EXCHANGE_RATES_STORAGE_KEY = 'exchangeRates'; +const LAST_UPDATED = 'LAST_UPDATED'; +const GROUP_IO_BLUEWALLET = 'group.io.bluewallet.bluewallet'; +const BTC_PREFIX = 'BTC_'; + +export interface CurrencyRate { + LastUpdated: Date | null; + Rate: number | string | null; +} + +interface ExchangeRates { + [key: string]: number | boolean | undefined; + LAST_UPDATED_ERROR: boolean; +} + +let preferredFiatCurrency: FiatUnitType = FiatUnit.USD; +let exchangeRates: ExchangeRates = { LAST_UPDATED_ERROR: false }; +let lastTimeUpdateExchangeRateWasCalled: number = 0; +let skipUpdateExchangeRate: boolean = false; + +async function setPreferredCurrency(item: FiatUnitType): Promise { + await AsyncStorage.setItem(PREFERRED_CURRENCY_STORAGE_KEY, JSON.stringify(item)); + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + await DefaultPreference.set(PREFERRED_CURRENCY_STORAGE_KEY, item.endPointKey); + await DefaultPreference.set('preferredCurrencyLocale', item.locale.replace('-', '_')); + // @ts-ignore: Convert to TSX later + WidgetCommunication.reloadAllTimelines(); +} + +async function getPreferredCurrency(): Promise { + const preferredCurrency = JSON.parse((await AsyncStorage.getItem(PREFERRED_CURRENCY_STORAGE_KEY)) || '{}'); + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + await DefaultPreference.set(PREFERRED_CURRENCY_STORAGE_KEY, preferredCurrency.endPointKey); + await DefaultPreference.set('preferredCurrencyLocale', preferredCurrency.locale.replace('-', '_')); + return preferredCurrency; +} + +async function _restoreSavedExchangeRatesFromStorage(): Promise { + try { + const rates = await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY); + exchangeRates = rates ? JSON.parse(rates) : { LAST_UPDATED_ERROR: false }; + } catch (_) { + exchangeRates = { LAST_UPDATED_ERROR: false }; + } +} + +async function _restoreSavedPreferredFiatCurrencyFromStorage(): Promise { + try { + const storedCurrency = await AsyncStorage.getItem(PREFERRED_CURRENCY_STORAGE_KEY); + if (!storedCurrency) throw new Error('No Preferred Fiat selected'); + preferredFiatCurrency = JSON.parse(storedCurrency); + if (!FiatUnit[preferredFiatCurrency.endPointKey]) { + throw new Error('Invalid Fiat Unit'); + } + } catch (_) { + const deviceCurrencies = RNLocalize.getCurrencies(); + preferredFiatCurrency = deviceCurrencies[0] && FiatUnit[deviceCurrencies[0]] ? FiatUnit[deviceCurrencies[0]] : FiatUnit.USD; + } +} + +async function updateExchangeRate(): Promise { + if (skipUpdateExchangeRate) return; + if (Date.now() - lastTimeUpdateExchangeRateWasCalled <= 10000) { + // simple debounce so there's no race conditions + return; + } + lastTimeUpdateExchangeRateWasCalled = Date.now(); + + const lastUpdated = exchangeRates[LAST_UPDATED] as number | undefined; + if (lastUpdated && Date.now() - lastUpdated <= 30 * 60 * 1000) { + // not updating too often + return; + } + console.log('updating exchange rate...'); + + try { + const rate = await getFiatRate(preferredFiatCurrency.endPointKey); + exchangeRates[LAST_UPDATED] = Date.now(); + exchangeRates[BTC_PREFIX + preferredFiatCurrency.endPointKey] = rate; + exchangeRates.LAST_UPDATED_ERROR = false; + await AsyncStorage.setItem(EXCHANGE_RATES_STORAGE_KEY, JSON.stringify(exchangeRates)); + } catch (error) { + console.error('Error encountered when attempting to update exchange rate...', error); + const rate = JSON.parse((await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY)) || '{}'); + rate.LAST_UPDATED_ERROR = true; + exchangeRates.LAST_UPDATED_ERROR = true; + await AsyncStorage.setItem(EXCHANGE_RATES_STORAGE_KEY, JSON.stringify(rate)); + } +} + +async function isRateOutdated(): Promise { + try { + const rate = JSON.parse((await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY)) || '{}'); + return rate.LAST_UPDATED_ERROR || Date.now() - (rate[LAST_UPDATED] || 0) >= 31 * 60 * 1000; + } catch { + return true; + } +} + +async function initCurrencyDaemon(clearLastUpdatedTime: boolean = false): Promise { + await _restoreSavedExchangeRatesFromStorage(); + await _restoreSavedPreferredFiatCurrencyFromStorage(); + + if (clearLastUpdatedTime) { + exchangeRates[LAST_UPDATED] = 0; + lastTimeUpdateExchangeRateWasCalled = 0; + } + + await updateExchangeRate(); +} + +function satoshiToLocalCurrency(satoshi: number, format: boolean = true): string { + const exchangeRateKey = BTC_PREFIX + preferredFiatCurrency.endPointKey; + const exchangeRate = exchangeRates[exchangeRateKey]; + + if (typeof exchangeRate !== 'number') { + updateExchangeRate(); + return '...'; + } + + const btcAmount = new BigNumber(satoshi).dividedBy(100000000); + const convertedAmount = btcAmount.multipliedBy(exchangeRate); + let formattedAmount: string; + + if (convertedAmount.isGreaterThanOrEqualTo(0.005) || convertedAmount.isLessThanOrEqualTo(-0.005)) { + formattedAmount = convertedAmount.toFixed(2); + } else { + formattedAmount = convertedAmount.toPrecision(2); + } + + if (format === false) return formattedAmount; + + try { + const formatter = new Intl.NumberFormat(preferredFiatCurrency.locale, { + style: 'currency', + currency: preferredFiatCurrency.endPointKey, + minimumFractionDigits: 2, + maximumFractionDigits: 8, + }); + return formatter.format(Number(formattedAmount)); + } catch (error) { + console.warn(error); + return formattedAmount; + } +} + +function BTCToLocalCurrency(bitcoin: BigNumber.Value): string { + const sat = new BigNumber(bitcoin).multipliedBy(100000000).toNumber(); + return satoshiToLocalCurrency(sat); +} + +async function mostRecentFetchedRate(): Promise { + const currencyInformation = JSON.parse((await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY)) || '{}'); + + const formatter = new Intl.NumberFormat(preferredFiatCurrency.locale, { + style: 'currency', + currency: preferredFiatCurrency.endPointKey, + }); + + const rate = currencyInformation[BTC_PREFIX + preferredFiatCurrency.endPointKey]; + return { + LastUpdated: currencyInformation[LAST_UPDATED], + Rate: rate ? formatter.format(rate) : '...', + }; +} + +function satoshiToBTC(satoshi: number): string { + return new BigNumber(satoshi).dividedBy(100000000).toString(10); +} + +function btcToSatoshi(btc: BigNumber.Value): number { + return new BigNumber(btc).multipliedBy(100000000).toNumber(); +} + +function fiatToBTC(fiatFloat: number): string { + const exchangeRateKey = BTC_PREFIX + preferredFiatCurrency.endPointKey; + const exchangeRate = exchangeRates[exchangeRateKey]; + + if (typeof exchangeRate !== 'number') { + throw new Error('Exchange rate not available'); + } + + const btcAmount = new BigNumber(fiatFloat).dividedBy(exchangeRate); + return btcAmount.toFixed(8); +} + +function getCurrencySymbol(): string { + return preferredFiatCurrency.symbol; +} + +function _setPreferredFiatCurrency(currency: FiatUnitType): void { + preferredFiatCurrency = currency; +} + +function _setExchangeRate(pair: string, rate: number): void { + exchangeRates[pair] = rate; +} + +function _setSkipUpdateExchangeRate(): void { + skipUpdateExchangeRate = true; +} + +export { + updateExchangeRate, + initCurrencyDaemon, + satoshiToLocalCurrency, + fiatToBTC, + satoshiToBTC, + BTCToLocalCurrency, + setPreferredCurrency, + getPreferredCurrency, + btcToSatoshi, + getCurrencySymbol, + _setPreferredFiatCurrency, + _setExchangeRate, + _setSkipUpdateExchangeRate, + PREFERRED_CURRENCY_STORAGE_KEY, + EXCHANGE_RATES_STORAGE_KEY, + LAST_UPDATED, + mostRecentFetchedRate, + isRateOutdated, +}; diff --git a/blue_modules/storage-context.js b/blue_modules/storage-context.js index d1e8e912b..ff2a86b31 100644 --- a/blue_modules/storage-context.js +++ b/blue_modules/storage-context.js @@ -7,9 +7,9 @@ import loc, { STORAGE_KEY as LOC_STORAGE_KEY } from '../loc'; import { LegacyWallet, WatchOnlyWallet } from '../class'; import alert from '../components/Alert'; import triggerHapticFeedback, { HapticFeedbackTypes } from './hapticFeedback'; +import { PREFERRED_CURRENCY_STORAGE_KEY } from './currency'; const BlueApp = require('../BlueApp'); const BlueElectrum = require('./BlueElectrum'); -const currency = require('../blue_modules/currency'); const A = require('../blue_modules/analytics'); const _lastTimeTriedToRefetchWallet = {}; // hashmap of timestamps we _started_ refetching some wallet @@ -23,7 +23,7 @@ export const BlueStorageProvider = ({ children }) => { const [walletsInitialized, setWalletsInitialized] = useState(false); const [preferredFiatCurrency, _setPreferredFiatCurrency] = useState(FiatUnit.USD); const [language, _setLanguage] = useState(); - const getPreferredCurrencyAsyncStorage = useAsyncStorage(currency.PREFERRED_CURRENCY).getItem; + const getPreferredCurrencyAsyncStorage = useAsyncStorage(PREFERRED_CURRENCY_STORAGE_KEY).getItem; const getLanguageAsyncStorage = useAsyncStorage(LOC_STORAGE_KEY).getItem; const [isHandOffUseEnabled, setIsHandOffUseEnabled] = useState(false); const [isElectrumDisabled, setIsElectrumDisabled] = useState(true); diff --git a/components/AmountInput.js b/components/AmountInput.js index 07fbaff0f..6ecb95d1f 100644 --- a/components/AmountInput.js +++ b/components/AmountInput.js @@ -10,7 +10,14 @@ import loc, { formatBalanceWithoutSuffix, formatBalancePlain, removeTrailingZero import { BlueText } from '../BlueComponents'; import dayjs from 'dayjs'; import { useTheme } from './themes'; -const currency = require('../blue_modules/currency'); +import { + fiatToBTC, + getCurrencySymbol, + isRateOutdated, + mostRecentFetchedRate, + satoshiToBTC, + updateExchangeRate, +} from '../blue_modules/currency'; dayjs.extend(require('dayjs/plugin/localizedFormat')); class AmountInput extends Component { @@ -57,13 +64,12 @@ class AmountInput extends Component { } componentDidMount() { - currency - .mostRecentFetchedRate() - .then(mostRecentFetchedRate => { - this.setState({ mostRecentFetchedRate }); + mostRecentFetchedRate() + .then(mostRecentFetchedRateValue => { + this.setState({ mostRecentFetchedRate: mostRecentFetchedRateValue }); }) .finally(() => { - currency.isRateOutdated().then(isRateOutdated => this.setState({ isRateOutdated })); + isRateOutdated().then(isRateOutdatedValue => this.setState({ isRateOutdated: isRateOutdatedValue })); }); } @@ -86,7 +92,7 @@ class AmountInput extends Component { sats = amount; break; case BitcoinUnit.LOCAL_CURRENCY: - sats = new BigNumber(currency.fiatToBTC(amount)).multipliedBy(100000000).toString(); + sats = new BigNumber(fiatToBTC(amount)).multipliedBy(100000000).toString(); break; } if (previousUnit === BitcoinUnit.LOCAL_CURRENCY && AmountInput.conversionCache[amount + previousUnit]) { @@ -191,14 +197,14 @@ class AmountInput extends Component { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); this.setState({ isRateBeingUpdated: true }, async () => { try { - await currency.updateExchangeRate(); - currency.mostRecentFetchedRate().then(mostRecentFetchedRate => { + await updateExchangeRate(); + mostRecentFetchedRate().then(mostRecentFetchedRateValue => { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); - this.setState({ mostRecentFetchedRate }); + this.setState({ mostRecentFetchedRate: mostRecentFetchedRateValue }); }); } finally { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); - this.setState({ isRateBeingUpdated: false, isRateOutdated: await currency.isRateOutdated() }); + this.setState({ isRateBeingUpdated: false, isRateOutdated: await isRateOutdated() }); } }); }; @@ -220,11 +226,11 @@ class AmountInput extends Component { secondaryDisplayCurrency = formatBalanceWithoutSuffix((isNaN(amount) ? 0 : amount).toString(), BitcoinUnit.LOCAL_CURRENCY, false); break; case BitcoinUnit.LOCAL_CURRENCY: - secondaryDisplayCurrency = currency.fiatToBTC(parseFloat(isNaN(amount) ? 0 : amount)); + secondaryDisplayCurrency = fiatToBTC(parseFloat(isNaN(amount) ? 0 : amount)); if (AmountInput.conversionCache[isNaN(amount) ? 0 : amount + BitcoinUnit.LOCAL_CURRENCY]) { // cache hit! we reuse old value that supposedly doesn't have rounding errors const sats = AmountInput.conversionCache[isNaN(amount) ? 0 : amount + BitcoinUnit.LOCAL_CURRENCY]; - secondaryDisplayCurrency = currency.satoshiToBTC(sats); + secondaryDisplayCurrency = satoshiToBTC(sats); } break; } @@ -251,7 +257,7 @@ class AmountInput extends Component { {unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && ( - {currency.getCurrencySymbol() + ' '} + {getCurrencySymbol() + ' '} )} {amount !== BitcoinUnit.MAX ? ( }, [wallet, verifyIfWalletAllowsOnchainAddress]); const handleCopyPress = () => { - Clipboard.setString(formatBalance(wallet.getBalance(), wallet.getPreferredBalanceUnit()).toString()); + const value = formatBalance(wallet.getBalance(), wallet.getPreferredBalanceUnit()); + if (value) { + Clipboard.setString(value); + } }; const updateWalletVisibility = (w: AbstractWallet, newHideBalance: boolean) => { @@ -134,8 +137,8 @@ const TransactionsNavigationHeader: React.FC const balance = useMemo(() => { const hideBalance = wallet.hideBalance; const balanceUnit = wallet.getPreferredBalanceUnit(); - const balanceFormatted = formatBalance(wallet.getBalance(), balanceUnit, true).toString(); - return !hideBalance && balanceFormatted; + const balanceFormatted = formatBalance(wallet.getBalance(), balanceUnit, true); + return !hideBalance && balanceFormatted?.toString(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [wallet.hideBalance, wallet.getPreferredBalanceUnit()]); @@ -211,6 +214,7 @@ const TransactionsNavigationHeader: React.FC ) : ( { amountSats = parseInt(fundingAmount.amount, 10); break; case BitcoinUnit.BTC: - amountSats = currency.btcToSatoshi(fundingAmount.amount); + amountSats = btcToSatoshi(fundingAmount.amount); break; case BitcoinUnit.LOCAL_CURRENCY: // also accounting for cached fiat->sat conversion to avoid rounding error - amountSats = currency.btcToSatoshi(currency.fiatToBTC(fundingAmount.amount)); + amountSats = btcToSatoshi(fiatToBTC(fundingAmount.amount)); break; } setFundingAmount({ amount: fundingAmount.amount, amountSats }); @@ -237,10 +237,10 @@ const LdkOpenChannel = (props: any) => { let amountSats = fundingAmount.amountSats; switch (unit) { case BitcoinUnit.BTC: - amountSats = currency.btcToSatoshi(text); + amountSats = btcToSatoshi(text); break; case BitcoinUnit.LOCAL_CURRENCY: - amountSats = currency.btcToSatoshi(currency.fiatToBTC(text)); + amountSats = btcToSatoshi(fiatToBTC(Number(text))); break; case BitcoinUnit.SATS: amountSats = parseInt(text, 10); diff --git a/screen/lnd/lndCreateInvoice.js b/screen/lnd/lndCreateInvoice.js index 7e0cbeb30..73a931b49 100644 --- a/screen/lnd/lndCreateInvoice.js +++ b/screen/lnd/lndCreateInvoice.js @@ -31,7 +31,7 @@ import { requestCameraAuthorization } from '../../helpers/scan-qr'; import { useTheme } from '../../components/themes'; import Button from '../../components/Button'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; -const currency = require('../../blue_modules/currency'); +import { btcToSatoshi, fiatToBTC, satoshiToBTC } from '../../blue_modules/currency'; const LNDCreateInvoice = () => { const { wallets, saveToDisk, setSelectedWalletID } = useContext(BlueStorageContext); @@ -156,11 +156,11 @@ const LNDCreateInvoice = () => { invoiceAmount = parseInt(invoiceAmount, 10); // basically nop break; case BitcoinUnit.BTC: - invoiceAmount = currency.btcToSatoshi(invoiceAmount); + invoiceAmount = btcToSatoshi(invoiceAmount); break; case BitcoinUnit.LOCAL_CURRENCY: // trying to fetch cached sat equivalent for this fiat amount - invoiceAmount = AmountInput.getCachedSatoshis(invoiceAmount) || currency.btcToSatoshi(currency.fiatToBTC(invoiceAmount)); + invoiceAmount = AmountInput.getCachedSatoshis(invoiceAmount) || btcToSatoshi(fiatToBTC(invoiceAmount)); break; } @@ -285,7 +285,7 @@ const LNDCreateInvoice = () => { // nop break; case BitcoinUnit.BTC: - newAmount = currency.satoshiToBTC(newAmount); + newAmount = satoshiToBTC(newAmount); break; case BitcoinUnit.LOCAL_CURRENCY: newAmount = formatBalancePlain(newAmount, BitcoinUnit.LOCAL_CURRENCY); diff --git a/screen/lnd/lnurlPay.js b/screen/lnd/lnurlPay.js index f429854c8..62c95a75e 100644 --- a/screen/lnd/lnurlPay.js +++ b/screen/lnd/lnurlPay.js @@ -17,8 +17,8 @@ import { useTheme } from '../../components/themes'; import Button from '../../components/Button'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import SafeArea from '../../components/SafeArea'; +import { btcToSatoshi, fiatToBTC, satoshiToBTC, satoshiToLocalCurrency } from '../../blue_modules/currency'; const prompt = require('../../helpers/prompt'); -const currency = require('../../blue_modules/currency'); /** * if user has default currency - fiat, attempting to pay will trigger conversion from entered in input field fiat value @@ -86,10 +86,10 @@ const LnurlPay = () => { } switch (unit) { case BitcoinUnit.BTC: - newAmount = currency.satoshiToBTC(newAmount); + newAmount = satoshiToBTC(newAmount); break; case BitcoinUnit.LOCAL_CURRENCY: - newAmount = currency.satoshiToLocalCurrency(newAmount, false); + newAmount = satoshiToLocalCurrency(newAmount, false); _cacheFiatToSat[newAmount] = originalSatAmount; break; } @@ -120,13 +120,13 @@ const LnurlPay = () => { amountSats = parseInt(amountSats, 10); // nop break; case BitcoinUnit.BTC: - amountSats = currency.btcToSatoshi(amountSats); + amountSats = btcToSatoshi(amountSats); break; case BitcoinUnit.LOCAL_CURRENCY: if (_cacheFiatToSat[amount]) { amountSats = _cacheFiatToSat[amount]; } else { - amountSats = currency.btcToSatoshi(currency.fiatToBTC(amountSats)); + amountSats = btcToSatoshi(fiatToBTC(amountSats)); } break; } diff --git a/screen/lnd/scanLndInvoice.js b/screen/lnd/scanLndInvoice.js index 512687363..a8bd2697f 100644 --- a/screen/lnd/scanLndInvoice.js +++ b/screen/lnd/scanLndInvoice.js @@ -27,7 +27,7 @@ import { useTheme } from '../../components/themes'; import Button from '../../components/Button'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import SafeArea from '../../components/SafeArea'; -const currency = require('../../blue_modules/currency'); +import { btcToSatoshi, fiatToBTC } from '../../blue_modules/currency'; const ScanLndInvoice = () => { const { wallets, fetchAndSaveWalletTransactions } = useContext(BlueStorageContext); @@ -182,10 +182,10 @@ const ScanLndInvoice = () => { amountSats = parseInt(amountSats, 10); // nop break; case BitcoinUnit.BTC: - amountSats = currency.btcToSatoshi(amountSats); + amountSats = btcToSatoshi(amountSats); break; case BitcoinUnit.LOCAL_CURRENCY: - amountSats = currency.btcToSatoshi(currency.fiatToBTC(amountSats)); + amountSats = btcToSatoshi(fiatToBTC(amountSats)); break; } setIsLoading(true); diff --git a/screen/receive/details.js b/screen/receive/details.js index 037eaf328..128b1af5b 100644 --- a/screen/receive/details.js +++ b/screen/receive/details.js @@ -38,7 +38,7 @@ import { SuccessView } from '../send/success'; import { useTheme } from '../../components/themes'; import Button from '../../components/Button'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; -const currency = require('../../blue_modules/currency'); +import { fiatToBTC, satoshiToBTC } from '../../blue_modules/currency'; const ReceiveDetails = () => { const { walletID, address } = useRoute().params; @@ -385,14 +385,14 @@ const ReceiveDetails = () => { // nop break; case BitcoinUnit.SATS: - amount = currency.satoshiToBTC(customAmount); + amount = satoshiToBTC(customAmount); break; case BitcoinUnit.LOCAL_CURRENCY: if (AmountInput.conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY]) { // cache hit! we reuse old value that supposedly doesnt have rounding errors - amount = currency.satoshiToBTC(AmountInput.conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY]); + amount = satoshiToBTC(AmountInput.conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY]); } else { - amount = currency.fiatToBTC(customAmount); + amount = fiatToBTC(customAmount); } break; } @@ -447,9 +447,9 @@ const ReceiveDetails = () => { case BitcoinUnit.BTC: return customAmount + ' BTC'; case BitcoinUnit.SATS: - return currency.satoshiToBTC(customAmount) + ' BTC'; + return satoshiToBTC(customAmount) + ' BTC'; case BitcoinUnit.LOCAL_CURRENCY: - return currency.fiatToBTC(customAmount) + ' BTC'; + return fiatToBTC(customAmount) + ' BTC'; } return customAmount + ' ' + customUnit; } else { diff --git a/screen/send/confirm.js b/screen/send/confirm.js index e9e3040ea..511bc009a 100644 --- a/screen/send/confirm.js +++ b/screen/send/confirm.js @@ -17,7 +17,7 @@ import { useTheme } from '../../components/themes'; import Button from '../../components/Button'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import SafeArea from '../../components/SafeArea'; -const currency = require('../../blue_modules/currency'); +import { satoshiToBTC, satoshiToLocalCurrency } from '../../blue_modules/currency'; const BlueElectrum = require('../../blue_modules/BlueElectrum'); const Bignumber = require('bignumber.js'); const bitcoin = require('bitcoinjs-lib'); @@ -182,11 +182,11 @@ const Confirm = () => { <> - {currency.satoshiToBTC(item.value)} + {satoshiToBTC(item.value)} {' ' + loc.units[BitcoinUnit.BTC]} - {currency.satoshiToLocalCurrency(item.value)} + {satoshiToLocalCurrency(item.value)} {loc.send.create_to} @@ -233,7 +233,7 @@ const Confirm = () => { - {loc.send.create_fee}: {formatBalance(feeSatoshi, BitcoinUnit.BTC)} ({currency.satoshiToLocalCurrency(feeSatoshi)}) + {loc.send.create_fee}: {formatBalance(feeSatoshi, BitcoinUnit.BTC)} ({satoshiToLocalCurrency(feeSatoshi)}) {isLoading ? :