@rango-dev/widget-embedded 0.30.1-next.2 → 0.30.1-next.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Quote/Quote.styles.d.ts +1 -1
- package/dist/components/RefreshModal/RefreshModal.styles.d.ts +1 -1
- package/dist/containers/Wallets/Wallets.d.ts.map +1 -1
- package/dist/hooks/usePrepareBlockchainList/usePrepareBlockchainList.helpers.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +4 -4
- package/dist/services/cacheService.d.ts +21 -0
- package/dist/services/cacheService.d.ts.map +1 -0
- package/dist/store/slices/config.d.ts +2 -1
- package/dist/store/slices/config.d.ts.map +1 -1
- package/dist/store/slices/data.d.ts +2 -1
- package/dist/store/slices/data.d.ts.map +1 -1
- package/dist/store/utils/data.d.ts +20 -0
- package/dist/store/utils/data.d.ts.map +1 -0
- package/dist/store/utils/data.test.d.ts +2 -0
- package/dist/store/utils/data.test.d.ts.map +1 -0
- package/dist/store/utils/index.d.ts +2 -0
- package/dist/store/utils/index.d.ts.map +1 -0
- package/dist/test-utils/fixtures.d.ts +1 -0
- package/dist/test-utils/fixtures.d.ts.map +1 -1
- package/dist/types/wallets.d.ts +1 -1
- package/dist/types/wallets.d.ts.map +1 -1
- package/dist/utils/settings.d.ts +1 -1
- package/dist/widget-embedded.build.json +1 -1
- package/package.json +2 -2
- package/src/containers/Wallets/Wallets.tsx +7 -2
- package/src/services/cacheService.ts +35 -0
- package/src/store/slices/config.ts +39 -1
- package/src/store/slices/data.test.ts +84 -0
- package/src/store/slices/data.ts +44 -24
- package/src/store/utils/data.test.ts +175 -0
- package/src/store/utils/data.ts +117 -0
- package/src/store/utils/index.ts +1 -0
- package/src/test-utils/fixtures.ts +14 -7
- package/src/types/wallets.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rango-dev/widget-embedded",
|
|
3
|
-
"version": "0.30.1-next.
|
|
3
|
+
"version": "0.30.1-next.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"source": "./src/index.ts",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"@rango-dev/queue-manager-rango-preset": "^0.35.2-next.0",
|
|
31
31
|
"@rango-dev/queue-manager-react": "^0.26.0",
|
|
32
32
|
"@rango-dev/signer-solana": "^0.30.1-next.0",
|
|
33
|
-
"@rango-dev/ui": "^0.36.1-next.
|
|
33
|
+
"@rango-dev/ui": "^0.36.1-next.2",
|
|
34
34
|
"@rango-dev/wallets-react": "^0.21.2-next.0",
|
|
35
35
|
"@rango-dev/wallets-shared": "^0.35.1",
|
|
36
36
|
"bignumber.js": "^9.1.1",
|
|
@@ -31,7 +31,12 @@ export const WidgetContext = createContext<WidgetContextInterface>({
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
function Main(props: PropsWithChildren<PropTypes>) {
|
|
34
|
-
const {
|
|
34
|
+
const {
|
|
35
|
+
updateConfig,
|
|
36
|
+
updateSettings,
|
|
37
|
+
fetch: fetchMeta,
|
|
38
|
+
fetchStatus,
|
|
39
|
+
} = useAppStore();
|
|
35
40
|
const blockchains = useAppStore().blockchains();
|
|
36
41
|
const { findToken } = useAppStore();
|
|
37
42
|
const config = useAppStore().config;
|
|
@@ -61,7 +66,7 @@ function Main(props: PropsWithChildren<PropTypes>) {
|
|
|
61
66
|
dappConfig: props.config,
|
|
62
67
|
};
|
|
63
68
|
}
|
|
64
|
-
}, [props.config]);
|
|
69
|
+
}, [props.config, fetchStatus]);
|
|
65
70
|
|
|
66
71
|
const evmBasedChainNames = blockchains
|
|
67
72
|
.filter(isEvmBlockchain)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Token } from 'rango-sdk';
|
|
2
|
+
|
|
3
|
+
interface CacheServiceInterface<V> {
|
|
4
|
+
get<K extends keyof V>(key: K): V[K] | undefined;
|
|
5
|
+
set<K extends keyof V>(key: K, value: V[K]): void;
|
|
6
|
+
remove<K extends keyof V>(key: K): void;
|
|
7
|
+
clear(): void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class CacheService<T> implements CacheServiceInterface<T> {
|
|
11
|
+
#cache: Map<string, T[keyof T]> = new Map();
|
|
12
|
+
|
|
13
|
+
get<K extends keyof T>(key: K): T[K] | undefined {
|
|
14
|
+
return this.#cache.get(key as string) as T[K];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
set<K extends keyof T>(key: K, value: T[K]): void {
|
|
18
|
+
this.#cache.set(key as string, value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
remove<K extends keyof T>(key: K): void {
|
|
22
|
+
this.#cache.delete(key as string);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
clear(): void {
|
|
26
|
+
this.#cache.clear();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type CachedEntries = {
|
|
31
|
+
supportedSourceTokens: Token[];
|
|
32
|
+
supportedDestinationTokens: Token[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const cacheService = new CacheService<CachedEntries>();
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import type { DataSlice } from './data';
|
|
1
2
|
import type { SettingsSlice } from './settings';
|
|
2
3
|
import type { WidgetConfig } from '../../types';
|
|
3
4
|
import type { StateCreatorWithInitialData } from '../app';
|
|
4
5
|
|
|
6
|
+
import { cacheService } from '../../services/cacheService';
|
|
7
|
+
import { matchTokensFromConfigWithMeta } from '../utils';
|
|
8
|
+
|
|
5
9
|
export const DEFAULT_CONFIG: WidgetConfig = {
|
|
6
10
|
apiKey: '',
|
|
7
11
|
title: undefined,
|
|
@@ -54,7 +58,7 @@ export interface ConfigSlice {
|
|
|
54
58
|
|
|
55
59
|
export const createConfigSlice: StateCreatorWithInitialData<
|
|
56
60
|
WidgetConfig,
|
|
57
|
-
ConfigSlice & SettingsSlice,
|
|
61
|
+
ConfigSlice & SettingsSlice & DataSlice,
|
|
58
62
|
ConfigSlice
|
|
59
63
|
> = (initialData, set, get) => {
|
|
60
64
|
return {
|
|
@@ -87,6 +91,40 @@ export const createConfigSlice: StateCreatorWithInitialData<
|
|
|
87
91
|
// Actions
|
|
88
92
|
updateConfig: (nextConfig: WidgetConfig) => {
|
|
89
93
|
const currentConfig = get().config;
|
|
94
|
+
const {
|
|
95
|
+
_tokensMapByTokenHash: tokensMapByTokenHash,
|
|
96
|
+
_tokensMapByBlockchainName: tokensMapByBlockchainName,
|
|
97
|
+
} = get();
|
|
98
|
+
|
|
99
|
+
const supportedSourceTokens = matchTokensFromConfigWithMeta({
|
|
100
|
+
type: 'source',
|
|
101
|
+
config: {
|
|
102
|
+
blockchains: nextConfig.from?.blockchains,
|
|
103
|
+
tokens: nextConfig.from?.tokens,
|
|
104
|
+
},
|
|
105
|
+
meta: {
|
|
106
|
+
tokensMapByBlockchainName,
|
|
107
|
+
tokensMapByTokenHash,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const supportedDestinationTokens = matchTokensFromConfigWithMeta({
|
|
112
|
+
type: 'destination',
|
|
113
|
+
config: {
|
|
114
|
+
blockchains: nextConfig.to?.blockchains,
|
|
115
|
+
tokens: nextConfig.to?.tokens,
|
|
116
|
+
},
|
|
117
|
+
meta: {
|
|
118
|
+
tokensMapByBlockchainName,
|
|
119
|
+
tokensMapByTokenHash,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
cacheService.set('supportedSourceTokens', supportedSourceTokens);
|
|
124
|
+
cacheService.set(
|
|
125
|
+
'supportedDestinationTokens',
|
|
126
|
+
supportedDestinationTokens
|
|
127
|
+
);
|
|
90
128
|
|
|
91
129
|
set({
|
|
92
130
|
config: {
|
|
@@ -3,6 +3,7 @@ import type { EvmBlockchainMeta, Token } from 'rango-sdk';
|
|
|
3
3
|
|
|
4
4
|
import { assert, beforeEach, describe, expect, test } from 'vitest';
|
|
5
5
|
|
|
6
|
+
import { cacheService } from '../../services/cacheService';
|
|
6
7
|
import {
|
|
7
8
|
createEvmBlockchain,
|
|
8
9
|
createInitialAppStore,
|
|
@@ -17,6 +18,7 @@ let customTokens: [Token, Token, Token];
|
|
|
17
18
|
let rangoBlockchain: EvmBlockchainMeta;
|
|
18
19
|
|
|
19
20
|
beforeEach(() => {
|
|
21
|
+
cacheService.clear();
|
|
20
22
|
rangoBlockchain = createEvmBlockchain();
|
|
21
23
|
|
|
22
24
|
customTokens = [
|
|
@@ -52,6 +54,10 @@ beforeEach(() => {
|
|
|
52
54
|
customTokens.forEach((token) => {
|
|
53
55
|
const tokenHash = createTokenHash(token);
|
|
54
56
|
initData._tokensMapByTokenHash.set(tokenHash, token);
|
|
57
|
+
if (!initData._tokensMapByBlockchainName[token.blockchain]) {
|
|
58
|
+
initData._tokensMapByBlockchainName[token.blockchain] = [];
|
|
59
|
+
}
|
|
60
|
+
initData._tokensMapByBlockchainName[token.blockchain].push(tokenHash);
|
|
55
61
|
});
|
|
56
62
|
|
|
57
63
|
const appStore = createAppStore();
|
|
@@ -351,3 +357,81 @@ describe('search in tokens', () => {
|
|
|
351
357
|
expect(secondResult).toBe('RNG');
|
|
352
358
|
});
|
|
353
359
|
});
|
|
360
|
+
|
|
361
|
+
describe('supported tokens from config', () => {
|
|
362
|
+
test('Should ensure tokens include only tokens from the config', () => {
|
|
363
|
+
const rangoToken = customTokens[0];
|
|
364
|
+
const djangoToken = customTokens[1];
|
|
365
|
+
|
|
366
|
+
const configTokens = [rangoToken, djangoToken];
|
|
367
|
+
|
|
368
|
+
appStoreState = updateAppStoreConfig(appStoreState, {
|
|
369
|
+
from: {
|
|
370
|
+
tokens: configTokens,
|
|
371
|
+
},
|
|
372
|
+
to: { tokens: configTokens },
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
let sourceTokens = appStoreState.tokens({
|
|
376
|
+
type: 'source',
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
let destinationTokens = appStoreState.tokens({
|
|
380
|
+
type: 'destination',
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
expect(configTokens).toMatchObject(sourceTokens);
|
|
384
|
+
expect(configTokens).toMatchObject(destinationTokens);
|
|
385
|
+
|
|
386
|
+
appStoreState = updateAppStoreConfig(appStoreState, {
|
|
387
|
+
from: {
|
|
388
|
+
blockchains: [rangoBlockchain.name],
|
|
389
|
+
tokens: {
|
|
390
|
+
[rangoBlockchain.name]: {
|
|
391
|
+
tokens: configTokens,
|
|
392
|
+
isExcluded: false,
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
to: {
|
|
397
|
+
blockchains: [rangoBlockchain.name],
|
|
398
|
+
tokens: {
|
|
399
|
+
[rangoBlockchain.name]: {
|
|
400
|
+
tokens: configTokens,
|
|
401
|
+
isExcluded: false,
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
sourceTokens = appStoreState.tokens({
|
|
408
|
+
type: 'source',
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
destinationTokens = appStoreState.tokens({
|
|
412
|
+
type: 'destination',
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
expect(sourceTokens).toMatchObject(sourceTokens);
|
|
416
|
+
expect(configTokens).toMatchObject(destinationTokens);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test('Check tokens calculation is caching', () => {
|
|
420
|
+
const rangoToken = customTokens[0];
|
|
421
|
+
const djangoToken = customTokens[1];
|
|
422
|
+
|
|
423
|
+
appStoreState = updateAppStoreConfig(appStoreState, {
|
|
424
|
+
from: {
|
|
425
|
+
tokens: [rangoToken, djangoToken],
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
expect(cacheService.get('supportedSourceTokens')?.length ?? 0).toBe(0);
|
|
430
|
+
|
|
431
|
+
appStoreState.tokens({
|
|
432
|
+
type: 'source',
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
expect(cacheService.get('supportedSourceTokens')?.length ?? 0).toBe(2);
|
|
436
|
+
});
|
|
437
|
+
});
|
package/src/store/slices/data.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
// We keep all the received data from server in this slice
|
|
2
2
|
|
|
3
3
|
import type { ConfigSlice } from './config';
|
|
4
|
-
import type {
|
|
4
|
+
import type { CachedEntries } from '../../services/cacheService';
|
|
5
|
+
import type { Balance, Blockchain, TokenHash } from '../../types';
|
|
5
6
|
import type { Asset, BlockchainMeta, SwapperMeta, Token } from 'rango-sdk';
|
|
6
7
|
import type { StateCreator } from 'zustand';
|
|
7
8
|
|
|
9
|
+
import { cacheService } from '../../services/cacheService';
|
|
8
10
|
import { httpService as sdk } from '../../services/httpService';
|
|
9
11
|
import { compareWithSearchFor, containsText } from '../../utils/common';
|
|
10
|
-
import { isTokenExcludedInConfig } from '../../utils/configs';
|
|
11
12
|
import { createTokenHash, isTokenNative } from '../../utils/meta';
|
|
12
13
|
import { sortLiquiditySourcesByGroupTitle } from '../../utils/settings';
|
|
13
14
|
import { areTokensEqual, compareTokenBalance } from '../../utils/wallets';
|
|
15
|
+
import { matchTokensFromConfigWithMeta } from '../utils';
|
|
14
16
|
|
|
15
17
|
type BlockchainOptions = {
|
|
16
18
|
type?: 'source' | 'destination';
|
|
@@ -30,6 +32,7 @@ export type FetchStatus = 'loading' | 'success' | 'failed';
|
|
|
30
32
|
export interface DataSlice {
|
|
31
33
|
_blockchainsMapByName: Map<string, BlockchainMeta>;
|
|
32
34
|
_tokensMapByTokenHash: Map<TokenHash, Token>;
|
|
35
|
+
_tokensMapByBlockchainName: Record<Blockchain, TokenHash[]>;
|
|
33
36
|
_popularTokens: Token[];
|
|
34
37
|
_swappers: SwapperMeta[];
|
|
35
38
|
fetchStatus: FetchStatus;
|
|
@@ -48,8 +51,9 @@ export const createDataSlice: StateCreator<
|
|
|
48
51
|
DataSlice
|
|
49
52
|
> = (set, get) => ({
|
|
50
53
|
// State
|
|
51
|
-
_blockchainsMapByName: new Map
|
|
52
|
-
_tokensMapByTokenHash: new Map
|
|
54
|
+
_blockchainsMapByName: new Map(),
|
|
55
|
+
_tokensMapByTokenHash: new Map(),
|
|
56
|
+
_tokensMapByBlockchainName: {},
|
|
53
57
|
_popularTokens: [],
|
|
54
58
|
_swappers: [],
|
|
55
59
|
fetchStatus: 'loading',
|
|
@@ -84,31 +88,41 @@ export const createDataSlice: StateCreator<
|
|
|
84
88
|
return list;
|
|
85
89
|
},
|
|
86
90
|
tokens: (options) => {
|
|
87
|
-
const
|
|
88
|
-
const tokensFromState = Array.from(
|
|
91
|
+
const { _tokensMapByTokenHash, _tokensMapByBlockchainName, config } = get();
|
|
92
|
+
const tokensFromState = Array.from(_tokensMapByTokenHash.values());
|
|
89
93
|
const blockchainsMapByName = get()._blockchainsMapByName;
|
|
90
|
-
|
|
91
|
-
if (!options || !options?.type) {
|
|
94
|
+
if (!options || !options.type) {
|
|
92
95
|
return tokensFromState;
|
|
93
96
|
}
|
|
94
97
|
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
const configType = options.type === 'source' ? 'from' : 'to';
|
|
99
|
+
const cacheKey: keyof CachedEntries =
|
|
100
|
+
options.type === 'source'
|
|
101
|
+
? 'supportedSourceTokens'
|
|
102
|
+
: 'supportedDestinationTokens';
|
|
103
|
+
|
|
104
|
+
let supportedTokens = cacheService.get(cacheKey);
|
|
105
|
+
if (!supportedTokens) {
|
|
106
|
+
supportedTokens = matchTokensFromConfigWithMeta({
|
|
107
|
+
type: options.type,
|
|
108
|
+
config: {
|
|
109
|
+
blockchains: config[configType]?.blockchains,
|
|
110
|
+
tokens: config[configType]?.tokens,
|
|
111
|
+
},
|
|
112
|
+
meta: {
|
|
113
|
+
tokensMapByTokenHash: _tokensMapByTokenHash,
|
|
114
|
+
tokensMapByBlockchainName: _tokensMapByBlockchainName,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
cacheService.set(cacheKey, supportedTokens);
|
|
118
|
+
}
|
|
119
|
+
|
|
99
120
|
const blockchains = get().blockchains({
|
|
100
121
|
type: options.type,
|
|
101
122
|
});
|
|
102
123
|
|
|
103
|
-
const list =
|
|
124
|
+
const list = supportedTokens
|
|
104
125
|
.filter((token) => {
|
|
105
|
-
if (
|
|
106
|
-
supportedTokensConfig &&
|
|
107
|
-
isTokenExcludedInConfig(token, supportedTokensConfig)
|
|
108
|
-
) {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
126
|
// If a specific blockchain has passed, we only keep that blockchain's tokens.
|
|
113
127
|
if (!!options.blockchain && token.blockchain !== options.blockchain) {
|
|
114
128
|
return false;
|
|
@@ -261,17 +275,18 @@ export const createDataSlice: StateCreator<
|
|
|
261
275
|
const response = await sdk().getAllMetadata({
|
|
262
276
|
enableCentralizedSwappers,
|
|
263
277
|
});
|
|
264
|
-
|
|
265
278
|
set({ fetchStatus: 'success' });
|
|
266
279
|
const blockchainsMapByName: Map<string, BlockchainMeta> = new Map<
|
|
267
280
|
string,
|
|
268
281
|
BlockchainMeta
|
|
269
282
|
>();
|
|
270
283
|
|
|
271
|
-
const
|
|
284
|
+
const tokensMapByTokenHash: Map<TokenHash, Token> = new Map<
|
|
272
285
|
TokenHash,
|
|
273
286
|
Token
|
|
274
287
|
>();
|
|
288
|
+
const tokensMapByBlockchainName: DataSlice['_tokensMapByBlockchainName'] =
|
|
289
|
+
{};
|
|
275
290
|
const tokens: Token[] = [];
|
|
276
291
|
const popularTokens: Token[] = response.popularTokens;
|
|
277
292
|
const swappers: SwapperMeta[] = response.swappers;
|
|
@@ -299,12 +314,17 @@ export const createDataSlice: StateCreator<
|
|
|
299
314
|
|
|
300
315
|
tokens.forEach((token) => {
|
|
301
316
|
const tokenHash = createTokenHash(token);
|
|
302
|
-
|
|
317
|
+
if (!tokensMapByBlockchainName[token.blockchain]) {
|
|
318
|
+
tokensMapByBlockchainName[token.blockchain] = [];
|
|
319
|
+
}
|
|
320
|
+
tokensMapByTokenHash.set(tokenHash, token);
|
|
321
|
+
tokensMapByBlockchainName[token.blockchain].push(tokenHash);
|
|
303
322
|
});
|
|
304
323
|
|
|
305
324
|
set({
|
|
306
325
|
_blockchainsMapByName: blockchainsMapByName,
|
|
307
|
-
_tokensMapByTokenHash:
|
|
326
|
+
_tokensMapByTokenHash: tokensMapByTokenHash,
|
|
327
|
+
_tokensMapByBlockchainName: tokensMapByBlockchainName,
|
|
308
328
|
_popularTokens: popularTokens,
|
|
309
329
|
_swappers: swappers,
|
|
310
330
|
});
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import type { Token } from 'rango-sdk';
|
|
2
|
+
|
|
3
|
+
import { describe, expect, test } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { createToken } from '../../test-utils/fixtures';
|
|
6
|
+
import { createTokenHash } from '../../utils/meta';
|
|
7
|
+
|
|
8
|
+
import { matchTokensFromConfigWithMeta } from './data'; // Adjust path as necessary
|
|
9
|
+
|
|
10
|
+
type MatchTokensFromConfigWithMetaParam = Parameters<
|
|
11
|
+
typeof matchTokensFromConfigWithMeta
|
|
12
|
+
>[0];
|
|
13
|
+
|
|
14
|
+
const BLOCKCHAIN_A = 'blockchainA';
|
|
15
|
+
const BLOCKCHAIN_B = 'blockchainB';
|
|
16
|
+
const BLOCKCHAIN_C = 'blockchainC';
|
|
17
|
+
|
|
18
|
+
const tokens: Token[] = [
|
|
19
|
+
{ ...createToken(), blockchain: BLOCKCHAIN_A },
|
|
20
|
+
{ ...createToken(), blockchain: BLOCKCHAIN_A },
|
|
21
|
+
{ ...createToken(), blockchain: BLOCKCHAIN_B },
|
|
22
|
+
{ ...createToken(), blockchain: BLOCKCHAIN_C },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
function createMetaForMockData(
|
|
26
|
+
tokens: Token[]
|
|
27
|
+
): MatchTokensFromConfigWithMetaParam['meta'] {
|
|
28
|
+
const meta: MatchTokensFromConfigWithMetaParam['meta'] = {
|
|
29
|
+
tokensMapByTokenHash: new Map(),
|
|
30
|
+
tokensMapByBlockchainName: {},
|
|
31
|
+
};
|
|
32
|
+
tokens.forEach((token) => {
|
|
33
|
+
const tokenHash = createTokenHash(token);
|
|
34
|
+
meta.tokensMapByTokenHash.set(tokenHash, token);
|
|
35
|
+
if (!meta.tokensMapByBlockchainName[token.blockchain]) {
|
|
36
|
+
meta.tokensMapByBlockchainName[token.blockchain] = [];
|
|
37
|
+
}
|
|
38
|
+
meta.tokensMapByBlockchainName[token.blockchain].push(tokenHash);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return meta;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe('matchTokensFromConfigWithMeta', () => {
|
|
45
|
+
test('should include tokens from config.tokens array that exist in meta', () => {
|
|
46
|
+
const mockData: MatchTokensFromConfigWithMetaParam = {
|
|
47
|
+
type: 'source',
|
|
48
|
+
meta: createMetaForMockData(tokens),
|
|
49
|
+
config: {
|
|
50
|
+
blockchains: undefined,
|
|
51
|
+
tokens: [tokens[0], tokens[1]],
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const result = matchTokensFromConfigWithMeta(mockData);
|
|
56
|
+
expect(result).toEqual([tokens[0], tokens[1]]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('If config.blockchains is not defined or is empty, we should include all tokens from the meta for every blockchain that does not have tokens specified in the config, as well as tokens specified in the config that exist in the meta.', () => {
|
|
60
|
+
const mockData: MatchTokensFromConfigWithMetaParam = {
|
|
61
|
+
type: 'source',
|
|
62
|
+
meta: createMetaForMockData(tokens),
|
|
63
|
+
config: {
|
|
64
|
+
blockchains: undefined,
|
|
65
|
+
tokens: {
|
|
66
|
+
[BLOCKCHAIN_A]: { tokens: [tokens[0]], isExcluded: false },
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const runTestWithConfig = (config: {
|
|
72
|
+
blockchains: MatchTokensFromConfigWithMetaParam['config']['blockchains'];
|
|
73
|
+
}) => {
|
|
74
|
+
mockData.config.blockchains = config.blockchains;
|
|
75
|
+
const result = matchTokensFromConfigWithMeta(mockData);
|
|
76
|
+
/**
|
|
77
|
+
* In this case, the result might differ from what was expected.
|
|
78
|
+
* To pass the test, we reordered the expected result.
|
|
79
|
+
* Changing the order of tokens does not impact the overall behavior of our app,
|
|
80
|
+
* as we have a comprehensive sorting logic applied to tokens within the data slice.
|
|
81
|
+
*/
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
|
83
|
+
expect(result).toEqual([tokens[2], tokens[3], tokens[0]]);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
runTestWithConfig({ blockchains: undefined });
|
|
87
|
+
runTestWithConfig({ blockchains: [] });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('should include tokens from config.tokens object that exist in meta', () => {
|
|
91
|
+
const mockData: MatchTokensFromConfigWithMetaParam = {
|
|
92
|
+
type: 'source',
|
|
93
|
+
meta: createMetaForMockData(tokens),
|
|
94
|
+
config: {
|
|
95
|
+
blockchains: [BLOCKCHAIN_A, BLOCKCHAIN_B],
|
|
96
|
+
tokens: {
|
|
97
|
+
[BLOCKCHAIN_A]: { tokens: [tokens[0]], isExcluded: false },
|
|
98
|
+
[BLOCKCHAIN_B]: { tokens: [tokens[2]], isExcluded: false },
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const result = matchTokensFromConfigWithMeta(mockData);
|
|
104
|
+
expect(result).toEqual([tokens[0], tokens[2]]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('should not include tokens from config.tokens whose blockchain does not exist in config.blockchains', () => {
|
|
108
|
+
const mockData: MatchTokensFromConfigWithMetaParam = {
|
|
109
|
+
type: 'source',
|
|
110
|
+
meta: createMetaForMockData(tokens),
|
|
111
|
+
config: {
|
|
112
|
+
blockchains: [BLOCKCHAIN_A],
|
|
113
|
+
tokens: {
|
|
114
|
+
[BLOCKCHAIN_B]: { tokens: [tokens[2]], isExcluded: false },
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const result = matchTokensFromConfigWithMeta(mockData);
|
|
120
|
+
expect(result).toEqual([tokens[0], tokens[1]]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('should include all tokens from a blockchain if that blockchain does not have any tokens in config.tokens', () => {
|
|
124
|
+
const mockData: MatchTokensFromConfigWithMetaParam = {
|
|
125
|
+
type: 'source',
|
|
126
|
+
meta: createMetaForMockData(tokens),
|
|
127
|
+
config: {
|
|
128
|
+
blockchains: [BLOCKCHAIN_A],
|
|
129
|
+
tokens: {},
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const result = matchTokensFromConfigWithMeta(mockData);
|
|
134
|
+
expect(result).toEqual([tokens[0], tokens[1]]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('should include all tokens from config.blockchains that exist in the meta, except tokens excluded in config.token', () => {
|
|
138
|
+
const mockData: MatchTokensFromConfigWithMetaParam = {
|
|
139
|
+
type: 'source',
|
|
140
|
+
meta: createMetaForMockData(tokens),
|
|
141
|
+
config: {
|
|
142
|
+
blockchains: [BLOCKCHAIN_A],
|
|
143
|
+
tokens: {
|
|
144
|
+
[BLOCKCHAIN_A]: { tokens: [tokens[0]], isExcluded: true },
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const result = matchTokensFromConfigWithMeta(mockData);
|
|
150
|
+
expect(result).toEqual([tokens[1]]);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('should include all tokens from the meta when config parameters are empty values', () => {
|
|
154
|
+
const mockData: MatchTokensFromConfigWithMetaParam = {
|
|
155
|
+
type: 'source',
|
|
156
|
+
meta: createMetaForMockData(tokens),
|
|
157
|
+
config: {
|
|
158
|
+
blockchains: [],
|
|
159
|
+
tokens: {},
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const runTestWithConfig = (
|
|
164
|
+
config: MatchTokensFromConfigWithMetaParam['config']
|
|
165
|
+
) => {
|
|
166
|
+
mockData.config = config;
|
|
167
|
+
const result = matchTokensFromConfigWithMeta(mockData);
|
|
168
|
+
expect(result).toEqual(tokens);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
runTestWithConfig({ blockchains: [], tokens: {} });
|
|
172
|
+
runTestWithConfig({ blockchains: undefined, tokens: undefined });
|
|
173
|
+
runTestWithConfig({ blockchains: [], tokens: [] });
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { BlockchainAndTokenConfig, TokenHash } from '../../types';
|
|
2
|
+
import type { DataSlice } from '../slices/data';
|
|
3
|
+
import type { Asset, Token } from 'rango-sdk';
|
|
4
|
+
|
|
5
|
+
import { createTokenHash } from '../../utils/meta';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* This function retrieves tokens and blockchains from the widget configuration and combines them with token data from the meta response to determine which tokens should be displayed in the widget.
|
|
9
|
+
* To optimize performance, the function minimizes unnecessary iterations over the meta tokens.
|
|
10
|
+
* The result of this function should be cached and recalculated whenever the widget configuration changes.
|
|
11
|
+
*/
|
|
12
|
+
export function matchTokensFromConfigWithMeta(params: {
|
|
13
|
+
type: 'source' | 'destination';
|
|
14
|
+
config: {
|
|
15
|
+
blockchains: BlockchainAndTokenConfig['blockchains'];
|
|
16
|
+
tokens: BlockchainAndTokenConfig['tokens'];
|
|
17
|
+
};
|
|
18
|
+
meta: {
|
|
19
|
+
tokensMapByTokenHash: DataSlice['_tokensMapByTokenHash'];
|
|
20
|
+
tokensMapByBlockchainName: DataSlice['_tokensMapByBlockchainName'];
|
|
21
|
+
};
|
|
22
|
+
}): Token[] {
|
|
23
|
+
const { config, meta } = params;
|
|
24
|
+
const result: Record<TokenHash, Token> = {};
|
|
25
|
+
const configTokens = config.tokens;
|
|
26
|
+
|
|
27
|
+
// Helper function to add tokens to result
|
|
28
|
+
const addTokens = (tokens: (Asset | TokenHash)[]) => {
|
|
29
|
+
tokens.forEach((item) => {
|
|
30
|
+
if (typeof item !== 'string') {
|
|
31
|
+
item = createTokenHash(item);
|
|
32
|
+
}
|
|
33
|
+
const tokenFromMeta = meta.tokensMapByTokenHash.get(item);
|
|
34
|
+
if (tokenFromMeta) {
|
|
35
|
+
result[item] = tokenFromMeta;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* We support two types of configurations for passing tokens in the widget config.
|
|
42
|
+
* We check the type of configuration and calculate the result based on that.
|
|
43
|
+
*/
|
|
44
|
+
if (Array.isArray(configTokens)) {
|
|
45
|
+
/**
|
|
46
|
+
* If "config.tokens" is an array,
|
|
47
|
+
* we iterate through it and include any token that exists in the meta data in the result.
|
|
48
|
+
*/
|
|
49
|
+
if (configTokens.length > 0) {
|
|
50
|
+
addTokens(configTokens);
|
|
51
|
+
return Object.values(result);
|
|
52
|
+
}
|
|
53
|
+
return Array.from(meta.tokensMapByTokenHash.values());
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* From this point onward,
|
|
58
|
+
* the function handles the other type of widget configuration.
|
|
59
|
+
*/
|
|
60
|
+
if (!configTokens) {
|
|
61
|
+
return Array.from(meta.tokensMapByTokenHash.values());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let configBlockchains: string[];
|
|
65
|
+
|
|
66
|
+
if (config.blockchains?.length) {
|
|
67
|
+
configBlockchains = config.blockchains;
|
|
68
|
+
} else {
|
|
69
|
+
/**
|
|
70
|
+
* If config.blockchains does not exist,
|
|
71
|
+
* we include all tokens from every blockchain in the meta data that are not specified in the tokens from the config.
|
|
72
|
+
*/
|
|
73
|
+
configBlockchains = Object.keys(configTokens);
|
|
74
|
+
const configBlockchainsSet = new Set(configBlockchains);
|
|
75
|
+
|
|
76
|
+
Object.keys(meta.tokensMapByBlockchainName).forEach((blockchain) => {
|
|
77
|
+
if (!configBlockchainsSet.has(blockchain)) {
|
|
78
|
+
addTokens(meta.tokensMapByBlockchainName[blockchain]);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
//We iterate over each blockchain and retrieve the related tokens for each one from the configuration.
|
|
83
|
+
configBlockchains.forEach((blockchain) => {
|
|
84
|
+
const targetTokensForBlockchain = configTokens[blockchain];
|
|
85
|
+
/**
|
|
86
|
+
* If no tokens exist for a blockchain,
|
|
87
|
+
* we include all tokens for that blockchain from the meta response.
|
|
88
|
+
*/
|
|
89
|
+
if (
|
|
90
|
+
!targetTokensForBlockchain &&
|
|
91
|
+
meta.tokensMapByBlockchainName?.[blockchain]
|
|
92
|
+
) {
|
|
93
|
+
addTokens(meta.tokensMapByBlockchainName[blockchain]);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (targetTokensForBlockchain) {
|
|
98
|
+
if (targetTokensForBlockchain.isExcluded) {
|
|
99
|
+
/**
|
|
100
|
+
* If tokens are excluded,
|
|
101
|
+
* we first include all tokens from the meta for that blockchain,
|
|
102
|
+
* then remove the excluded tokens from the result.
|
|
103
|
+
*/
|
|
104
|
+
addTokens(meta.tokensMapByBlockchainName[blockchain]);
|
|
105
|
+
targetTokensForBlockchain.tokens.forEach((token) => {
|
|
106
|
+
const tokenHash = createTokenHash(token);
|
|
107
|
+
delete result[tokenHash];
|
|
108
|
+
});
|
|
109
|
+
} else {
|
|
110
|
+
//We retrieve the corresponding token from the meta and include it in the result.
|
|
111
|
+
addTokens(targetTokensForBlockchain.tokens);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return Object.values(result);
|
|
117
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './data';
|