From d15309aaa1d9ac73e4920c4d285b9ad1e2d292d5 Mon Sep 17 00:00:00 2001 From: Yaroslav Grachev Date: Wed, 20 Sep 2023 07:35:12 +0300 Subject: [PATCH 1/4] feat: price provider model --- src/renderer/app/index.tsx | 5 ++ src/renderer/entities/price/index.ts | 2 + src/renderer/entities/price/lib/constants.ts | 10 +++ src/renderer/entities/price/lib/types.ts | 3 + .../entities/price/model/currency-model.ts | 86 +++++++++++++++++++ .../price/model/price-provider-model.ts | 83 ++++++++++++++++++ .../__tests__/localStorageService.test.ts | 10 +++ .../shared/api/local-storage/index.ts | 1 + .../service/localStorageService.ts | 22 +++++ .../__tests__/coingeckoAdapter.test.ts} | 18 ++-- .../__tests__}/utils.test.ts | 8 +- .../api/price-provider/common/constants.ts | 10 +++ .../{price => price-provider}/common/types.ts | 10 +++ .../{price => price-provider}/common/utils.ts | 17 ++-- .../shared/api/price-provider/index.ts | 3 + .../service/coingeckoService.ts | 47 ++++++++++ .../api/price-provider/service/fiatService.ts | 70 +++++++++++++++ .../api/price/coingecko/CoingeckoAdapter.ts | 51 ----------- .../shared/api/price/coingecko/consts.ts | 1 - src/renderer/shared/api/price/index.ts | 3 - 20 files changed, 380 insertions(+), 80 deletions(-) create mode 100644 src/renderer/entities/price/index.ts create mode 100644 src/renderer/entities/price/lib/constants.ts create mode 100644 src/renderer/entities/price/lib/types.ts create mode 100644 src/renderer/entities/price/model/currency-model.ts create mode 100644 src/renderer/entities/price/model/price-provider-model.ts create mode 100644 src/renderer/shared/api/local-storage/__tests__/localStorageService.test.ts create mode 100644 src/renderer/shared/api/local-storage/index.ts create mode 100644 src/renderer/shared/api/local-storage/service/localStorageService.ts rename src/renderer/shared/api/{price/coingecko/CoingeckoAdapter.test.ts => price-provider/__tests__/coingeckoAdapter.test.ts} (66%) rename src/renderer/shared/api/{price/common => price-provider/__tests__}/utils.test.ts (85%) create mode 100644 src/renderer/shared/api/price-provider/common/constants.ts rename src/renderer/shared/api/{price => price-provider}/common/types.ts (78%) rename src/renderer/shared/api/{price => price-provider}/common/utils.ts (65%) create mode 100644 src/renderer/shared/api/price-provider/index.ts create mode 100644 src/renderer/shared/api/price-provider/service/coingeckoService.ts create mode 100644 src/renderer/shared/api/price-provider/service/fiatService.ts delete mode 100644 src/renderer/shared/api/price/coingecko/CoingeckoAdapter.ts delete mode 100644 src/renderer/shared/api/price/coingecko/consts.ts delete mode 100644 src/renderer/shared/api/price/index.ts diff --git a/src/renderer/app/index.tsx b/src/renderer/app/index.tsx index ae34b251e8..bd82885420 100644 --- a/src/renderer/app/index.tsx +++ b/src/renderer/app/index.tsx @@ -2,6 +2,8 @@ import { createRoot } from 'react-dom/client'; import { HashRouter as Router } from 'react-router-dom'; import log from 'electron-log'; +import { currencyModel } from '@renderer/entities/price'; +// import { priceModel, currencyModel } from '@renderer/entities/price'; import App from './App'; import './i18n'; @@ -26,6 +28,9 @@ if (!container) { throw new Error('Root container is missing in index.html'); } +// priceModel.events.appStarted(); +currencyModel.events.appStarted(); + createRoot(container).render( diff --git a/src/renderer/entities/price/index.ts b/src/renderer/entities/price/index.ts new file mode 100644 index 0000000000..2490e34c97 --- /dev/null +++ b/src/renderer/entities/price/index.ts @@ -0,0 +1,2 @@ +export * as priceModel from './model/price-provider-model'; +export * as currencyModel from './model/currency-model'; diff --git a/src/renderer/entities/price/lib/constants.ts b/src/renderer/entities/price/lib/constants.ts new file mode 100644 index 0000000000..66207af593 --- /dev/null +++ b/src/renderer/entities/price/lib/constants.ts @@ -0,0 +1,10 @@ +import { PriceApiProvider } from './types'; + +export const DEFAULT_CURRENCY_CONFIG = []; + +export const DEFAULT_CURRENCY_SYMBOL = 'usd'; + +export const DEFAULT_SHOW_FIAT = true; + +export const DEFAULT_FIAT_PROVIDER = PriceApiProvider.COINGEKO; +export const DEFAULT_ASSETS_PRICES = []; diff --git a/src/renderer/entities/price/lib/types.ts b/src/renderer/entities/price/lib/types.ts new file mode 100644 index 0000000000..977633b110 --- /dev/null +++ b/src/renderer/entities/price/lib/types.ts @@ -0,0 +1,3 @@ +export const enum PriceApiProvider { + COINGEKO = 'coingeko', +} diff --git a/src/renderer/entities/price/model/currency-model.ts b/src/renderer/entities/price/model/currency-model.ts new file mode 100644 index 0000000000..64334b3f77 --- /dev/null +++ b/src/renderer/entities/price/model/currency-model.ts @@ -0,0 +1,86 @@ +import { createEvent, createStore, createEffect, forward, sample } from 'effector'; + +import { CurrencyConfig, fiatService } from '@renderer/shared/api/price-provider'; +import { DEFAULT_CURRENCY_SYMBOL, DEFAULT_CURRENCY_CONFIG } from '../lib/constants'; + +const appStarted = createEvent(); + +export const $currencyConfig = createStore([]); +export const $activeCurrency = createStore(null); + +const $activeCurrencySymbol = createStore(null); + +const currencyChanged = createEvent(); + +const getCurrencyConfigFx = createEffect((): CurrencyConfig[] => { + return fiatService.getCurrencyConfig(DEFAULT_CURRENCY_CONFIG); +}); + +const fetchCurrencyConfigFx = createEffect((): Promise => { + return fiatService.fetchCurrencyConfig(); +}); + +const saveCurrencyConfigFx = createEffect((config: CurrencyConfig[]) => { + fiatService.saveCurrencyConfig(config); +}); + +const getActiveCurrencySymbolFx = createEffect((): string => { + return fiatService.getActiveCurrencySymbol(DEFAULT_CURRENCY_SYMBOL); +}); + +const setActiveCurrencySymbolFx = createEffect((symbol: string) => { + fiatService.saveActiveCurrencySymbol(symbol); +}); + +type ChangeParams = { + id?: CurrencyConfig['id']; + symbol?: CurrencyConfig['symbol']; + config: CurrencyConfig[]; +}; +const currencyChangedFx = createEffect(({ id, symbol, config }) => { + return config.find((currency) => currency.id === id || currency.symbol === symbol); +}); + +forward({ + from: appStarted, + to: [getActiveCurrencySymbolFx, getCurrencyConfigFx, fetchCurrencyConfigFx], +}); + +forward({ from: getActiveCurrencySymbolFx.doneData, to: $activeCurrencySymbol }); + +forward({ from: getCurrencyConfigFx.doneData, to: $currencyConfig }); + +forward({ from: fetchCurrencyConfigFx.doneData, to: [$currencyConfig, saveCurrencyConfigFx] }); + +sample({ + clock: [getCurrencyConfigFx.doneData, fetchCurrencyConfigFx.doneData], + source: $activeCurrencySymbol, + filter: (symbol: CurrencyConfig['symbol'] | null): symbol is CurrencyConfig['symbol'] => Boolean(symbol), + fn: (symbol, config) => ({ symbol, config }), + target: currencyChangedFx, +}); + +sample({ + clock: currencyChanged, + source: $currencyConfig, + fn: (config, id) => ({ config, id }), + target: currencyChangedFx, +}); + +sample({ + clock: currencyChangedFx.doneData, + filter: (newCurrency?: CurrencyConfig): newCurrency is CurrencyConfig => Boolean(newCurrency), + target: $activeCurrency, +}); + +sample({ + clock: $activeCurrency, + filter: (currency: CurrencyConfig | null): currency is CurrencyConfig => Boolean(currency), + fn: (currency) => currency.symbol, + target: setActiveCurrencySymbolFx, +}); + +export const events = { + appStarted, + currencyChanged, +}; diff --git a/src/renderer/entities/price/model/price-provider-model.ts b/src/renderer/entities/price/model/price-provider-model.ts new file mode 100644 index 0000000000..e2a242b12b --- /dev/null +++ b/src/renderer/entities/price/model/price-provider-model.ts @@ -0,0 +1,83 @@ +export {}; + +// import { createEvent, createStore, forward, createEffect, sample } from 'effector'; +// +// import { PriceApiProvider } from '../lib/types'; +// import { DEFAULT_SHOW_FIAT, DEFAULT_FIAT_PROVIDER, DEFAULT_ASSETS_PRICES } from '../lib/constants'; +// import { fiatService, PriceObject } from '@renderer/shared/api/price-provider'; +// import * as currencyModel from './currency-model'; +// +// const appStarted = createEvent(); +// +// export const $fiatFlag = createStore(false); +// export const $priceProvider = createStore(null); +// export const $chainsPrices = createStore([]); +// +// const fiatFlagChanged = createEvent(); +// const priceProviderChanged = createEvent(); +// const chainsPricesRequested = createEvent(); +// +// const getFiatFlagFx = createEffect((): boolean => { +// return fiatService.getFiatFlag(DEFAULT_SHOW_FIAT); +// }); +// +// const saveFiatFlagFx = createEffect((flag: boolean) => { +// fiatService.saveFiatFlag(flag); +// }); +// +// const getPriceProviderFx = createEffect((): string => { +// return fiatService.getFiatProvider(DEFAULT_FIAT_PROVIDER); +// }); +// +// const savePriceProviderFx = createEffect((provider: string) => { +// fiatService.saveFiatProvider(provider); +// }); +// +// type FetchPrices = { +// provider: PriceApiProvider; +// currency: string; +// includeRates: boolean; +// }; +// const fetchChainsPricesFx = createEffect>( +// async ({ provider, currency, includeRates }) => { +// // const ProvidersMap: Record = { +// // [PriceApiProvider.COINGEKO]: coingekoService, +// // }; +// // +// // // const chains = chains +// // +// // return ProvidersMap[provider].getPrice(); +// }, +// ); +// +// const getChainsPricesFx = createEffect(() => { +// fiatService.getAssetsPrices(DEFAULT_ASSETS_PRICES); +// }); +// +// const saveChainsPricesFx = createEffect((prices: any[]) => { +// fiatService.saveAssetsPrices(prices); +// }); +// +// forward({ from: appStarted, to: [getFiatFlagFx, getPriceProviderFx] }); +// +// sample({ +// clock: currencyModel.$activeCurrency, +// source: $priceProvider, +// fn: (provider, currency) => ({ provider }), +// target: fetchChainsPricesFx, +// }); +// +// forward({ from: fiatFlagChanged, to: [$fiatFlag, saveFiatFlagFx] }); +// +// forward({ from: priceProviderChanged, to: [$priceProvider, savePriceProviderFx] }); +// +// sample({ +// clock: chainsPricesRequested, +// source: $priceProvider, +// target: [$priceProvider, savePriceProviderFx], +// }); +// +// export const events = { +// appStarted, +// showFiatChanged: fiatFlagChanged, +// }; diff --git a/src/renderer/shared/api/local-storage/__tests__/localStorageService.test.ts b/src/renderer/shared/api/local-storage/__tests__/localStorageService.test.ts new file mode 100644 index 0000000000..1a8d594683 --- /dev/null +++ b/src/renderer/shared/api/local-storage/__tests__/localStorageService.test.ts @@ -0,0 +1,10 @@ +import { localStorageService } from '../service/localStorageService'; + +// TODO: complete +describe('shared/api/local-storage/localStorageService', () => { + test('', () => { + localStorageService.saveToStorage('wqe', 'qwe'); + + expect(1).toEqual(1); + }); +}); diff --git a/src/renderer/shared/api/local-storage/index.ts b/src/renderer/shared/api/local-storage/index.ts new file mode 100644 index 0000000000..722f91d12c --- /dev/null +++ b/src/renderer/shared/api/local-storage/index.ts @@ -0,0 +1 @@ +export { localStorageService } from './service/localStorageService'; diff --git a/src/renderer/shared/api/local-storage/service/localStorageService.ts b/src/renderer/shared/api/local-storage/service/localStorageService.ts new file mode 100644 index 0000000000..70632be252 --- /dev/null +++ b/src/renderer/shared/api/local-storage/service/localStorageService.ts @@ -0,0 +1,22 @@ +export const localStorageService = { + getFromStorage, + saveToStorage, +}; + +function getFromStorage(key: string, defaultValue: T): T { + const storageItem = localStorage.getItem(key); + + if (!storageItem) return defaultValue; + + try { + return storageItem ? JSON.parse(storageItem) : defaultValue; + } catch { + console.error(`🔸LocalStorageService - Could not retrieve item by key - ${key}`); + + return defaultValue; + } +} + +function saveToStorage(key: string, value: T) { + localStorage.setItem(key, JSON.stringify(value)); +} diff --git a/src/renderer/shared/api/price/coingecko/CoingeckoAdapter.test.ts b/src/renderer/shared/api/price-provider/__tests__/coingeckoAdapter.test.ts similarity index 66% rename from src/renderer/shared/api/price/coingecko/CoingeckoAdapter.test.ts rename to src/renderer/shared/api/price-provider/__tests__/coingeckoAdapter.test.ts index a59f77c25b..82fb2f757d 100644 --- a/src/renderer/shared/api/price/coingecko/CoingeckoAdapter.test.ts +++ b/src/renderer/shared/api/price-provider/__tests__/coingeckoAdapter.test.ts @@ -1,7 +1,7 @@ -import { useCoinGeckoAdapter } from './CoingeckoAdapter'; +import { coingekoService } from '../service/coingeckoService'; -describe('api/price/coingecko/CoinGeckoAdapter', () => { - test('get price from coingecko', async () => { +describe('shared/api/price-provider/coinGeckoAdapter', () => { + test('get price-provider from coingecko', async () => { global.fetch = jest.fn(() => Promise.resolve({ json: () => @@ -22,15 +22,13 @@ describe('api/price/coingecko/CoinGeckoAdapter', () => { }), ) as jest.Mock; - const { getPrice } = useCoinGeckoAdapter(); - - const result = await getPrice(['kusama', 'polkadot'], ['usd', 'rub'], true); + const result = await coingekoService.getPrice(['kusama', 'polkadot'], ['usd', 'rub'], true); expect(result['kusama']['usd'].price).toBeDefined(); expect(result['polkadot']['rub'].change).toBeDefined(); }); - test('get price from coingecko', async () => { + test('get history data from coingecko', async () => { global.fetch = jest.fn(() => Promise.resolve({ json: () => @@ -44,10 +42,8 @@ describe('api/price/coingecko/CoinGeckoAdapter', () => { }), ) as jest.Mock; - const { getHistoryData } = useCoinGeckoAdapter(); - - const result = await getHistoryData('kusama', 'usd', 1692700000, 1692701000); + const result = await coingekoService.getHistoryData('kusama', 'usd', 1692700000, 1692701000); - expect(result.length).toBe(3); + expect(result.length).toEqual(3); }); }); diff --git a/src/renderer/shared/api/price/common/utils.test.ts b/src/renderer/shared/api/price-provider/__tests__/utils.test.ts similarity index 85% rename from src/renderer/shared/api/price/common/utils.test.ts rename to src/renderer/shared/api/price-provider/__tests__/utils.test.ts index 0bd97faea1..6878d064b5 100644 --- a/src/renderer/shared/api/price/common/utils.test.ts +++ b/src/renderer/shared/api/price-provider/__tests__/utils.test.ts @@ -1,13 +1,13 @@ -import { convertPriceToObjectView, convertPriceToDBView, getCurrencyChangeKey } from './utils'; +import { convertPriceToObjectView, convertPriceToDBView, getCurrencyChangeKey } from '../common/utils'; -describe('api/price/common', () => { +describe('api/price-provider/common', () => { test('get correct change key', () => { const result = getCurrencyChangeKey('polkadot'); expect(result).toEqual('polkadot_24h_change'); }); - test('convert price from object to array', () => { + test('convert price-provider from object to array', () => { const result = convertPriceToDBView({ kusama: { usd: { @@ -34,7 +34,7 @@ describe('api/price/common', () => { expect(result.length).toEqual(4); }); - test('convert price from array to object', () => { + test('convert price-provider from array to object', () => { const result = convertPriceToObjectView([ { assetId: 'kusama', currency: 'usd', price: 19.06, change: -5.22061353514796 }, { assetId: 'kusama', currency: 'rub', price: 1795.2, change: -5.284952983744856 }, diff --git a/src/renderer/shared/api/price-provider/common/constants.ts b/src/renderer/shared/api/price-provider/common/constants.ts new file mode 100644 index 0000000000..ee11692bee --- /dev/null +++ b/src/renderer/shared/api/price-provider/common/constants.ts @@ -0,0 +1,10 @@ +export const COINGECKO_URL = 'https://api.coingecko.com/api/v3'; + +export const CURRENCY_URL = + 'https://raw.githubusercontent.com/novasamatech/nova-wallet-android/develop/feature-currency-impl/src/main/res/raw/currencies.json'; + +export const CURRENCY_CONFIG_KEY = 'currency_config'; +export const ACTIVE_CURRENCY_KEY = 'currency_active'; +export const FIAT_FLAG_KEY = 'show_fiat'; +export const FIAT_PROVIDER = 'fiat_provider'; +export const ASSETS_PRICES = 'assets_prices'; diff --git a/src/renderer/shared/api/price/common/types.ts b/src/renderer/shared/api/price-provider/common/types.ts similarity index 78% rename from src/renderer/shared/api/price/common/types.ts rename to src/renderer/shared/api/price-provider/common/types.ts index 1c25c6c485..977fd8a04d 100644 --- a/src/renderer/shared/api/price/common/types.ts +++ b/src/renderer/shared/api/price-provider/common/types.ts @@ -19,3 +19,13 @@ export type PriceAdapter = { getPrice: (ids: AssetId[], currencies: Currency[], includeRateChange: boolean) => Promise; getHistoryData: (id: AssetId, currency: Currency, from: number, to: number) => Promise; }; + +export type CurrencyConfig = { + id: number; + code: string; + name: string; + symbol: string; + category: 'fiat' | 'crypto'; + popular: boolean; + coingeckoId: string; +}; diff --git a/src/renderer/shared/api/price/common/utils.ts b/src/renderer/shared/api/price-provider/common/utils.ts similarity index 65% rename from src/renderer/shared/api/price/common/utils.ts rename to src/renderer/shared/api/price-provider/common/utils.ts index ab454bdbd0..fedf6e7c32 100644 --- a/src/renderer/shared/api/price/common/utils.ts +++ b/src/renderer/shared/api/price-provider/common/utils.ts @@ -1,10 +1,10 @@ import { PriceObject, PriceDB } from './types'; -export const getCurrencyChangeKey = (currency: string): string => { +export function getCurrencyChangeKey(currency: string): string { return `${currency}_24h_change`; -}; +} -export const convertPriceToDBView = (price: PriceObject): PriceDB[] => { +export function convertPriceToDBView(price: PriceObject): PriceDB[] { const priceDB: PriceDB[] = []; Object.entries(price).forEach(([assetId, assetPrice]) => { @@ -19,19 +19,16 @@ export const convertPriceToDBView = (price: PriceObject): PriceDB[] => { }); return priceDB; -}; +} -export const convertPriceToObjectView = (prices: PriceDB[]): PriceObject => { +export function convertPriceToObjectView(prices: PriceDB[]): PriceObject { return prices.reduce((result, { assetId, currency, price, change }) => { if (!result[assetId]) { result[assetId] = {}; } - result[assetId][currency] = { - price, - change, - }; + result[assetId][currency] = { price, change }; return result; }, {}); -}; +} diff --git a/src/renderer/shared/api/price-provider/index.ts b/src/renderer/shared/api/price-provider/index.ts new file mode 100644 index 0000000000..ad074ffca1 --- /dev/null +++ b/src/renderer/shared/api/price-provider/index.ts @@ -0,0 +1,3 @@ +export { coingekoService } from './service/coingeckoService'; +export { fiatService } from './service/fiatService'; +export type { CurrencyConfig, PriceAdapter, PriceObject } from './common/types'; diff --git a/src/renderer/shared/api/price-provider/service/coingeckoService.ts b/src/renderer/shared/api/price-provider/service/coingeckoService.ts new file mode 100644 index 0000000000..12b7c808e4 --- /dev/null +++ b/src/renderer/shared/api/price-provider/service/coingeckoService.ts @@ -0,0 +1,47 @@ +import { AssetId, Currency, PriceObject, PriceAdapter, PriceItem, PriceRange } from '../common/types'; +import { getCurrencyChangeKey } from '../common/utils'; +import { COINGECKO_URL } from '../common/constants'; + +export const coingekoService: PriceAdapter = { + getPrice, + getHistoryData, +}; + +async function getPrice(ids: AssetId[], currencies: Currency[], includeRateChange: boolean): Promise { + const url = new URL(`${COINGECKO_URL}/simple/price`); + url.search = new URLSearchParams({ + ids: ids.join(','), + vs_currencies: currencies.join(','), + include_24hr_change: includeRateChange.toString(), + }).toString(); + + const response = await fetch(url); + const data = await response.json(); + + return ids.reduce((acc, assetId) => { + acc[assetId] = currencies.reduce>((accPrice, currency) => { + accPrice[currency] = { + price: data[assetId][currency], + change: data[assetId][getCurrencyChangeKey(currency)], + }; + + return accPrice; + }, {}); + + return acc; + }, {}); +} + +async function getHistoryData(id: string, currency: string, from: number, to: number): Promise { + const url = new URL(`${COINGECKO_URL}/coins/${id}/market_chart/range`); + url.search = new URLSearchParams({ + vs_currency: currency, + from: from.toString(), + to: to.toString(), + }).toString(); + + const response = await fetch(url); + const data = await response.json(); + + return data.prices; +} diff --git a/src/renderer/shared/api/price-provider/service/fiatService.ts b/src/renderer/shared/api/price-provider/service/fiatService.ts new file mode 100644 index 0000000000..02b19a0b47 --- /dev/null +++ b/src/renderer/shared/api/price-provider/service/fiatService.ts @@ -0,0 +1,70 @@ +import { localStorageService } from '@renderer/shared/api/local-storage'; +import { CurrencyConfig } from '../common/types'; +import { + CURRENCY_URL, + CURRENCY_CONFIG_KEY, + ACTIVE_CURRENCY_KEY, + FIAT_FLAG_KEY, + FIAT_PROVIDER, + ASSETS_PRICES, +} from '../common/constants'; + +export const fiatService = { + fetchCurrencyConfig, + getCurrencyConfig, + saveCurrencyConfig, + getActiveCurrencySymbol, + saveActiveCurrencySymbol, + getFiatFlag, + saveFiatFlag, + getFiatProvider, + saveFiatProvider, + getAssetsPrices, + saveAssetsPrices, +}; + +async function fetchCurrencyConfig(): Promise { + const response = await fetch(CURRENCY_URL, { cache: 'default' }); + + return response.json(); +} + +function getCurrencyConfig(defaultConfig: CurrencyConfig[]): CurrencyConfig[] { + return localStorageService.getFromStorage(CURRENCY_CONFIG_KEY, defaultConfig); +} + +function saveCurrencyConfig(config: CurrencyConfig[]) { + localStorageService.saveToStorage(CURRENCY_CONFIG_KEY, config); +} + +function getActiveCurrencySymbol(defaultSymbol: string): string { + return localStorageService.getFromStorage(ACTIVE_CURRENCY_KEY, defaultSymbol); +} + +function saveActiveCurrencySymbol(symbol: string) { + localStorageService.saveToStorage(ACTIVE_CURRENCY_KEY, symbol); +} + +function getFiatFlag(defaultFlag: boolean): boolean { + return localStorageService.getFromStorage(FIAT_FLAG_KEY, defaultFlag); +} + +function saveFiatFlag(flag: boolean) { + localStorageService.saveToStorage(FIAT_FLAG_KEY, flag); +} + +function getFiatProvider(defaultFiatProvider: string): string { + return localStorageService.getFromStorage(FIAT_PROVIDER, defaultFiatProvider); +} + +function saveFiatProvider(provider: string) { + localStorageService.saveToStorage(FIAT_PROVIDER, provider); +} + +function getAssetsPrices(defaultPrices: T[]): T[] { + return localStorageService.getFromStorage(ASSETS_PRICES, defaultPrices); +} + +function saveAssetsPrices(prices: T[]) { + localStorageService.saveToStorage(ASSETS_PRICES, prices); +} diff --git a/src/renderer/shared/api/price/coingecko/CoingeckoAdapter.ts b/src/renderer/shared/api/price/coingecko/CoingeckoAdapter.ts deleted file mode 100644 index 5571e6b173..0000000000 --- a/src/renderer/shared/api/price/coingecko/CoingeckoAdapter.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { AssetId, Currency, PriceObject, PriceAdapter, PriceItem, PriceRange } from '../common/types'; -import { getCurrencyChangeKey } from '../common/utils'; -import { COINGECKO_URL } from './consts'; - -export const useCoinGeckoAdapter = (): PriceAdapter => { - const getPrice = async (ids: AssetId[], currencies: Currency[], includeRateChange: boolean): Promise => { - const url = new URL(`${COINGECKO_URL}/simple/price`); - url.search = new URLSearchParams({ - ids: ids.join(','), - vs_currencies: currencies.join(','), - include_24hr_change: includeRateChange.toString(), - }).toString(); - - const response = await fetch(url); - - const data = await response.json(); - - return ids.reduce((acc, assetId) => { - acc[assetId] = currencies.reduce>((accPrice, currency) => { - accPrice[currency] = { - price: data[assetId][currency], - change: data[assetId][getCurrencyChangeKey(currency)], - }; - - return accPrice; - }, {}); - - return acc; - }, {}); - }; - - const getHistoryData = async (id: string, currency: string, from: number, to: number): Promise => { - const url = new URL(`${COINGECKO_URL}/coins/${id}/market_chart/range`); - url.search = new URLSearchParams({ - vs_currency: currency, - from: from.toString(), - to: to.toString(), - }).toString(); - - const response = await fetch(url); - - const data = await response.json(); - - return data.prices; - }; - - return { - getPrice, - getHistoryData, - }; -}; diff --git a/src/renderer/shared/api/price/coingecko/consts.ts b/src/renderer/shared/api/price/coingecko/consts.ts deleted file mode 100644 index a6fb2a747d..0000000000 --- a/src/renderer/shared/api/price/coingecko/consts.ts +++ /dev/null @@ -1 +0,0 @@ -export const COINGECKO_URL = 'https://api.coingecko.com/api/v3'; diff --git a/src/renderer/shared/api/price/index.ts b/src/renderer/shared/api/price/index.ts deleted file mode 100644 index b2dd9830df..0000000000 --- a/src/renderer/shared/api/price/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './coingecko/CoingeckoAdapter'; -export * from './common/types'; -export * from './common/utils'; From 7e3c1928ae79b3588d28a0a1c36faa121962caeb Mon Sep 17 00:00:00 2001 From: Yaroslav Grachev Date: Wed, 20 Sep 2023 11:06:05 +0300 Subject: [PATCH 2/4] feat: load assets prices --- src/renderer/app/App.tsx | 6 + src/renderer/app/index.tsx | 5 - src/renderer/entities/price/index.ts | 2 +- src/renderer/entities/price/lib/constants.ts | 9 +- .../entities/price/model/currency-model.ts | 52 +++-- .../price/model/price-provider-model.ts | 181 ++++++++++-------- .../api/price-provider/common/constants.ts | 4 +- .../api/price-provider/service/fiatService.ts | 21 +- 8 files changed, 148 insertions(+), 132 deletions(-) diff --git a/src/renderer/app/App.tsx b/src/renderer/app/App.tsx index 4cd180c77c..229a3b5739 100644 --- a/src/renderer/app/App.tsx +++ b/src/renderer/app/App.tsx @@ -1,9 +1,11 @@ import { useEffect, useState } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { useNavigate, useRoutes } from 'react-router-dom'; +import { useUnit } from 'effector-react'; import { FallbackScreen } from '@renderer/components/common'; import { useAccount } from '@renderer/entities/account'; +import { priceProviderModel, currencyModel } from '@renderer/entities/price'; import { ConfirmDialogProvider, I18Provider, @@ -22,6 +24,10 @@ const App = () => { const appRoutes = useRoutes(routesConfig); const { getAccounts } = useAccount(); + const [assetsPrices, activeCurrency] = useUnit([priceProviderModel.$assetsPrices, currencyModel.$activeCurrency]); + console.log('🔴 assetsPrices === > ', assetsPrices); + console.log('🔴 currency === > ', activeCurrency); + const [showSplashScreen, setShowSplashScreen] = useState(true); const [isAccountsLoading, setIsAccountsLoading] = useState(true); diff --git a/src/renderer/app/index.tsx b/src/renderer/app/index.tsx index 0bc37540ac..f73a3cd76f 100644 --- a/src/renderer/app/index.tsx +++ b/src/renderer/app/index.tsx @@ -2,8 +2,6 @@ import { createRoot } from 'react-dom/client'; import { HashRouter as Router } from 'react-router-dom'; import log from 'electron-log'; -import { currencyModel } from '@renderer/entities/price'; -// import { priceModel, currencyModel } from '@renderer/entities/price'; import { kernelModel } from '@renderer/shared/core'; import App from './App'; @@ -29,9 +27,6 @@ if (!container) { throw new Error('Root container is missing in index.html'); } -// priceModel.events.appStarted(); -currencyModel.events.appStarted(); - kernelModel.events.appStarted(); createRoot(container).render( diff --git a/src/renderer/entities/price/index.ts b/src/renderer/entities/price/index.ts index 2490e34c97..41de983ff6 100644 --- a/src/renderer/entities/price/index.ts +++ b/src/renderer/entities/price/index.ts @@ -1,2 +1,2 @@ -export * as priceModel from './model/price-provider-model'; +export * as priceProviderModel from './model/price-provider-model'; export * as currencyModel from './model/currency-model'; diff --git a/src/renderer/entities/price/lib/constants.ts b/src/renderer/entities/price/lib/constants.ts index 66207af593..51c589f57a 100644 --- a/src/renderer/entities/price/lib/constants.ts +++ b/src/renderer/entities/price/lib/constants.ts @@ -1,10 +1,11 @@ import { PriceApiProvider } from './types'; -export const DEFAULT_CURRENCY_CONFIG = []; +export const DEFAULT_FIAT_FLAG = false; -export const DEFAULT_CURRENCY_SYMBOL = 'usd'; +export const DEFAULT_CURRENCY_CONFIG = []; -export const DEFAULT_SHOW_FIAT = true; +export const DEFAULT_CURRENCY_CODE = 'usd'; export const DEFAULT_FIAT_PROVIDER = PriceApiProvider.COINGEKO; -export const DEFAULT_ASSETS_PRICES = []; + +export const DEFAULT_ASSETS_PRICES = {}; diff --git a/src/renderer/entities/price/model/currency-model.ts b/src/renderer/entities/price/model/currency-model.ts index 64334b3f77..b0d2e9cdb3 100644 --- a/src/renderer/entities/price/model/currency-model.ts +++ b/src/renderer/entities/price/model/currency-model.ts @@ -1,14 +1,13 @@ import { createEvent, createStore, createEffect, forward, sample } from 'effector'; +import { kernelModel } from '@renderer/shared/core'; import { CurrencyConfig, fiatService } from '@renderer/shared/api/price-provider'; -import { DEFAULT_CURRENCY_SYMBOL, DEFAULT_CURRENCY_CONFIG } from '../lib/constants'; - -const appStarted = createEvent(); +import { DEFAULT_CURRENCY_CODE, DEFAULT_CURRENCY_CONFIG } from '../lib/constants'; export const $currencyConfig = createStore([]); export const $activeCurrency = createStore(null); -const $activeCurrencySymbol = createStore(null); +const $activeCurrencyCode = createStore(null); const currencyChanged = createEvent(); @@ -24,29 +23,34 @@ const saveCurrencyConfigFx = createEffect((config: CurrencyConfig[]) => { fiatService.saveCurrencyConfig(config); }); -const getActiveCurrencySymbolFx = createEffect((): string => { - return fiatService.getActiveCurrencySymbol(DEFAULT_CURRENCY_SYMBOL); +const getActiveCurrencyCodeFx = createEffect((): string => { + return fiatService.getActiveCurrencyCode(DEFAULT_CURRENCY_CODE); }); -const setActiveCurrencySymbolFx = createEffect((symbol: string) => { - fiatService.saveActiveCurrencySymbol(symbol); +const saveActiveCurrencyCodeFx = createEffect((currency: CurrencyConfig) => { + fiatService.saveActiveCurrencyCode(currency.code); }); type ChangeParams = { id?: CurrencyConfig['id']; - symbol?: CurrencyConfig['symbol']; + code?: CurrencyConfig['code']; config: CurrencyConfig[]; }; -const currencyChangedFx = createEffect(({ id, symbol, config }) => { - return config.find((currency) => currency.id === id || currency.symbol === symbol); +const currencyChangedFx = createEffect(({ id, code, config }) => { + return config.find((currency) => { + const hasId = currency.id === id; + const hasCode = currency.code.toLowerCase() === code?.toLowerCase(); + + return hasId || hasCode; + }); }); forward({ - from: appStarted, - to: [getActiveCurrencySymbolFx, getCurrencyConfigFx, fetchCurrencyConfigFx], + from: kernelModel.events.appStarted, + to: [getActiveCurrencyCodeFx, getCurrencyConfigFx, fetchCurrencyConfigFx], }); -forward({ from: getActiveCurrencySymbolFx.doneData, to: $activeCurrencySymbol }); +forward({ from: getActiveCurrencyCodeFx.doneData, to: $activeCurrencyCode }); forward({ from: getCurrencyConfigFx.doneData, to: $currencyConfig }); @@ -54,9 +58,9 @@ forward({ from: fetchCurrencyConfigFx.doneData, to: [$currencyConfig, saveCurren sample({ clock: [getCurrencyConfigFx.doneData, fetchCurrencyConfigFx.doneData], - source: $activeCurrencySymbol, - filter: (symbol: CurrencyConfig['symbol'] | null): symbol is CurrencyConfig['symbol'] => Boolean(symbol), - fn: (symbol, config) => ({ symbol, config }), + source: $activeCurrencyCode, + filter: (code: CurrencyConfig['code'] | null): code is CurrencyConfig['code'] => Boolean(code), + fn: (code, config) => ({ code, config }), target: currencyChangedFx, }); @@ -69,18 +73,12 @@ sample({ sample({ clock: currencyChangedFx.doneData, - filter: (newCurrency?: CurrencyConfig): newCurrency is CurrencyConfig => Boolean(newCurrency), - target: $activeCurrency, -}); - -sample({ - clock: $activeCurrency, - filter: (currency: CurrencyConfig | null): currency is CurrencyConfig => Boolean(currency), - fn: (currency) => currency.symbol, - target: setActiveCurrencySymbolFx, + source: $activeCurrency, + filter: (prev, next) => prev?.id !== next?.id, + fn: (_, next) => next!, + target: [$activeCurrency, saveActiveCurrencyCodeFx], }); export const events = { - appStarted, currencyChanged, }; diff --git a/src/renderer/entities/price/model/price-provider-model.ts b/src/renderer/entities/price/model/price-provider-model.ts index e2a242b12b..93f66a0050 100644 --- a/src/renderer/entities/price/model/price-provider-model.ts +++ b/src/renderer/entities/price/model/price-provider-model.ts @@ -1,83 +1,98 @@ -export {}; - -// import { createEvent, createStore, forward, createEffect, sample } from 'effector'; -// -// import { PriceApiProvider } from '../lib/types'; -// import { DEFAULT_SHOW_FIAT, DEFAULT_FIAT_PROVIDER, DEFAULT_ASSETS_PRICES } from '../lib/constants'; -// import { fiatService, PriceObject } from '@renderer/shared/api/price-provider'; -// import * as currencyModel from './currency-model'; -// -// const appStarted = createEvent(); -// -// export const $fiatFlag = createStore(false); -// export const $priceProvider = createStore(null); -// export const $chainsPrices = createStore([]); -// -// const fiatFlagChanged = createEvent(); -// const priceProviderChanged = createEvent(); -// const chainsPricesRequested = createEvent(); -// -// const getFiatFlagFx = createEffect((): boolean => { -// return fiatService.getFiatFlag(DEFAULT_SHOW_FIAT); -// }); -// -// const saveFiatFlagFx = createEffect((flag: boolean) => { -// fiatService.saveFiatFlag(flag); -// }); -// -// const getPriceProviderFx = createEffect((): string => { -// return fiatService.getFiatProvider(DEFAULT_FIAT_PROVIDER); -// }); -// -// const savePriceProviderFx = createEffect((provider: string) => { -// fiatService.saveFiatProvider(provider); -// }); -// -// type FetchPrices = { -// provider: PriceApiProvider; -// currency: string; -// includeRates: boolean; -// }; -// const fetchChainsPricesFx = createEffect>( -// async ({ provider, currency, includeRates }) => { -// // const ProvidersMap: Record = { -// // [PriceApiProvider.COINGEKO]: coingekoService, -// // }; -// // -// // // const chains = chains -// // -// // return ProvidersMap[provider].getPrice(); -// }, -// ); -// -// const getChainsPricesFx = createEffect(() => { -// fiatService.getAssetsPrices(DEFAULT_ASSETS_PRICES); -// }); -// -// const saveChainsPricesFx = createEffect((prices: any[]) => { -// fiatService.saveAssetsPrices(prices); -// }); -// -// forward({ from: appStarted, to: [getFiatFlagFx, getPriceProviderFx] }); -// -// sample({ -// clock: currencyModel.$activeCurrency, -// source: $priceProvider, -// fn: (provider, currency) => ({ provider }), -// target: fetchChainsPricesFx, -// }); -// -// forward({ from: fiatFlagChanged, to: [$fiatFlag, saveFiatFlagFx] }); -// -// forward({ from: priceProviderChanged, to: [$priceProvider, savePriceProviderFx] }); -// -// sample({ -// clock: chainsPricesRequested, -// source: $priceProvider, -// target: [$priceProvider, savePriceProviderFx], -// }); -// -// export const events = { -// appStarted, -// showFiatChanged: fiatFlagChanged, -// }; +import { createEvent, createStore, forward, createEffect, sample } from 'effector'; + +import { PriceApiProvider } from '../lib/types'; +import { DEFAULT_FIAT_PROVIDER, DEFAULT_ASSETS_PRICES, DEFAULT_FIAT_FLAG } from '../lib/constants'; +import { fiatService, coingekoService, PriceObject, PriceAdapter } from '@renderer/shared/api/price-provider'; +import { kernelModel } from '@renderer/shared/core'; +import * as currencyModel from './currency-model'; +import { chainsService } from '@renderer/entities/network'; +import { nonNullable } from '@renderer/shared/lib/utils'; + +export const $fiatFlag = createStore(null); +export const $priceProvider = createStore(null); +export const $assetsPrices = createStore(null); + +const fiatFlagChanged = createEvent(); +const priceProviderChanged = createEvent(); +const assetsPricesRequested = createEvent(); + +const getFiatFlagFx = createEffect((): boolean => { + return fiatService.getFiatFlag(DEFAULT_FIAT_FLAG); +}); + +const saveFiatFlagFx = createEffect((flag: boolean) => { + fiatService.saveFiatFlag(flag); +}); + +const getPriceProviderFx = createEffect((): PriceApiProvider => { + return fiatService.getFiatProvider(DEFAULT_FIAT_PROVIDER); +}); + +const savePriceProviderFx = createEffect((provider: string) => { + fiatService.saveFiatProvider(provider); +}); + +type FetchPrices = { + provider: PriceApiProvider; + currencies: string[]; + includeRates: boolean; +}; +const fetchAssetsPricesFx = createEffect(({ provider, currencies, includeRates }) => { + const ProvidersMap: Record = { + [PriceApiProvider.COINGEKO]: coingekoService, + }; + + const priceIds = chainsService.getChainsData().reduce((acc, chain) => { + const ids = chain.assets.map((asset) => asset.priceId).filter(nonNullable); + acc.push(...ids); + + return acc; + }, []); + + return ProvidersMap[provider].getPrice(priceIds, currencies, includeRates); +}); + +const getAssetsPricesFx = createEffect((): PriceObject => { + return fiatService.getAssetsPrices(DEFAULT_ASSETS_PRICES); +}); + +const saveAssetsPricesFx = createEffect((prices: PriceObject) => { + fiatService.saveAssetsPrices(prices); +}); + +forward({ + from: kernelModel.events.appStarted, + to: [getFiatFlagFx, getPriceProviderFx, getAssetsPricesFx], +}); + +forward({ from: getFiatFlagFx.doneData, to: $fiatFlag }); + +forward({ from: getPriceProviderFx.doneData, to: $priceProvider }); + +forward({ from: getAssetsPricesFx.doneData, to: $assetsPrices }); + +sample({ + clock: [$priceProvider, currencyModel.$activeCurrency], + source: { provider: $priceProvider, currency: currencyModel.$activeCurrency }, + filter: ({ provider, currency }) => provider !== null && currency !== null, + fn: ({ provider, currency }) => { + return { provider: provider!, currencies: [currency!.coingeckoId], includeRates: true }; + }, + target: fetchAssetsPricesFx, +}); + +forward({ from: fiatFlagChanged, to: [$fiatFlag, saveFiatFlagFx] }); + +forward({ from: priceProviderChanged, to: [$priceProvider, savePriceProviderFx] }); + +forward({ from: fetchAssetsPricesFx.doneData, to: [$assetsPrices, saveAssetsPricesFx] }); + +sample({ + clock: assetsPricesRequested, + source: $priceProvider, + target: [$priceProvider, saveAssetsPricesFx], +}); + +export const events = { + showFiatChanged: fiatFlagChanged, +}; diff --git a/src/renderer/shared/api/price-provider/common/constants.ts b/src/renderer/shared/api/price-provider/common/constants.ts index ee11692bee..b3e96cd8d7 100644 --- a/src/renderer/shared/api/price-provider/common/constants.ts +++ b/src/renderer/shared/api/price-provider/common/constants.ts @@ -4,7 +4,7 @@ export const CURRENCY_URL = 'https://raw.githubusercontent.com/novasamatech/nova-wallet-android/develop/feature-currency-impl/src/main/res/raw/currencies.json'; export const CURRENCY_CONFIG_KEY = 'currency_config'; -export const ACTIVE_CURRENCY_KEY = 'currency_active'; -export const FIAT_FLAG_KEY = 'show_fiat'; +export const CURRENCY_CODE_KEY = 'currency_code'; +export const FIAT_FLAG_KEY = 'fiat_flag'; export const FIAT_PROVIDER = 'fiat_provider'; export const ASSETS_PRICES = 'assets_prices'; diff --git a/src/renderer/shared/api/price-provider/service/fiatService.ts b/src/renderer/shared/api/price-provider/service/fiatService.ts index 02b19a0b47..1d780be0d9 100644 --- a/src/renderer/shared/api/price-provider/service/fiatService.ts +++ b/src/renderer/shared/api/price-provider/service/fiatService.ts @@ -3,7 +3,7 @@ import { CurrencyConfig } from '../common/types'; import { CURRENCY_URL, CURRENCY_CONFIG_KEY, - ACTIVE_CURRENCY_KEY, + CURRENCY_CODE_KEY, FIAT_FLAG_KEY, FIAT_PROVIDER, ASSETS_PRICES, @@ -13,8 +13,8 @@ export const fiatService = { fetchCurrencyConfig, getCurrencyConfig, saveCurrencyConfig, - getActiveCurrencySymbol, - saveActiveCurrencySymbol, + getActiveCurrencyCode, + saveActiveCurrencyCode, getFiatFlag, saveFiatFlag, getFiatProvider, @@ -37,12 +37,13 @@ function saveCurrencyConfig(config: CurrencyConfig[]) { localStorageService.saveToStorage(CURRENCY_CONFIG_KEY, config); } -function getActiveCurrencySymbol(defaultSymbol: string): string { - return localStorageService.getFromStorage(ACTIVE_CURRENCY_KEY, defaultSymbol); +function getActiveCurrencyCode(defaultCode: string): string { + return localStorageService.getFromStorage(CURRENCY_CODE_KEY, defaultCode.toLowerCase()); } -function saveActiveCurrencySymbol(symbol: string) { - localStorageService.saveToStorage(ACTIVE_CURRENCY_KEY, symbol); +function saveActiveCurrencyCode(code: string) { + console.log(code); + localStorageService.saveToStorage(CURRENCY_CODE_KEY, code.toLowerCase()); } function getFiatFlag(defaultFlag: boolean): boolean { @@ -53,7 +54,7 @@ function saveFiatFlag(flag: boolean) { localStorageService.saveToStorage(FIAT_FLAG_KEY, flag); } -function getFiatProvider(defaultFiatProvider: string): string { +function getFiatProvider(defaultFiatProvider: T): T { return localStorageService.getFromStorage(FIAT_PROVIDER, defaultFiatProvider); } @@ -61,10 +62,10 @@ function saveFiatProvider(provider: string) { localStorageService.saveToStorage(FIAT_PROVIDER, provider); } -function getAssetsPrices(defaultPrices: T[]): T[] { +function getAssetsPrices(defaultPrices: T): T { return localStorageService.getFromStorage(ASSETS_PRICES, defaultPrices); } -function saveAssetsPrices(prices: T[]) { +function saveAssetsPrices(prices: T) { localStorageService.saveToStorage(ASSETS_PRICES, prices); } From 790c3e40e663f9e92c5f92a1f92eff72c04fc492 Mon Sep 17 00:00:00 2001 From: Yaroslav Grachev Date: Wed, 20 Sep 2023 11:18:43 +0300 Subject: [PATCH 3/4] chore: code style, export events --- src/renderer/entities/price/lib/constants.ts | 4 ---- .../entities/price/model/price-provider-model.ts | 4 +++- .../__tests__/localStorageService.test.ts | 10 ---------- .../shared/api/price-provider/__tests__/utils.test.ts | 4 ++-- 4 files changed, 5 insertions(+), 17 deletions(-) delete mode 100644 src/renderer/shared/api/local-storage/__tests__/localStorageService.test.ts diff --git a/src/renderer/entities/price/lib/constants.ts b/src/renderer/entities/price/lib/constants.ts index 51c589f57a..4cd26ed761 100644 --- a/src/renderer/entities/price/lib/constants.ts +++ b/src/renderer/entities/price/lib/constants.ts @@ -1,11 +1,7 @@ import { PriceApiProvider } from './types'; export const DEFAULT_FIAT_FLAG = false; - export const DEFAULT_CURRENCY_CONFIG = []; - export const DEFAULT_CURRENCY_CODE = 'usd'; - export const DEFAULT_FIAT_PROVIDER = PriceApiProvider.COINGEKO; - export const DEFAULT_ASSETS_PRICES = {}; diff --git a/src/renderer/entities/price/model/price-provider-model.ts b/src/renderer/entities/price/model/price-provider-model.ts index 93f66a0050..b520f1ea09 100644 --- a/src/renderer/entities/price/model/price-provider-model.ts +++ b/src/renderer/entities/price/model/price-provider-model.ts @@ -94,5 +94,7 @@ sample({ }); export const events = { - showFiatChanged: fiatFlagChanged, + fiatFlagChanged, + priceProviderChanged, + assetsPricesRequested, }; diff --git a/src/renderer/shared/api/local-storage/__tests__/localStorageService.test.ts b/src/renderer/shared/api/local-storage/__tests__/localStorageService.test.ts deleted file mode 100644 index 1a8d594683..0000000000 --- a/src/renderer/shared/api/local-storage/__tests__/localStorageService.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { localStorageService } from '../service/localStorageService'; - -// TODO: complete -describe('shared/api/local-storage/localStorageService', () => { - test('', () => { - localStorageService.saveToStorage('wqe', 'qwe'); - - expect(1).toEqual(1); - }); -}); diff --git a/src/renderer/shared/api/price-provider/__tests__/utils.test.ts b/src/renderer/shared/api/price-provider/__tests__/utils.test.ts index 6878d064b5..50d75ac20f 100644 --- a/src/renderer/shared/api/price-provider/__tests__/utils.test.ts +++ b/src/renderer/shared/api/price-provider/__tests__/utils.test.ts @@ -7,7 +7,7 @@ describe('api/price-provider/common', () => { expect(result).toEqual('polkadot_24h_change'); }); - test('convert price-provider from object to array', () => { + test('convert price from object to array', () => { const result = convertPriceToDBView({ kusama: { usd: { @@ -34,7 +34,7 @@ describe('api/price-provider/common', () => { expect(result.length).toEqual(4); }); - test('convert price-provider from array to object', () => { + test('convert price from array to object', () => { const result = convertPriceToObjectView([ { assetId: 'kusama', currency: 'usd', price: 19.06, change: -5.22061353514796 }, { assetId: 'kusama', currency: 'rub', price: 1795.2, change: -5.284952983744856 }, From 89e3121d542d9f8cdc7a8d0ff7b7eba5c26f9092 Mon Sep 17 00:00:00 2001 From: Yaroslav Grachev Date: Thu, 21 Sep 2023 09:05:41 +0300 Subject: [PATCH 4/4] feat: tests for models, review fixes --- src/renderer/assets/currency/currencies.json | 424 ++++++++++++++++++ src/renderer/entities/price/index.ts | 4 +- src/renderer/entities/price/lib/constants.ts | 1 - .../model/__tests__/currency-model.test.ts | 61 +++ .../__tests__/price-provider-model.test.ts | 77 ++++ .../entities/price/model/currency-model.ts | 51 +-- .../price/model/price-provider-model.ts | 35 +- ...apter.test.ts => coingeckoService.test.ts} | 2 +- .../__tests__/fiatService.test.ts | 80 ++++ .../price-provider/__tests__/utils.test.ts | 2 +- .../api/price-provider/common/constants.ts | 9 +- .../shared/api/price-provider/common/types.ts | 4 +- .../shared/api/price-provider/index.ts | 2 +- .../api/price-provider/service/fiatService.ts | 45 +- src/renderer/shared/core/index.ts | 2 +- .../shared/core/model/kernel-model.ts | 6 +- 16 files changed, 708 insertions(+), 97 deletions(-) create mode 100644 src/renderer/assets/currency/currencies.json create mode 100644 src/renderer/entities/price/model/__tests__/currency-model.test.ts create mode 100644 src/renderer/entities/price/model/__tests__/price-provider-model.test.ts rename src/renderer/shared/api/price-provider/__tests__/{coingeckoAdapter.test.ts => coingeckoService.test.ts} (95%) create mode 100644 src/renderer/shared/api/price-provider/__tests__/fiatService.test.ts diff --git a/src/renderer/assets/currency/currencies.json b/src/renderer/assets/currency/currencies.json new file mode 100644 index 0000000000..73a0966c3c --- /dev/null +++ b/src/renderer/assets/currency/currencies.json @@ -0,0 +1,424 @@ +[ + { + "code": "USD", + "name": "United States Dollar", + "symbol": "$", + "category": "fiat", + "popular": true, + "id": 0, + "coingeckoId": "usd" + }, + { + "code": "EUR", + "name": "Euro", + "symbol": "€", + "category": "fiat", + "popular": true, + "id": 1, + "coingeckoId": "eur" + }, + { + "code": "JPY", + "name": "Japanese Yen", + "symbol": "¥", + "category": "fiat", + "popular": true, + "id": 2, + "coingeckoId": "jpy" + }, + { + "code": "CNY", + "name": "Chinese Yuan", + "symbol": "¥", + "category": "fiat", + "popular": true, + "id": 3, + "coingeckoId": "cny" + }, + { + "code": "TWD", + "name": "New Taiwan dollar", + "symbol": "$", + "category": "fiat", + "popular": true, + "id": 4, + "coingeckoId": "twd" + }, + { + "code": "RUB", + "name": "Russian Ruble", + "symbol": "₽", + "category": "fiat", + "popular": true, + "id": 5, + "coingeckoId": "rub" + }, + { + "code": "AED", + "name": "United Arab Emirates dirham", + "category": "fiat", + "popular": true, + "id": 6, + "coingeckoId": "aed" + }, + { + "code": "IDR", + "name": "Indonesian Rupiah", + "category": "fiat", + "popular": true, + "id": 7, + "coingeckoId": "idr" + }, + { + "code": "KRW", + "name": "South Korean won", + "symbol": "₩", + "category": "fiat", + "popular": true, + "id": 8, + "coingeckoId": "krw" + }, + { + "code": "ARS", + "name": "Argentine Peso", + "symbol": "$", + "category": "fiat", + "popular": false, + "id": 9, + "coingeckoId": "ars" + }, + { + "code": "AUD", + "name": "Australian Dollar", + "symbol": "$", + "category": "fiat", + "popular": false, + "id": 10, + "coingeckoId": "aud" + }, + { + "code": "BDT", + "name": "Bangladeshi Taka", + "category": "fiat", + "popular": false, + "id": 11, + "coingeckoId": "bdt" + }, + { + "code": "BHD", + "name": "Bahraini Dinar", + "category": "fiat", + "popular": false, + "id": 12, + "coingeckoId": "bhd" + }, + { + "code": "BMD", + "name": "Bermudan Dollar", + "symbol": "$", + "category": "fiat", + "popular": false, + "id": 13, + "coingeckoId": "bmd" + }, + { + "code": "BRL", + "name": "Brazilian Real", + "symbol": "$", + "category": "fiat", + "popular": false, + "id": 14, + "coingeckoId": "brl" + }, + { + "code": "CAD", + "name": "Canadian Dollar", + "symbol": "$", + "category": "fiat", + "popular": false, + "id": 15, + "coingeckoId": "cad" + }, + { + "code": "CHF", + "name": "Swiss Franc", + "category": "fiat", + "popular": false, + "id": 16, + "coingeckoId": "chf" + }, + { + "code": "CLP", + "name": "Chilean Peso", + "symbol": "$", + "category": "fiat", + "popular": false, + "id": 17, + "coingeckoId": "clp" + }, + { + "code": "CZK", + "name": "Czech Koruna", + "symbol": "Kč", + "category": "fiat", + "popular": false, + "id": 18, + "coingeckoId": "czk" + }, + { + "code": "DKK", + "name": "Danish Krone", + "category": "fiat", + "popular": false, + "id": 19, + "coingeckoId": "dkk" + }, + { + "code": "GBP", + "name": "British Pound Sterling", + "symbol": "£", + "category": "fiat", + "popular": false, + "id": 20, + "coingeckoId": "gbp" + }, + { + "code": "HKD", + "name": "Hong Kong Dollar", + "symbol": "$", + "category": "fiat", + "popular": false, + "id": 21, + "coingeckoId": "hkd" + }, + { + "code": "HUF", + "name": "Hungarian Forint", + "category": "fiat", + "popular": false, + "id": 22, + "coingeckoId": "huf" + }, + { + "code": "ILS", + "name": "Israeli New Shekel", + "symbol": "₪", + "category": "fiat", + "popular": false, + "id": 23, + "coingeckoId": "ils" + }, + { + "code": "INR", + "name": "Indian Rupee", + "symbol": "₹", + "category": "fiat", + "popular": false, + "id": 24, + "coingeckoId": "inr" + }, + { + "code": "KDW", + "name": "Kuwaiti Dinar", + "category": "fiat", + "popular": false, + "id": 25, + "coingeckoId": "kdw" + }, + { + "code": "LKR", + "name": "Sri Lankan Rupee", + "category": "fiat", + "popular": false, + "id": 26, + "coingeckoId": "lkr" + }, + { + "code": "MMK", + "name": "Myanmar Kyat", + "category": "fiat", + "popular": false, + "id": 27, + "coingeckoId": "mmk" + }, + { + "code": "MXN", + "name": "Mexican Peso", + "symbol": "$", + "category": "fiat", + "popular": false, + "id": 28, + "coingeckoId": "mxn" + }, + { + "code": "MYR", + "name": "Malaysian Ringgit", + "category": "fiat", + "popular": false, + "id": 29, + "coingeckoId": "myr" + }, + { + "code": "NGN", + "name": "Nigerian Naira", + "symbol": "₦", + "category": "fiat", + "popular": false, + "id": 30, + "coingeckoId": "ngn" + }, + { + "code": "NOK", + "name": "Norwegian Krone", + "category": "fiat", + "popular": false, + "id": 31, + "coingeckoId": "nok" + }, + { + "code": "NZD", + "name": "New Zealand Dollar", + "symbol": "$", + "category": "fiat", + "popular": false, + "id": 32, + "coingeckoId": "nzd" + }, + { + "code": "PHP", + "name": "Philippine peso", + "symbol": "₱", + "category": "fiat", + "popular": false, + "id": 33, + "coingeckoId": "php" + }, + { + "code": "PKR", + "name": "Pakistani Rupee", + "category": "fiat", + "popular": false, + "id": 34, + "coingeckoId": "pkr" + }, + { + "code": "PLN", + "name": "Poland złoty", + "symbol": "zł", + "category": "fiat", + "popular": false, + "id": 35, + "coingeckoId": "pln" + }, + { + "code": "SAR", + "name": "Saudi Riyal", + "category": "fiat", + "popular": false, + "id": 36, + "coingeckoId": "sar" + }, + { + "code": "SEK", + "name": "Swedish Krona", + "category": "fiat", + "popular": false, + "id": 37, + "coingeckoId": "sek" + }, + { + "code": "SGD", + "name": "Singapore Dollar", + "symbol": "$", + "category": "fiat", + "popular": false, + "id": 38, + "coingeckoId": "sgd" + }, + { + "code": "THB", + "name": "Thai Baht", + "symbol": "฿", + "category": "fiat", + "popular": false, + "id": 39, + "coingeckoId": "thb" + }, + { + "code": "TRY", + "name": "Turkish lira", + "symbol": "₺", + "category": "fiat", + "popular": false, + "id": 40, + "coingeckoId": "try" + }, + { + "code": "UAH", + "name": "Ukrainian hryvnia", + "symbol": "₴", + "category": "fiat", + "popular": false, + "id": 41, + "coingeckoId": "uah" + }, + { + "code": "VEF", + "name": "Venezuelan bolívar", + "category": "fiat", + "popular": false, + "id": 42, + "coingeckoId": "vef" + }, + { + "code": "VND", + "name": "Vietnamese dong", + "symbol": "₫", + "category": "fiat", + "popular": false, + "id": 43, + "coingeckoId": "vnd" + }, + { + "code": "ZAR", + "name": "South African rand", + "category": "fiat", + "popular": false, + "id": 44, + "coingeckoId": "zar" + }, + { + "code": "XDR", + "name": "IMF Special Drawing Rights", + "category": "fiat", + "popular": false, + "id": 45, + "coingeckoId": "xdr" + }, + { + "code": "DOT", + "name": "Polkadot", + "category": "crypto", + "popular": true, + "id": 46, + "coingeckoId": "dot" + }, + { + "code": "BTC", + "name": "Bitcoin", + "symbol": "₿", + "category": "crypto", + "popular": true, + "id": 47, + "coingeckoId": "btc" + }, + { + "code": "ETH", + "name": "Ether", + "symbol": "Ξ", + "category": "crypto", + "popular": true, + "id": 48, + "coingeckoId": "eth" + } +] diff --git a/src/renderer/entities/price/index.ts b/src/renderer/entities/price/index.ts index 41de983ff6..26960d7f11 100644 --- a/src/renderer/entities/price/index.ts +++ b/src/renderer/entities/price/index.ts @@ -1,2 +1,2 @@ -export * as priceProviderModel from './model/price-provider-model'; -export * as currencyModel from './model/currency-model'; +export { priceProviderModel } from './model/price-provider-model'; +export { currencyModel } from './model/currency-model'; diff --git a/src/renderer/entities/price/lib/constants.ts b/src/renderer/entities/price/lib/constants.ts index 4cd26ed761..8631638b58 100644 --- a/src/renderer/entities/price/lib/constants.ts +++ b/src/renderer/entities/price/lib/constants.ts @@ -1,7 +1,6 @@ import { PriceApiProvider } from './types'; export const DEFAULT_FIAT_FLAG = false; -export const DEFAULT_CURRENCY_CONFIG = []; export const DEFAULT_CURRENCY_CODE = 'usd'; export const DEFAULT_FIAT_PROVIDER = PriceApiProvider.COINGEKO; export const DEFAULT_ASSETS_PRICES = {}; diff --git a/src/renderer/entities/price/model/__tests__/currency-model.test.ts b/src/renderer/entities/price/model/__tests__/currency-model.test.ts new file mode 100644 index 0000000000..e69ba7042f --- /dev/null +++ b/src/renderer/entities/price/model/__tests__/currency-model.test.ts @@ -0,0 +1,61 @@ +import { fork, allSettled } from 'effector'; + +import { kernelModel } from '@renderer/shared/core'; +import { currencyModel } from '../currency-model'; +import { fiatService, CurrencyItem } from '@renderer/shared/api/price-provider'; + +describe('entities/price/model/currency-model', () => { + const config: CurrencyItem[] = [ + { + code: 'EUR', + name: 'Euro', + symbol: '€', + category: 'fiat', + popular: true, + id: 1, + coingeckoId: 'eur', + }, + { + code: 'USD', + name: 'United States Dollar', + symbol: '$', + category: 'fiat', + popular: true, + id: 0, + coingeckoId: 'usd', + }, + ]; + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('should setup $currencyConfig on app start', async () => { + jest.spyOn(fiatService, 'getCurrencyConfig').mockReturnValue(config); + + const scope = fork(); + expect(scope.getState(currencyModel.$currencyConfig)).toEqual([]); + await allSettled(kernelModel.events.appStarted, { scope }); + expect(scope.getState(currencyModel.$currencyConfig)).toEqual(config); + }); + + test('should setup $activeCurrency on app start', async () => { + jest.spyOn(fiatService, 'getCurrencyConfig').mockReturnValue(config); + jest.spyOn(fiatService, 'getActiveCurrencyCode').mockReturnValue('usd'); + + const scope = fork(); + expect(scope.getState(currencyModel.$activeCurrency)).toBeNull(); + await allSettled(kernelModel.events.appStarted, { scope }); + expect(scope.getState(currencyModel.$activeCurrency)).toEqual(config[1]); + }); + + test('should change $activeCurrency when currencyChanged', async () => { + jest.spyOn(fiatService, 'getCurrencyConfig').mockReturnValue(config); + + const scope = fork(); + await allSettled(kernelModel.events.appStarted, { scope }); + await allSettled(currencyModel.events.currencyChanged, { scope, params: 1 }); + + expect(scope.getState(currencyModel.$activeCurrency)).toEqual(config[0]); + }); +}); diff --git a/src/renderer/entities/price/model/__tests__/price-provider-model.test.ts b/src/renderer/entities/price/model/__tests__/price-provider-model.test.ts new file mode 100644 index 0000000000..d137f7adc4 --- /dev/null +++ b/src/renderer/entities/price/model/__tests__/price-provider-model.test.ts @@ -0,0 +1,77 @@ +import { fork, allSettled } from 'effector'; + +import { kernelModel } from '@renderer/shared/core'; +import { fiatService, PriceObject, coingekoService } from '@renderer/shared/api/price-provider'; +import { priceProviderModel } from '../price-provider-model'; +import { PriceApiProvider } from '../../lib/types'; +import { currencyModel } from '../currency-model'; + +describe('entities/price/model/price-provider-model', () => { + const prices: PriceObject = { + kusama: { + usd: { price: 19.24, change: -4.745815232356294 }, + }, + }; + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('should setup $fiatFlag on app start', async () => { + jest.spyOn(fiatService, 'getFiatFlag').mockReturnValue(true); + + const scope = fork(); + expect(scope.getState(priceProviderModel.$fiatFlag)).toBeNull(); + await allSettled(kernelModel.events.appStarted, { scope }); + expect(scope.getState(priceProviderModel.$fiatFlag)).toEqual(true); + }); + + test('should setup $priceProvider on app start', async () => { + const provider = PriceApiProvider.COINGEKO; + jest.spyOn(fiatService, 'getPriceProvider').mockReturnValue(provider); + + const scope = fork(); + expect(scope.getState(priceProviderModel.$priceProvider)).toBeNull(); + await allSettled(kernelModel.events.appStarted, { scope }); + expect(scope.getState(priceProviderModel.$priceProvider)).toEqual(provider); + }); + + test('should setup $assetsPrices on app start', async () => { + jest.spyOn(fiatService, 'getPriceProvider').mockReturnValue(null); + jest.spyOn(fiatService, 'getAssetsPrices').mockReturnValue(prices); + + const scope = fork(); + expect(scope.getState(priceProviderModel.$assetsPrices)).toBeNull(); + await allSettled(kernelModel.events.appStarted, { scope }); + expect(scope.getState(priceProviderModel.$assetsPrices)).toEqual(prices); + }); + + test('should change $fiatFlag when fiatFlagChanged', async () => { + jest.spyOn(fiatService, 'getFiatFlag').mockReturnValue(true); + + const scope = fork(); + await allSettled(kernelModel.events.appStarted, { scope }); + await allSettled(priceProviderModel.events.fiatFlagChanged, { scope, params: false }); + expect(scope.getState(priceProviderModel.$fiatFlag)).toEqual(false); + }); + + test('should change $priceProvider when priceProviderChanged', async () => { + jest.spyOn(fiatService, 'getPriceProvider').mockReturnValue(PriceApiProvider.COINGEKO); + + const scope = fork(); + await allSettled(priceProviderModel.events.priceProviderChanged, { scope, params: 'my_provider' }); + expect(scope.getState(priceProviderModel.$priceProvider)).toEqual('my_provider'); + }); + + test('should fetch $assetsPrices when assetsPricesRequested', async () => { + jest.spyOn(coingekoService, 'getPrice').mockResolvedValue(prices); + + const scope = fork({ + values: new Map() + .set(priceProviderModel.$priceProvider, PriceApiProvider.COINGEKO) + .set(currencyModel.$activeCurrency, 'usd'), + }); + await allSettled(priceProviderModel.events.assetsPricesRequested, { scope, params: { includeRates: false } }); + expect(scope.getState(priceProviderModel.$assetsPrices)).toEqual(prices); + }); +}); diff --git a/src/renderer/entities/price/model/currency-model.ts b/src/renderer/entities/price/model/currency-model.ts index b0d2e9cdb3..617f647874 100644 --- a/src/renderer/entities/price/model/currency-model.ts +++ b/src/renderer/entities/price/model/currency-model.ts @@ -1,42 +1,33 @@ import { createEvent, createStore, createEffect, forward, sample } from 'effector'; import { kernelModel } from '@renderer/shared/core'; -import { CurrencyConfig, fiatService } from '@renderer/shared/api/price-provider'; -import { DEFAULT_CURRENCY_CODE, DEFAULT_CURRENCY_CONFIG } from '../lib/constants'; +import { CurrencyItem, fiatService } from '@renderer/shared/api/price-provider'; +import { DEFAULT_CURRENCY_CODE } from '../lib/constants'; -export const $currencyConfig = createStore([]); -export const $activeCurrency = createStore(null); +const $currencyConfig = createStore([]); +const $activeCurrency = createStore(null); +const $activeCurrencyCode = createStore(null); -const $activeCurrencyCode = createStore(null); +const currencyChanged = createEvent(); -const currencyChanged = createEvent(); - -const getCurrencyConfigFx = createEffect((): CurrencyConfig[] => { - return fiatService.getCurrencyConfig(DEFAULT_CURRENCY_CONFIG); -}); - -const fetchCurrencyConfigFx = createEffect((): Promise => { - return fiatService.fetchCurrencyConfig(); -}); - -const saveCurrencyConfigFx = createEffect((config: CurrencyConfig[]) => { - fiatService.saveCurrencyConfig(config); +const getCurrencyConfigFx = createEffect((): CurrencyItem[] => { + return fiatService.getCurrencyConfig(); }); const getActiveCurrencyCodeFx = createEffect((): string => { return fiatService.getActiveCurrencyCode(DEFAULT_CURRENCY_CODE); }); -const saveActiveCurrencyCodeFx = createEffect((currency: CurrencyConfig) => { +const saveActiveCurrencyCodeFx = createEffect((currency: CurrencyItem) => { fiatService.saveActiveCurrencyCode(currency.code); }); type ChangeParams = { - id?: CurrencyConfig['id']; - code?: CurrencyConfig['code']; - config: CurrencyConfig[]; + id?: CurrencyItem['id']; + code?: CurrencyItem['code']; + config: CurrencyItem[]; }; -const currencyChangedFx = createEffect(({ id, code, config }) => { +const currencyChangedFx = createEffect(({ id, code, config }) => { return config.find((currency) => { const hasId = currency.id === id; const hasCode = currency.code.toLowerCase() === code?.toLowerCase(); @@ -47,19 +38,17 @@ const currencyChangedFx = createEffect forward({ from: kernelModel.events.appStarted, - to: [getActiveCurrencyCodeFx, getCurrencyConfigFx, fetchCurrencyConfigFx], + to: [getActiveCurrencyCodeFx, getCurrencyConfigFx], }); forward({ from: getActiveCurrencyCodeFx.doneData, to: $activeCurrencyCode }); forward({ from: getCurrencyConfigFx.doneData, to: $currencyConfig }); -forward({ from: fetchCurrencyConfigFx.doneData, to: [$currencyConfig, saveCurrencyConfigFx] }); - sample({ - clock: [getCurrencyConfigFx.doneData, fetchCurrencyConfigFx.doneData], + clock: getCurrencyConfigFx.doneData, source: $activeCurrencyCode, - filter: (code: CurrencyConfig['code'] | null): code is CurrencyConfig['code'] => Boolean(code), + filter: (code: CurrencyItem['code'] | null): code is CurrencyItem['code'] => Boolean(code), fn: (code, config) => ({ code, config }), target: currencyChangedFx, }); @@ -79,6 +68,10 @@ sample({ target: [$activeCurrency, saveActiveCurrencyCodeFx], }); -export const events = { - currencyChanged, +export const currencyModel = { + $currencyConfig, + $activeCurrency, + events: { + currencyChanged, + }, }; diff --git a/src/renderer/entities/price/model/price-provider-model.ts b/src/renderer/entities/price/model/price-provider-model.ts index b520f1ea09..30ebb38ed8 100644 --- a/src/renderer/entities/price/model/price-provider-model.ts +++ b/src/renderer/entities/price/model/price-provider-model.ts @@ -4,17 +4,17 @@ import { PriceApiProvider } from '../lib/types'; import { DEFAULT_FIAT_PROVIDER, DEFAULT_ASSETS_PRICES, DEFAULT_FIAT_FLAG } from '../lib/constants'; import { fiatService, coingekoService, PriceObject, PriceAdapter } from '@renderer/shared/api/price-provider'; import { kernelModel } from '@renderer/shared/core'; -import * as currencyModel from './currency-model'; import { chainsService } from '@renderer/entities/network'; import { nonNullable } from '@renderer/shared/lib/utils'; +import { currencyModel } from './currency-model'; -export const $fiatFlag = createStore(null); -export const $priceProvider = createStore(null); -export const $assetsPrices = createStore(null); +const $fiatFlag = createStore(null); +const $priceProvider = createStore(null); +const $assetsPrices = createStore(null); const fiatFlagChanged = createEvent(); const priceProviderChanged = createEvent(); -const assetsPricesRequested = createEvent(); +const assetsPricesRequested = createEvent<{ includeRates: boolean }>(); const getFiatFlagFx = createEffect((): boolean => { return fiatService.getFiatFlag(DEFAULT_FIAT_FLAG); @@ -25,11 +25,11 @@ const saveFiatFlagFx = createEffect((flag: boolean) => { }); const getPriceProviderFx = createEffect((): PriceApiProvider => { - return fiatService.getFiatProvider(DEFAULT_FIAT_PROVIDER); + return fiatService.getPriceProvider(DEFAULT_FIAT_PROVIDER); }); const savePriceProviderFx = createEffect((provider: string) => { - fiatService.saveFiatProvider(provider); + fiatService.savePriceProvider(provider); }); type FetchPrices = { @@ -72,7 +72,7 @@ forward({ from: getPriceProviderFx.doneData, to: $priceProvider }); forward({ from: getAssetsPricesFx.doneData, to: $assetsPrices }); sample({ - clock: [$priceProvider, currencyModel.$activeCurrency], + clock: [assetsPricesRequested, $priceProvider, currencyModel.$activeCurrency], source: { provider: $priceProvider, currency: currencyModel.$activeCurrency }, filter: ({ provider, currency }) => provider !== null && currency !== null, fn: ({ provider, currency }) => { @@ -87,14 +87,13 @@ forward({ from: priceProviderChanged, to: [$priceProvider, savePriceProviderFx] forward({ from: fetchAssetsPricesFx.doneData, to: [$assetsPrices, saveAssetsPricesFx] }); -sample({ - clock: assetsPricesRequested, - source: $priceProvider, - target: [$priceProvider, saveAssetsPricesFx], -}); - -export const events = { - fiatFlagChanged, - priceProviderChanged, - assetsPricesRequested, +export const priceProviderModel = { + $fiatFlag, + $priceProvider, + $assetsPrices, + events: { + fiatFlagChanged, + priceProviderChanged, + assetsPricesRequested, + }, }; diff --git a/src/renderer/shared/api/price-provider/__tests__/coingeckoAdapter.test.ts b/src/renderer/shared/api/price-provider/__tests__/coingeckoService.test.ts similarity index 95% rename from src/renderer/shared/api/price-provider/__tests__/coingeckoAdapter.test.ts rename to src/renderer/shared/api/price-provider/__tests__/coingeckoService.test.ts index 82fb2f757d..933d4021ef 100644 --- a/src/renderer/shared/api/price-provider/__tests__/coingeckoAdapter.test.ts +++ b/src/renderer/shared/api/price-provider/__tests__/coingeckoService.test.ts @@ -1,6 +1,6 @@ import { coingekoService } from '../service/coingeckoService'; -describe('shared/api/price-provider/coinGeckoAdapter', () => { +describe('shared/api/price-provider/services/coingekoService', () => { test('get price-provider from coingecko', async () => { global.fetch = jest.fn(() => Promise.resolve({ diff --git a/src/renderer/shared/api/price-provider/__tests__/fiatService.test.ts b/src/renderer/shared/api/price-provider/__tests__/fiatService.test.ts new file mode 100644 index 0000000000..35f35c90bb --- /dev/null +++ b/src/renderer/shared/api/price-provider/__tests__/fiatService.test.ts @@ -0,0 +1,80 @@ +import { fiatService } from '../service/fiatService'; +import { localStorageService } from '@renderer/shared/api/local-storage'; +import { CURRENCY_CODE_KEY, FIAT_FLAG_KEY, PRICE_PROVIDER_KEY, ASSETS_PRICES_KEY } from '../common/constants'; + +describe('shared/api/price-provider/services/fiatService', () => { + const spyGetFn = (value: any) => jest.spyOn(localStorageService, 'getFromStorage').mockReturnValue(value); + const spySaveFn = () => jest.spyOn(localStorageService, 'saveToStorage').mockImplementation(); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('getActiveCurrencyCode should return value', () => { + const spyGet = spyGetFn('usd'); + + const defaultValue = 'code'; + fiatService.getActiveCurrencyCode(defaultValue); + expect(spyGet).toHaveBeenCalledWith(CURRENCY_CODE_KEY, defaultValue); + expect(spyGet).toReturnWith('usd'); + }); + + test('saveActiveCurrencyCode should save value', () => { + const spySave = spySaveFn(); + + const value = 'usd'; + fiatService.saveActiveCurrencyCode(value); + expect(spySave).toHaveBeenCalledWith(CURRENCY_CODE_KEY, value); + }); + + test('getFiatFlag should return value', () => { + const spyGet = spyGetFn(true); + + const defaultValue = false; + fiatService.getFiatFlag(defaultValue); + expect(spyGet).toHaveBeenCalledWith(FIAT_FLAG_KEY, defaultValue); + expect(spyGet).toReturnWith(true); + }); + + test('saveFiatFlag should save value', () => { + const spyGet = spySaveFn(); + + const value = false; + fiatService.saveFiatFlag(value); + expect(spyGet).toHaveBeenCalledWith(FIAT_FLAG_KEY, value); + }); + + test('getPriceProvider should return value', () => { + const spyGet = spyGetFn('coingeko'); + + const defaultValue = 'coinbase'; + fiatService.getPriceProvider(defaultValue); + expect(spyGet).toHaveBeenCalledWith(PRICE_PROVIDER_KEY, defaultValue); + expect(spyGet).toReturnWith('coingeko'); + }); + + test('saveFiatProvider should save value', () => { + const spyGet = spySaveFn(); + + const value = 'coinbase'; + fiatService.savePriceProvider(value); + expect(spyGet).toHaveBeenCalledWith(PRICE_PROVIDER_KEY, value); + }); + + test('getAssetsPrices should return value', () => { + const spyGet = spyGetFn({ acala: '100' }); + + const defaultValue = { polkadot: '200' }; + fiatService.getAssetsPrices(defaultValue); + expect(spyGet).toHaveBeenCalledWith(ASSETS_PRICES_KEY, defaultValue); + expect(spyGet).toReturnWith({ acala: '100' }); + }); + + test('saveAssetsPrices should save value', () => { + const spyGet = spySaveFn(); + + const value = { polkadot: '200' }; + fiatService.saveAssetsPrices(value); + expect(spyGet).toHaveBeenCalledWith(ASSETS_PRICES_KEY, value); + }); +}); diff --git a/src/renderer/shared/api/price-provider/__tests__/utils.test.ts b/src/renderer/shared/api/price-provider/__tests__/utils.test.ts index 50d75ac20f..04ef94b414 100644 --- a/src/renderer/shared/api/price-provider/__tests__/utils.test.ts +++ b/src/renderer/shared/api/price-provider/__tests__/utils.test.ts @@ -1,6 +1,6 @@ import { convertPriceToObjectView, convertPriceToDBView, getCurrencyChangeKey } from '../common/utils'; -describe('api/price-provider/common', () => { +describe('shared/api/price-provider/common/utils', () => { test('get correct change key', () => { const result = getCurrencyChangeKey('polkadot'); diff --git a/src/renderer/shared/api/price-provider/common/constants.ts b/src/renderer/shared/api/price-provider/common/constants.ts index b3e96cd8d7..d137549499 100644 --- a/src/renderer/shared/api/price-provider/common/constants.ts +++ b/src/renderer/shared/api/price-provider/common/constants.ts @@ -1,10 +1,5 @@ export const COINGECKO_URL = 'https://api.coingecko.com/api/v3'; - -export const CURRENCY_URL = - 'https://raw.githubusercontent.com/novasamatech/nova-wallet-android/develop/feature-currency-impl/src/main/res/raw/currencies.json'; - -export const CURRENCY_CONFIG_KEY = 'currency_config'; export const CURRENCY_CODE_KEY = 'currency_code'; export const FIAT_FLAG_KEY = 'fiat_flag'; -export const FIAT_PROVIDER = 'fiat_provider'; -export const ASSETS_PRICES = 'assets_prices'; +export const PRICE_PROVIDER_KEY = 'price_provider'; +export const ASSETS_PRICES_KEY = 'assets_prices'; diff --git a/src/renderer/shared/api/price-provider/common/types.ts b/src/renderer/shared/api/price-provider/common/types.ts index 977fd8a04d..b941f5d3f2 100644 --- a/src/renderer/shared/api/price-provider/common/types.ts +++ b/src/renderer/shared/api/price-provider/common/types.ts @@ -20,11 +20,11 @@ export type PriceAdapter = { getHistoryData: (id: AssetId, currency: Currency, from: number, to: number) => Promise; }; -export type CurrencyConfig = { +export type CurrencyItem = { id: number; code: string; name: string; - symbol: string; + symbol?: string; category: 'fiat' | 'crypto'; popular: boolean; coingeckoId: string; diff --git a/src/renderer/shared/api/price-provider/index.ts b/src/renderer/shared/api/price-provider/index.ts index ad074ffca1..c046960a7d 100644 --- a/src/renderer/shared/api/price-provider/index.ts +++ b/src/renderer/shared/api/price-provider/index.ts @@ -1,3 +1,3 @@ export { coingekoService } from './service/coingeckoService'; export { fiatService } from './service/fiatService'; -export type { CurrencyConfig, PriceAdapter, PriceObject } from './common/types'; +export type { CurrencyItem, PriceAdapter, PriceObject } from './common/types'; diff --git a/src/renderer/shared/api/price-provider/service/fiatService.ts b/src/renderer/shared/api/price-provider/service/fiatService.ts index 1d780be0d9..16ae148096 100644 --- a/src/renderer/shared/api/price-provider/service/fiatService.ts +++ b/src/renderer/shared/api/price-provider/service/fiatService.ts @@ -1,40 +1,22 @@ +import CURRENCY from '@renderer/assets/currency/currencies.json'; import { localStorageService } from '@renderer/shared/api/local-storage'; -import { CurrencyConfig } from '../common/types'; -import { - CURRENCY_URL, - CURRENCY_CONFIG_KEY, - CURRENCY_CODE_KEY, - FIAT_FLAG_KEY, - FIAT_PROVIDER, - ASSETS_PRICES, -} from '../common/constants'; +import { CurrencyItem } from '../common/types'; +import { CURRENCY_CODE_KEY, FIAT_FLAG_KEY, PRICE_PROVIDER_KEY, ASSETS_PRICES_KEY } from '../common/constants'; export const fiatService = { - fetchCurrencyConfig, getCurrencyConfig, - saveCurrencyConfig, getActiveCurrencyCode, saveActiveCurrencyCode, getFiatFlag, saveFiatFlag, - getFiatProvider, - saveFiatProvider, + getPriceProvider, + savePriceProvider, getAssetsPrices, saveAssetsPrices, }; -async function fetchCurrencyConfig(): Promise { - const response = await fetch(CURRENCY_URL, { cache: 'default' }); - - return response.json(); -} - -function getCurrencyConfig(defaultConfig: CurrencyConfig[]): CurrencyConfig[] { - return localStorageService.getFromStorage(CURRENCY_CONFIG_KEY, defaultConfig); -} - -function saveCurrencyConfig(config: CurrencyConfig[]) { - localStorageService.saveToStorage(CURRENCY_CONFIG_KEY, config); +function getCurrencyConfig(): CurrencyItem[] { + return CURRENCY as CurrencyItem[]; } function getActiveCurrencyCode(defaultCode: string): string { @@ -42,7 +24,6 @@ function getActiveCurrencyCode(defaultCode: string): string { } function saveActiveCurrencyCode(code: string) { - console.log(code); localStorageService.saveToStorage(CURRENCY_CODE_KEY, code.toLowerCase()); } @@ -54,18 +35,18 @@ function saveFiatFlag(flag: boolean) { localStorageService.saveToStorage(FIAT_FLAG_KEY, flag); } -function getFiatProvider(defaultFiatProvider: T): T { - return localStorageService.getFromStorage(FIAT_PROVIDER, defaultFiatProvider); +function getPriceProvider(defaultFiatProvider: T): T { + return localStorageService.getFromStorage(PRICE_PROVIDER_KEY, defaultFiatProvider); } -function saveFiatProvider(provider: string) { - localStorageService.saveToStorage(FIAT_PROVIDER, provider); +function savePriceProvider(provider: string) { + localStorageService.saveToStorage(PRICE_PROVIDER_KEY, provider); } function getAssetsPrices(defaultPrices: T): T { - return localStorageService.getFromStorage(ASSETS_PRICES, defaultPrices); + return localStorageService.getFromStorage(ASSETS_PRICES_KEY, defaultPrices); } function saveAssetsPrices(prices: T) { - localStorageService.saveToStorage(ASSETS_PRICES, prices); + localStorageService.saveToStorage(ASSETS_PRICES_KEY, prices); } diff --git a/src/renderer/shared/core/index.ts b/src/renderer/shared/core/index.ts index a66d38a5a4..36fbc281e9 100644 --- a/src/renderer/shared/core/index.ts +++ b/src/renderer/shared/core/index.ts @@ -1 +1 @@ -export * as kernelModel from './model/kernel-model'; +export { kernelModel } from './model/kernel-model'; diff --git a/src/renderer/shared/core/model/kernel-model.ts b/src/renderer/shared/core/model/kernel-model.ts index 35c883166d..b827920a77 100644 --- a/src/renderer/shared/core/model/kernel-model.ts +++ b/src/renderer/shared/core/model/kernel-model.ts @@ -2,6 +2,8 @@ import { createEvent } from 'effector'; const appStarted = createEvent(); -export const events = { - appStarted, +export const kernelModel = { + events: { + appStarted, + }, };