BlueWallet/blue_modules/currency.js

262 lines
8.8 KiB
JavaScript
Raw Normal View History

2020-12-16 04:15:57 +01:00
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';
2021-08-03 18:28:47 +02:00
import WidgetCommunication from './WidgetCommunication';
2021-09-09 21:36:35 +02:00
const PREFERRED_CURRENCY_STORAGE_KEY = 'preferredCurrency';
const EXCHANGE_RATES_STORAGE_KEY = 'currency';
2021-05-18 22:38:18 +02:00
2018-12-24 07:14:53 +01:00
let preferredFiatCurrency = FiatUnit.USD;
2021-10-27 06:19:06 +02:00
let exchangeRates = { LAST_UPDATED_ERROR: false };
2021-09-09 21:36:35 +02:00
let lastTimeUpdateExchangeRateWasCalled = 0;
let skipUpdateExchangeRate = false;
2018-07-06 17:38:42 +02:00
2021-09-09 21:36:35 +02:00
const LAST_UPDATED = 'LAST_UPDATED';
2018-07-06 17:38:42 +02:00
2018-12-31 22:27:03 +01:00
/**
* Saves to storage preferred currency, whole object
* from `./models/fiatUnit`
*
* @param item {Object} one of the values in `./models/fiatUnit`
* @returns {Promise<void>}
*/
async function setPrefferedCurrency(item) {
2021-09-09 21:36:35 +02:00
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('-', '_'));
2021-08-03 18:43:00 +02:00
WidgetCommunication.reloadAllTimelines();
2018-12-31 22:27:03 +01:00
}
async function getPreferredCurrency() {
2021-09-09 21:36:35 +02:00
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;
2018-12-31 22:27:03 +01:00
}
2021-09-09 21:36:35 +02:00
async function _restoreSavedExchangeRatesFromStorage() {
try {
exchangeRates = JSON.parse(await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY));
2021-10-27 06:19:06 +02:00
if (!exchangeRates) exchangeRates = { LAST_UPDATED_ERROR: false };
2021-09-09 21:36:35 +02:00
} catch (_) {
2021-10-27 06:19:06 +02:00
exchangeRates = { LAST_UPDATED_ERROR: false };
2018-07-06 17:38:42 +02:00
}
2021-09-09 21:36:35 +02:00
}
2018-12-31 22:27:03 +01:00
2021-09-09 21:36:35 +02:00
async function _restoreSavedPreferredFiatCurrencyFromStorage() {
2018-12-31 22:27:03 +01:00
try {
2021-09-09 21:36:35 +02:00
preferredFiatCurrency = JSON.parse(await AsyncStorage.getItem(PREFERRED_CURRENCY_STORAGE_KEY));
if (preferredFiatCurrency === null) {
throw Error('No Preferred Fiat selected');
}
2023-01-29 19:09:07 +01:00
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;
}
}
2021-09-09 21:36:35 +02:00
}
/**
* 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<void>}
*/
async function updateExchangeRate() {
if (skipUpdateExchangeRate) return;
2021-09-09 21:36:35 +02:00
if (+new Date() - lastTimeUpdateExchangeRateWasCalled <= 10 * 1000) {
// simple debounce so theres no race conditions
return;
}
lastTimeUpdateExchangeRateWasCalled = +new Date();
2021-10-29 06:59:57 +02:00
if (+new Date() - exchangeRates[LAST_UPDATED] <= 30 * 60 * 1000) {
// not updating too often
return;
}
2021-09-09 21:36:35 +02:00
console.log('updating exchange rate...');
2018-12-31 22:27:03 +01:00
let rate;
2018-07-06 17:38:42 +02:00
try {
rate = await getFiatRate(preferredFiatCurrency.endPointKey);
2021-10-13 20:00:58 +02:00
exchangeRates[LAST_UPDATED] = +new Date();
exchangeRates['BTC_' + preferredFiatCurrency.endPointKey] = rate;
2021-10-28 21:08:37 +02:00
exchangeRates.LAST_UPDATED_ERROR = false;
2021-10-13 20:00:58 +02:00
await AsyncStorage.setItem(EXCHANGE_RATES_STORAGE_KEY, JSON.stringify(exchangeRates));
2018-07-06 17:38:42 +02:00
} catch (Err) {
2021-10-13 20:00:58 +02:00
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
2021-10-29 06:15:42 +02:00
rate.LAST_UPDATED_ERROR = true;
2021-10-28 21:08:37 +02:00
exchangeRates.LAST_UPDATED_ERROR = true;
2021-10-29 06:15:42 +02:00
await AsyncStorage.setItem(EXCHANGE_RATES_STORAGE_KEY, JSON.stringify(rate));
2021-10-27 06:19:06 +02:00
throw Err;
2018-07-06 17:38:42 +02:00
}
}
2021-10-28 21:08:37 +02:00
async function isRateOutdated() {
try {
const rate = JSON.parse(await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY));
2021-10-29 06:15:42 +02:00
return rate.LAST_UPDATED_ERROR || +new Date() - rate.LAST_UPDATED >= 31 * 60 * 1000;
2021-10-28 21:08:37 +02:00
} catch {
return true;
}
2021-10-27 06:19:06 +02:00
}
2021-09-09 21:36:35 +02:00
/**
* 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<void>}
*/
async function init(clearLastUpdatedTime = false) {
await _restoreSavedExchangeRatesFromStorage();
await _restoreSavedPreferredFiatCurrencyFromStorage();
if (clearLastUpdatedTime) {
exchangeRates[LAST_UPDATED] = 0;
lastTimeUpdateExchangeRateWasCalled = 0;
2018-12-29 19:58:22 +01:00
}
2018-12-31 22:27:03 +01:00
2018-07-06 17:38:42 +02:00
return updateExchangeRate();
}
function satoshiToLocalCurrency(satoshi, format = true) {
if (!exchangeRates['BTC_' + preferredFiatCurrency.endPointKey]) {
2021-09-09 21:36:35 +02:00
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,
});
}
2018-12-24 07:14:53 +01:00
return formatter.format(b);
}
2018-12-23 06:43:50 +01:00
function BTCToLocalCurrency(bitcoin) {
2018-12-24 19:12:09 +01:00
let sat = new BigNumber(bitcoin);
sat = sat.multipliedBy(100000000).toNumber();
2018-12-23 06:43:50 +01:00
2018-12-24 19:12:09 +01:00
return satoshiToLocalCurrency(sat);
2018-12-23 06:43:50 +01:00
}
2021-10-13 21:46:47 +02:00
async function mostRecentFetchedRate() {
2021-10-29 06:15:42 +02:00
const currencyInformation = JSON.parse(await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY));
2021-10-13 21:46:47 +02:00
const formatter = new Intl.NumberFormat(preferredFiatCurrency.locale, {
style: 'currency',
currency: preferredFiatCurrency.endPointKey,
});
return {
2021-10-28 21:08:37 +02:00
LastUpdated: currencyInformation[LAST_UPDATED],
2021-10-29 06:15:42 +02:00
Rate: formatter.format(currencyInformation[`BTC_${preferredFiatCurrency.endPointKey}`]),
2021-10-13 21:46:47 +02:00
};
}
function satoshiToBTC(satoshi) {
let b = new BigNumber(satoshi);
b = b.dividedBy(100000000);
return b.toString(10);
}
2020-06-09 16:08:18 +02:00
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;
}
2018-07-06 17:38:42 +02:00
module.exports.updateExchangeRate = updateExchangeRate;
2021-09-09 21:36:35 +02:00
module.exports.init = init;
module.exports.satoshiToLocalCurrency = satoshiToLocalCurrency;
2020-06-09 16:08:18 +02:00
module.exports.fiatToBTC = fiatToBTC;
module.exports.satoshiToBTC = satoshiToBTC;
2018-12-23 06:53:26 +01:00
module.exports.BTCToLocalCurrency = BTCToLocalCurrency;
2018-12-31 22:27:03 +01:00
module.exports.setPrefferedCurrency = setPrefferedCurrency;
module.exports.getPreferredCurrency = getPreferredCurrency;
2020-06-09 16:08:18 +02:00
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
2021-09-09 21:36:35 +02:00
module.exports.PREFERRED_CURRENCY = PREFERRED_CURRENCY_STORAGE_KEY;
module.exports.EXCHANGE_RATES = EXCHANGE_RATES_STORAGE_KEY;
module.exports.LAST_UPDATED = LAST_UPDATED;
2021-10-13 21:46:47 +02:00
module.exports.mostRecentFetchedRate = mostRecentFetchedRate;
2021-10-27 06:19:06 +02:00
module.exports.isRateOutdated = isRateOutdated;