Skip to content

Commit 5ffa0d1

Browse files
committed
feat(trading): new trading selectors
1 parent 5c0da71 commit 5ffa0d1

File tree

10 files changed

+368
-28
lines changed

10 files changed

+368
-28
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"bitcoin": {
3+
"symbol": "btc",
4+
"name": "Bitcoin",
5+
"coingeckoId": "bitcoin",
6+
"services": {
7+
"buy": true,
8+
"sell": true,
9+
"exchange": true
10+
}
11+
},
12+
"ethereum": {
13+
"symbol": "eth",
14+
"name": "Ethereum",
15+
"coingeckoId": "ethereum",
16+
"services": {
17+
"buy": true,
18+
"sell": true,
19+
"exchange": true
20+
}
21+
},
22+
"ethereum--0x07150e919b4de5fd6a63de1f9384828396f25fdc": {
23+
"symbol": "base",
24+
"name": "Base Protocol",
25+
"coingeckoId": "base-protocol",
26+
"services": {
27+
"buy": false,
28+
"sell": false,
29+
"exchange": true
30+
}
31+
},
32+
"ethereum--0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": {
33+
"symbol": "usdc",
34+
"name": "USDC",
35+
"coingeckoId": "usd-coin",
36+
"services": {
37+
"buy": true,
38+
"sell": true,
39+
"exchange": true
40+
}
41+
},
42+
"base--0x0000000000000000000000000000000000000000": {
43+
"symbol": "eth",
44+
"name": "Ethereum",
45+
"coingeckoId": "ethereum",
46+
"services": {
47+
"buy": true,
48+
"sell": false,
49+
"exchange": true
50+
}
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"ethereum": {
3+
"id": "ethereum",
4+
"name": "Ethereum",
5+
"nativeCoinSymbol": "eth"
6+
},
7+
"base": {
8+
"id": "base",
9+
"name": "Base",
10+
"nativeCoinSymbol": "eth"
11+
}
12+
}

suite-common/trading/src/hooks/useTradingInfo.ts

+15-15
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { useCallback, useMemo } from 'react';
33
import { CoinInfo, CryptoId } from 'invity-api';
44

55
import {
6-
NetworkSymbolExtended,
76
getDisplaySymbol,
87
getNetwork,
98
getNetworkByTradeCryptoId,
@@ -24,6 +23,13 @@ import {
2423
testnetToProdCryptoId,
2524
} from '../utils';
2625
import { useSelector } from './useSelector';
26+
import {
27+
getCoinInfoByCryptoId,
28+
getCoinSymbolByCryptoId,
29+
getNativeCoinSymbolByCryptoId,
30+
getPlatformsInfoByCryptoId,
31+
getSymbolAndContractAddressByCryptoId,
32+
} from '../utils/infoUtils';
2733

2834
const supportedAddressValidatorSymbols = new Set(
2935
addressValidator.getCurrencies().map(c => c.symbol),
@@ -70,8 +76,6 @@ const sortPopularCurrencies = (
7076
/**
7177
* TODO: trading - delete section after migration
7278
*
73-
* TODO: trading - cryptoIdToPlatformName, cryptoIdToCoinName, cryptoIdToNativeCoinSymbol, cryptoIdToCoinSymbol, cryptoIdToSymbolAndContractAddress - could be refactored to selectors
74-
*
7579
* @param section used only for purpose in refactored desktop trading
7680
*/
7781
export const useTradingInfo = (section?: TradingType): TradingInfoProps => {
@@ -97,31 +101,27 @@ export const useTradingInfo = (section?: TradingType): TradingInfoProps => {
97101
);
98102

99103
const cryptoIdToPlatformName = useCallback(
100-
(cryptoId: CryptoId) => platforms[cryptoId]?.name,
104+
(cryptoId: CryptoId) => getPlatformsInfoByCryptoId(platforms, cryptoId)?.name,
101105
[platforms],
102106
);
103107

104-
const cryptoIdToCoinName = useCallback((cryptoId: CryptoId) => coins[cryptoId]?.name, [coins]);
108+
const cryptoIdToCoinName = useCallback(
109+
(cryptoId: CryptoId) => getCoinInfoByCryptoId(coins, cryptoId)?.name,
110+
[coins],
111+
);
105112

106113
const cryptoIdToNativeCoinSymbol = useCallback(
107-
(cryptoId: CryptoId) => {
108-
const { networkId } = parseCryptoId(cryptoId);
109-
110-
return platforms[networkId]?.nativeCoinSymbol ?? coins[networkId]?.symbol;
111-
},
114+
(cryptoId: CryptoId) => getNativeCoinSymbolByCryptoId(platforms, coins, cryptoId),
112115
[platforms, coins],
113116
);
114117

115118
const cryptoIdToCoinSymbol = useCallback(
116-
(cryptoId: CryptoId) => coins[cryptoId]?.symbol?.toUpperCase(),
119+
(cryptoId: CryptoId) => getCoinSymbolByCryptoId(coins, cryptoId),
117120
[coins],
118121
);
119122

120123
const cryptoIdToSymbolAndContractAddress = useCallback(
121-
(cryptoId: CryptoId | undefined) => ({
122-
coinSymbol: cryptoId && (coins[cryptoId]?.symbol as NetworkSymbolExtended | undefined),
123-
contractAddress: cryptoId && parseCryptoId(cryptoId).contractAddress,
124-
}),
124+
(cryptoId: CryptoId | undefined) => getSymbolAndContractAddressByCryptoId(coins, cryptoId),
125125
[coins],
126126
);
127127

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { Coins, CryptoId, Platforms } from 'invity-api';
2+
3+
import coins from '../../__fixtures__/coins.json';
4+
import platforms from '../../__fixtures__/platforms.json';
5+
import { initialState } from '../../reducers/tradingReducer';
6+
import { TradingPaymentMethodListProps } from '../../types';
7+
import {
8+
TradingRootState,
9+
selectCoinInfoByCryptoId,
10+
selectCoinSymbolByCryptoId,
11+
selectNativeCoinSymbolByCryptoId,
12+
selectPlatformByCryptoId,
13+
selectSymbolAndContractAddressByCryptoId,
14+
selectTradingBuyQuotesRequest,
15+
selectTradingBuySelectedQuote,
16+
} from '../tradingSelectors';
17+
18+
describe('tradingSelectors', () => {
19+
const state = {
20+
wallet: {
21+
tradingNew: {
22+
...initialState,
23+
buy: {
24+
...initialState.buy,
25+
quotesRequest: {
26+
wantCrypto: true,
27+
fiatCurrency: 'fiatCurrency',
28+
paymentMethod: 'eps',
29+
receiveCurrency: 'bitcoin',
30+
},
31+
selectedQuote: {
32+
paymentMethod: 'eps',
33+
},
34+
},
35+
info: {
36+
paymentMethods: [] as TradingPaymentMethodListProps[],
37+
coins: coins as Coins,
38+
platforms: platforms as Platforms,
39+
},
40+
},
41+
},
42+
} as TradingRootState;
43+
44+
it('selectTradingBuyQuotesRequest should return correct data', () => {
45+
expect(selectTradingBuyQuotesRequest(state)).toBe(
46+
state.wallet.tradingNew.buy.quotesRequest,
47+
);
48+
});
49+
50+
it('selectTradingBuySelectedQuote should return correct data', () => {
51+
expect(selectTradingBuySelectedQuote(state)).toBe(
52+
state.wallet.tradingNew.buy.selectedQuote,
53+
);
54+
});
55+
56+
it('selectCoinInfoByCryptoId should return coin data', () => {
57+
expect(selectCoinInfoByCryptoId(state, 'bitcoin' as CryptoId)).toEqual({
58+
symbol: 'btc',
59+
name: 'Bitcoin',
60+
coingeckoId: 'bitcoin',
61+
services: {
62+
buy: true,
63+
sell: true,
64+
exchange: true,
65+
},
66+
});
67+
});
68+
69+
it('selectCoinSymbolByCryptoId should return coin symbol', () => {
70+
expect(selectCoinSymbolByCryptoId(state, 'bitcoin' as CryptoId)).toBe('BTC');
71+
});
72+
73+
it('selectPlatformByCryptoId should return platform data', () => {
74+
expect(selectPlatformByCryptoId(state, 'ethereum' as CryptoId)).toEqual({
75+
id: 'ethereum',
76+
name: 'Ethereum',
77+
nativeCoinSymbol: 'eth',
78+
});
79+
});
80+
81+
it.each([
82+
['bitcoin', 'btc'],
83+
['ethereum', 'eth'],
84+
['ethereum--0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', 'eth'],
85+
] as [CryptoId, string][])(
86+
'selectNativeCoinSymbolByCryptoId should return native coin symbol for cryptoId [%s]',
87+
(cryptoId, expected) => {
88+
expect(selectNativeCoinSymbolByCryptoId(state, cryptoId)).toBe(expected);
89+
},
90+
);
91+
92+
describe('selectSymbolAndContractAddressByCryptoId', () => {
93+
it.each([
94+
['bitcoin', { coinSymbol: 'btc', contractAddress: undefined }],
95+
[
96+
'ethereum--0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
97+
{
98+
coinSymbol: 'usdc',
99+
contractAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
100+
},
101+
],
102+
] as [CryptoId, { coinSymbol: string; contractAddress: string }][])(
103+
'should return correct data for cryptoId [%s]',
104+
(cryptoId, expectedResult) => {
105+
expect(selectSymbolAndContractAddressByCryptoId(state, cryptoId)).toEqual(
106+
expectedResult,
107+
);
108+
},
109+
);
110+
111+
it('should be stable', () => {
112+
expect(selectSymbolAndContractAddressByCryptoId(state, 'bitcoin' as CryptoId)).toBe(
113+
selectSymbolAndContractAddressByCryptoId(state, 'bitcoin' as CryptoId),
114+
);
115+
});
116+
});
117+
});

suite-common/trading/src/selectors/tradingSelectors.ts

+44-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
1-
import { CryptoId, FiatCurrencyCode } from 'invity-api';
1+
import { Coins, CryptoId, FiatCurrencyCode } from 'invity-api';
22

33
import { createWeakMapSelector, returnStableArrayIfEmpty } from '@suite-common/redux-utils';
4+
import { NetworkSymbolExtended } from '@suite-common/wallet-config/libDev/src';
45
import { Account, SelectedAccountStatus } from '@suite-common/wallet-types';
56
import { AddressDisplayOptions } from '@suite-common/wallet-types/src/settings';
67

78
import { BuyInfo, TradingBuyState } from '../reducers/buyReducer';
89
import type { TradingInfo, TradingState } from '../reducers/tradingReducer';
910
import { InvityServerEnvironment, TradingFiatCurrenciesProps } from '../types';
11+
import {
12+
getCoinInfoByCryptoId,
13+
getCoinSymbolByCryptoId,
14+
getNativeCoinSymbolByCryptoId,
15+
getPlatformsInfoByCryptoId,
16+
getSymbolAndContractAddressByCryptoId,
17+
} from '../utils/infoUtils';
1018

1119
// partial copy of Suite state
1220
export type TradingRootState = {
@@ -115,8 +123,41 @@ export const selectTradingPaymentMethods = (state: TradingRootState) =>
115123
export const selectTradingTrades = (state: TradingRootState) =>
116124
returnStableArrayIfEmpty(state.wallet.tradingNew.trades);
117125

118-
export const cryptoIdToCoinSymbol = (state: TradingRootState, cryptoId: CryptoId) => {
126+
export const selectCoinInfoByCryptoId = (state: TradingRootState, cryptoId: CryptoId) => {
119127
const { coins = {} } = state.wallet.tradingNew.info;
120128

121-
return coins[cryptoId]?.symbol?.toUpperCase();
129+
return getCoinInfoByCryptoId(coins, cryptoId);
122130
};
131+
132+
export const selectCoinSymbolByCryptoId = (state: TradingRootState, cryptoId: CryptoId) => {
133+
const { coins = {} } = state.wallet.tradingNew.info;
134+
135+
return getCoinSymbolByCryptoId(coins, cryptoId);
136+
};
137+
138+
export const selectPlatformByCryptoId = (state: TradingRootState, cryptoId: CryptoId) => {
139+
const { platforms = {} } = state.wallet.tradingNew.info;
140+
141+
return getPlatformsInfoByCryptoId(platforms, cryptoId);
142+
};
143+
144+
export const selectNativeCoinSymbolByCryptoId = (state: TradingRootState, cryptoId: CryptoId) => {
145+
const { coins = {}, platforms = {} } = state.wallet.tradingNew.info;
146+
147+
return getNativeCoinSymbolByCryptoId(platforms, coins, cryptoId);
148+
};
149+
150+
export const selectSymbolAndContractAddressByCryptoId: (
151+
state: TradingRootState,
152+
cryptoId: CryptoId,
153+
) => {
154+
coinSymbol: NetworkSymbolExtended | undefined;
155+
contractAddress: string | undefined;
156+
} = createMemoizedSelector(
157+
[
158+
({ wallet }: TradingRootState, _: CryptoId): Coins | undefined =>
159+
wallet.tradingNew.info.coins,
160+
(_: TradingRootState, cryptoId: CryptoId): CryptoId => cryptoId,
161+
],
162+
getSymbolAndContractAddressByCryptoId,
163+
);

suite-common/trading/src/thunks/buy/handleRequestThunk.ts

+4-7
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { invityAPI } from '../../invityAPI';
1010
import { tradingBuyActions } from '../../reducers/buyReducer';
1111
import { tradingActions } from '../../reducers/tradingReducer';
1212
import {
13-
cryptoIdToCoinSymbol,
13+
selectCoinSymbolByCryptoId,
1414
selectTradingBuyQuotesRequest,
1515
} from '../../selectors/tradingSelectors';
1616
import type { TradingBuyFormProps, TradingBuyType } from '../../types';
@@ -30,11 +30,8 @@ type GetQuotesRequest = {
3030
signal: AbortSignal | null;
3131
};
3232

33-
const getQuotesRequest = async ({ requestData, signal }: GetQuotesRequest) => {
34-
const allQuotes = await invityAPI.getBuyQuotes(requestData, signal);
35-
36-
return allQuotes;
37-
};
33+
const getQuotesRequest = ({ requestData, signal }: GetQuotesRequest) =>
34+
invityAPI.getBuyQuotes(requestData, signal);
3835

3936
type GetQuoteRequestData = {
4037
formValues: TradingBuyFormProps;
@@ -130,7 +127,7 @@ export const handleRequestThunk = createThunk(
130127
const paymentMethodsFromQuotes = getTradingPaymentMethods<TradingBuyType>(quotesSuccess);
131128

132129
const symbol =
133-
cryptoIdToCoinSymbol(getState(), requestData.receiveCurrency) ??
130+
selectCoinSymbolByCryptoId(getState(), requestData.receiveCurrency) ??
134131
requestData.receiveCurrency;
135132
const limits = buyUtils.getAmountLimits({
136133
request: requestData,

suite-common/trading/src/thunks/buy/selectQuoteThunk.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { TRADING_BUY_THUNK_PREFIX } from '../../constants';
88
import { invityAPI } from '../../invityAPI';
99
import { tradingBuyActions } from '../../reducers/buyReducer';
1010
import {
11-
cryptoIdToCoinSymbol,
11+
selectCoinSymbolByCryptoId,
1212
selectTradingBuyInfo,
1313
selectTradingBuyQuotesRequest,
1414
} from '../../selectors/tradingSelectors';
@@ -39,7 +39,7 @@ export const selectQuoteThunk = createThunk(
3939
// consent to continue (modal)
4040
const result = await userConsent(
4141
provider.name,
42-
cryptoIdToCoinSymbol(getState(), quote.receiveCurrency),
42+
selectCoinSymbolByCryptoId(getState(), quote.receiveCurrency) ?? 'unknown',
4343
);
4444

4545
if (!result) return;

0 commit comments

Comments
 (0)