UNPKG

3.27 kBPlain TextView Raw
1import { TokenInfo } from './types';
2
3export type TokenInfoChangeKey = Exclude<
4 keyof TokenInfo,
5 'address' | 'chainId'
6>;
7export type TokenInfoChanges = Array<TokenInfoChangeKey>;
8
9/**
10 * compares two token info key values
11 * this subset of full deep equal functionality does not work on objects or object arrays
12 * @param a comparison item a
13 * @param b comparison item b
14 */
15function compareTokenInfoProperty(a: unknown, b: unknown): boolean {
16 if (a === b) return true;
17 if (typeof a !== typeof b) return false;
18 if (Array.isArray(a) && Array.isArray(b)) {
19 return a.every((el, i) => b[i] === el);
20 }
21 return false;
22}
23
24/**
25 * Differences between a base list and an updated list.
26 */
27export interface TokenListDiff {
28 /**
29 * Tokens from updated with chainId/address not present in base list
30 */
31 readonly added: TokenInfo[];
32 /**
33 * Tokens from base with chainId/address not present in the updated list
34 */
35 readonly removed: TokenInfo[];
36 /**
37 * The token info that changed
38 */
39 readonly changed: {
40 [chainId: number]: {
41 [address: string]: TokenInfoChanges;
42 };
43 };
44}
45
46/**
47 * Computes the diff of a token list where the first argument is the base and the second argument is the updated list.
48 * @param base base list
49 * @param update updated list
50 */
51export function diffTokenLists(
52 base: TokenInfo[],
53 update: TokenInfo[]
54): TokenListDiff {
55 const indexedBase = base.reduce<{
56 [chainId: number]: { [address: string]: TokenInfo };
57 }>((memo, tokenInfo) => {
58 if (!memo[tokenInfo.chainId]) memo[tokenInfo.chainId] = {};
59 memo[tokenInfo.chainId][tokenInfo.address] = tokenInfo;
60 return memo;
61 }, {});
62
63 const newListUpdates = update.reduce<{
64 added: TokenInfo[];
65 changed: {
66 [chainId: number]: {
67 [address: string]: TokenInfoChanges;
68 };
69 };
70 index: {
71 [chainId: number]: {
72 [address: string]: true;
73 };
74 };
75 }>(
76 (memo, tokenInfo) => {
77 const baseToken = indexedBase[tokenInfo.chainId]?.[tokenInfo.address];
78 if (!baseToken) {
79 memo.added.push(tokenInfo);
80 } else {
81 const changes: TokenInfoChanges = Object.keys(tokenInfo)
82 .filter(
83 (s): s is TokenInfoChangeKey => s !== 'address' && s !== 'chainId'
84 )
85 .filter(s => {
86 return !compareTokenInfoProperty(tokenInfo[s], baseToken[s]);
87 });
88 if (changes.length > 0) {
89 if (!memo.changed[tokenInfo.chainId]) {
90 memo.changed[tokenInfo.chainId] = {};
91 }
92 memo.changed[tokenInfo.chainId][tokenInfo.address] = changes;
93 }
94 }
95
96 if (!memo.index[tokenInfo.chainId]) {
97 memo.index[tokenInfo.chainId] = {
98 [tokenInfo.address]: true,
99 };
100 } else {
101 memo.index[tokenInfo.chainId][tokenInfo.address] = true;
102 }
103
104 return memo;
105 },
106 { added: [], changed: {}, index: {} }
107 );
108
109 const removed = base.reduce<TokenInfo[]>((list, curr) => {
110 if (
111 !newListUpdates.index[curr.chainId] ||
112 !newListUpdates.index[curr.chainId][curr.address]
113 ) {
114 list.push(curr);
115 }
116 return list;
117 }, []);
118
119 return {
120 added: newListUpdates.added,
121 changed: newListUpdates.changed,
122 removed,
123 };
124}
125
\No newline at end of file