1 import { createSlice } from '@reduxjs/toolkit';
3 import type { ModelState } from '@proton/account';
4 import { getInitialModelState } from '@proton/account';
5 import type { WasmApiExchangeRate, WasmFiatCurrencySymbol } from '@proton/andromeda';
6 import { createAsyncModelThunk, handleAsyncModel } from '@proton/redux-utilities';
7 import { SECOND } from '@proton/shared/lib/constants';
8 import { type SimpleMap } from '@proton/shared/lib/interfaces';
10 import type { WalletThunkArguments } from '../thunk';
12 const name = 'exchange_rate' as const;
14 type WasmApiExchangeRateByFiat = SimpleMap<WasmApiExchangeRate>;
16 export interface ExchangeRateState {
17 [name]: ModelState<WasmApiExchangeRateByFiat>;
20 type SliceState = ExchangeRateState[typeof name];
21 type Model = NonNullable<SliceState['value']>;
23 export const selectExchangeRate = (state: ExchangeRateState) => {
27 const SEPARATOR = '-';
29 export const serialiseKey = (fiat: WasmFiatCurrencySymbol, timestamp?: number) => {
30 return timestamp ? `${fiat}${SEPARATOR}${Number(timestamp)}` : fiat;
32 export const parseKey = (key: string) => {
33 const [fiat, timestamp] = key.split(SEPARATOR) as [WasmFiatCurrencySymbol, string?];
37 timestamp: timestamp ? Number(timestamp) : undefined,
41 export const getKeyAndTs = (fiat: WasmFiatCurrencySymbol, date?: Date) => {
42 const ts = date?.getTime();
43 const key = serialiseKey(fiat, ts);
45 // API expects ts in seconds
46 const tsInSeconds = ts && Math.floor(ts / SECOND);
48 return [key, tsInSeconds] as const;
51 const modelThunk = createAsyncModelThunk<
55 [WasmFiatCurrencySymbol, Date?]
57 miss: async ({ extraArgument, options, getState }) => {
58 const stateValue = getState()[name].value;
59 if (!options?.thunkArg) {
60 return stateValue ?? {};
63 const fiat = options?.thunkArg?.[0] ?? 'USD';
64 const date = options?.thunkArg?.[1];
66 const [key, ts] = getKeyAndTs(fiat, date);
69 const exchangeRate = await extraArgument.walletApi
71 .exchange_rate.getExchangeRate(fiat, ts ? BigInt(ts) : undefined)
81 return stateValue ?? {};
84 previous: ({ getState, options }) => {
85 const state = getState()[name];
87 if (!options?.thunkArg || !state.value) {
91 const [fiat, date] = options?.thunkArg;
92 const [key] = getKeyAndTs(fiat, date);
94 if (state.value[key]) {
102 const initialState = getInitialModelState<Model>();
104 const slice = createSlice({
108 extraReducers: (builder) => {
109 handleAsyncModel(builder, modelThunk);
113 export const exchangeRateReducer = { [name]: slice.reducer };
114 export const exchangeRateThunk = modelThunk.thunk;