@ledgerhq/coin-evm 0.2.0-next.0
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/.eslintrc.js +57 -0
- package/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +18 -0
- package/jest.config.js +6 -0
- package/package.json +102 -0
- package/src/__tests__/adapters.unit.test.ts +527 -0
- package/src/__tests__/broadcast.unit.test.ts +181 -0
- package/src/__tests__/buildOptimisticOperation.unit.test.ts +182 -0
- package/src/__tests__/createTransaction.unit.test.ts +52 -0
- package/src/__tests__/deviceTransactionConfig.unit.test.ts +245 -0
- package/src/__tests__/estimateMaxSpendable.unit.test.ts +123 -0
- package/src/__tests__/getTransactionStatus.unit.test.ts +355 -0
- package/src/__tests__/hw-getAddress.unit.test.ts +24 -0
- package/src/__tests__/logic.unit.test.ts +406 -0
- package/src/__tests__/preload.unit.test.ts +139 -0
- package/src/__tests__/prepareTransaction.unit.test.ts +394 -0
- package/src/__tests__/rpc.unit.test.ts +532 -0
- package/src/__tests__/signOperation.unit.test.ts +157 -0
- package/src/__tests__/synchronization.unit.test.ts +832 -0
- package/src/__tests__/transaction.unit.test.ts +196 -0
- package/src/abis/erc20.abi.json +230 -0
- package/src/abis/optimismGasPriceOracle.abi.json +252 -0
- package/src/adapters.ts +148 -0
- package/src/api/etherscan.ts +124 -0
- package/src/api/rpc.common.ts +354 -0
- package/src/api/rpc.native.ts +5 -0
- package/src/api/rpc.ts +2 -0
- package/src/bridge/js.ts +77 -0
- package/src/bridge.integration.test.ts +93 -0
- package/src/broadcast.ts +40 -0
- package/src/buildOptimisticOperation.ts +113 -0
- package/src/cli-transaction.ts +11 -0
- package/src/createTransaction.ts +25 -0
- package/src/datasets/ethereum.scanAccounts.1.ts +48 -0
- package/src/datasets/ethereum1.ts +20 -0
- package/src/datasets/ethereum2.ts +20 -0
- package/src/datasets/ethereum_classic.ts +68 -0
- package/src/deviceTransactionConfig.ts +64 -0
- package/src/errors.ts +5 -0
- package/src/estimateMaxSpendable.ts +19 -0
- package/src/getTransactionStatus.ts +186 -0
- package/src/hw-getAddress.ts +24 -0
- package/src/logic.ts +149 -0
- package/src/preload.ts +54 -0
- package/src/prepareTransaction.ts +176 -0
- package/src/signOperation.ts +127 -0
- package/src/specs.ts +344 -0
- package/src/speculos-deviceActions.ts +83 -0
- package/src/synchronization.ts +317 -0
- package/src/testUtils.ts +153 -0
- package/src/transaction.ts +193 -0
- package/src/types.ts +132 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { getCryptoCurrencyById, getTokenById } from "@ledgerhq/cryptoassets";
|
|
2
|
+
import * as cryptoAssetsTokens from "@ledgerhq/cryptoassets/tokens";
|
|
3
|
+
import { TokenCurrency } from "@ledgerhq/types-cryptoassets";
|
|
4
|
+
import BigNumber from "bignumber.js";
|
|
5
|
+
import * as RPC_API from "../api/rpc.common";
|
|
6
|
+
import {
|
|
7
|
+
eip1559TransactionHasFees,
|
|
8
|
+
getAdditionalLayer2Fees,
|
|
9
|
+
getEstimatedFees,
|
|
10
|
+
getSyncHash,
|
|
11
|
+
legacyTransactionHasFees,
|
|
12
|
+
mergeSubAccounts,
|
|
13
|
+
} from "../logic";
|
|
14
|
+
import { makeAccount, makeOperation, makeTokenAccount } from "../testUtils";
|
|
15
|
+
import { EvmTransactionEIP1559, EvmTransactionLegacy } from "../types";
|
|
16
|
+
|
|
17
|
+
describe("EVM Family", () => {
|
|
18
|
+
describe("logic.ts", () => {
|
|
19
|
+
describe("legacyTransactionHasFees", () => {
|
|
20
|
+
it("should return true for legacy tx with fees", () => {
|
|
21
|
+
const tx: Partial<EvmTransactionLegacy> = {
|
|
22
|
+
type: 0,
|
|
23
|
+
gasPrice: new BigNumber(100),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
expect(legacyTransactionHasFees(tx as EvmTransactionLegacy)).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should return true for type 1 (unused in the live for now) tx with fees", () => {
|
|
30
|
+
const tx: Partial<EvmTransactionLegacy> = {
|
|
31
|
+
type: 1,
|
|
32
|
+
gasPrice: new BigNumber(100),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
expect(legacyTransactionHasFees(tx as EvmTransactionLegacy)).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should return false for legacy tx without fees", () => {
|
|
39
|
+
const tx: Partial<EvmTransactionLegacy> = {
|
|
40
|
+
type: 0,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
expect(legacyTransactionHasFees(tx as any)).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should return false for legacy tx with wrong fees", () => {
|
|
47
|
+
const tx: Partial<EvmTransactionEIP1559> = {
|
|
48
|
+
type: 2,
|
|
49
|
+
maxFeePerGas: new BigNumber(100),
|
|
50
|
+
maxPriorityFeePerGas: new BigNumber(100),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
expect(legacyTransactionHasFees(tx as any)).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should return true for legacy tx with fees but no type (default being a legacy tx)", () => {
|
|
57
|
+
const tx: Partial<EvmTransactionLegacy> = {
|
|
58
|
+
gasPrice: new BigNumber(100),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
expect(legacyTransactionHasFees(tx as any)).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("eip1559TransactionHasFess", () => {
|
|
66
|
+
it("should return true for 1559 tx with fees", () => {
|
|
67
|
+
const tx: Partial<EvmTransactionEIP1559> = {
|
|
68
|
+
type: 2,
|
|
69
|
+
maxFeePerGas: new BigNumber(100),
|
|
70
|
+
maxPriorityFeePerGas: new BigNumber(100),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
expect(eip1559TransactionHasFees(tx as any)).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should return false for 1559 tx without fees", () => {
|
|
77
|
+
const tx: Partial<EvmTransactionEIP1559> = {
|
|
78
|
+
type: 2,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
expect(eip1559TransactionHasFees(tx as any)).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should return false for 1559 tx with wrong fees", () => {
|
|
85
|
+
const tx: unknown = {
|
|
86
|
+
type: 2,
|
|
87
|
+
gasPrice: new BigNumber(100),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
expect(eip1559TransactionHasFees(tx as any)).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("getEstimatedFees", () => {
|
|
95
|
+
it("should return the right fee estimation for a legacy tx", () => {
|
|
96
|
+
const tx = {
|
|
97
|
+
type: 0,
|
|
98
|
+
gasLimit: new BigNumber(3),
|
|
99
|
+
gasPrice: new BigNumber(23),
|
|
100
|
+
maxFeePerGas: new BigNumber(100),
|
|
101
|
+
maxPriorityFeePerGas: new BigNumber(40),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
expect(getEstimatedFees(tx as any)).toEqual(new BigNumber(69));
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should fallback with tx without type", () => {
|
|
108
|
+
const tx = {};
|
|
109
|
+
expect(getEstimatedFees(tx as any)).toEqual(new BigNumber(0));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("should fallback with badly formatted legacy tx", () => {
|
|
113
|
+
const tx = {
|
|
114
|
+
type: 0,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
expect(getEstimatedFees(tx as any)).toEqual(new BigNumber(0));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should return the right fee estimation for a 1559 tx", () => {
|
|
121
|
+
const tx = {
|
|
122
|
+
type: 2,
|
|
123
|
+
gasLimit: new BigNumber(42),
|
|
124
|
+
gasPrice: new BigNumber(23),
|
|
125
|
+
maxFeePerGas: new BigNumber(10),
|
|
126
|
+
maxPriorityFeePerGas: new BigNumber(40),
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
expect(getEstimatedFees(tx as any)).toEqual(new BigNumber(420));
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should fallback with badly formatted 1559 tx", () => {
|
|
133
|
+
const tx = {
|
|
134
|
+
type: 2,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
expect(getEstimatedFees(tx as any)).toEqual(new BigNumber(0));
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("getAdditionalLayer2Fees", () => {
|
|
142
|
+
const optimism = getCryptoCurrencyById("optimism");
|
|
143
|
+
const ethereum = getCryptoCurrencyById("ethereum");
|
|
144
|
+
|
|
145
|
+
beforeEach(() => {
|
|
146
|
+
jest.clearAllMocks();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should try to get additionalFees for a valid layer 2", async () => {
|
|
150
|
+
const spy = jest
|
|
151
|
+
.spyOn(RPC_API, "getOptimismAdditionalFees")
|
|
152
|
+
.mockImplementation(jest.fn());
|
|
153
|
+
|
|
154
|
+
await getAdditionalLayer2Fees(optimism, {} as any);
|
|
155
|
+
expect(spy).toBeCalled();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should not try to get additionalFees for an invalid layer 2", async () => {
|
|
159
|
+
const spy = jest
|
|
160
|
+
.spyOn(RPC_API, "getOptimismAdditionalFees")
|
|
161
|
+
.mockImplementation(jest.fn());
|
|
162
|
+
|
|
163
|
+
await getAdditionalLayer2Fees(ethereum, {} as any);
|
|
164
|
+
expect(spy).not.toBeCalled();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe("mergeSubAccounts", () => {
|
|
169
|
+
it("should merge 2 different sub accounts", () => {
|
|
170
|
+
const tokenAccount1 = {
|
|
171
|
+
...makeTokenAccount(
|
|
172
|
+
"0xkvn",
|
|
173
|
+
getTokenById("ethereum/erc20/usd__coin")
|
|
174
|
+
),
|
|
175
|
+
balance: new BigNumber(1),
|
|
176
|
+
operations: [],
|
|
177
|
+
};
|
|
178
|
+
const tokenAccount2 = {
|
|
179
|
+
...makeTokenAccount("0xkvn", getTokenById("ethereum/erc20/weth")),
|
|
180
|
+
balance: new BigNumber(2),
|
|
181
|
+
operations: [],
|
|
182
|
+
};
|
|
183
|
+
const account = makeAccount(
|
|
184
|
+
"0xkvn",
|
|
185
|
+
getCryptoCurrencyById("ethereum"),
|
|
186
|
+
[tokenAccount1]
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const newSubAccounts = mergeSubAccounts(account, [tokenAccount2]);
|
|
190
|
+
expect(newSubAccounts).toEqual([tokenAccount1, tokenAccount2]);
|
|
191
|
+
expect(newSubAccounts).not.toBe(account.subAccounts); // shouldn't mutate original account
|
|
192
|
+
expect(account.subAccounts).toEqual([tokenAccount1]); // shouldn't mutate original account
|
|
193
|
+
expect(newSubAccounts[0]).toBe(account.subAccounts?.[0]); // keeping the reference though
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("should merge 2 different sub accounts and update the first one", () => {
|
|
197
|
+
const tokenAccount1 = {
|
|
198
|
+
...makeTokenAccount(
|
|
199
|
+
"0xkvn",
|
|
200
|
+
getTokenById("ethereum/erc20/usd__coin")
|
|
201
|
+
),
|
|
202
|
+
balance: new BigNumber(1),
|
|
203
|
+
operations: [],
|
|
204
|
+
};
|
|
205
|
+
const tokenAccount1Bis = {
|
|
206
|
+
...tokenAccount1,
|
|
207
|
+
balance: new BigNumber(10),
|
|
208
|
+
spendableBalance: new BigNumber(11),
|
|
209
|
+
operationsCount: 0,
|
|
210
|
+
balanceHistoryCache: {
|
|
211
|
+
HOUR: {
|
|
212
|
+
latestDate: 123,
|
|
213
|
+
balances: [123],
|
|
214
|
+
},
|
|
215
|
+
DAY: {
|
|
216
|
+
latestDate: 234,
|
|
217
|
+
balances: [234],
|
|
218
|
+
},
|
|
219
|
+
WEEK: {
|
|
220
|
+
latestDate: 345,
|
|
221
|
+
balances: [345],
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
operations: [],
|
|
225
|
+
};
|
|
226
|
+
const tokenAccount2 = {
|
|
227
|
+
...makeTokenAccount("0xkvn", getTokenById("ethereum/erc20/weth")),
|
|
228
|
+
balance: new BigNumber(2),
|
|
229
|
+
operations: [],
|
|
230
|
+
};
|
|
231
|
+
const account = makeAccount(
|
|
232
|
+
"0xkvn",
|
|
233
|
+
getCryptoCurrencyById("ethereum"),
|
|
234
|
+
[tokenAccount1]
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const newSubAccounts = mergeSubAccounts(account, [
|
|
238
|
+
tokenAccount1Bis,
|
|
239
|
+
tokenAccount2,
|
|
240
|
+
]);
|
|
241
|
+
expect(newSubAccounts).toEqual([tokenAccount1Bis, tokenAccount2]);
|
|
242
|
+
expect(newSubAccounts).not.toBe(account.subAccounts); // shouldn't mutate original account
|
|
243
|
+
expect(account.subAccounts).toEqual([tokenAccount1]); // shouldn't mutate original account
|
|
244
|
+
expect(newSubAccounts[0]).not.toBe(account.subAccounts?.[0]); // changing the ref as a change happened in tokenAccount1
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("should update subAccount ops", () => {
|
|
248
|
+
const op1 = makeOperation();
|
|
249
|
+
const op2 = makeOperation({
|
|
250
|
+
hash: "0xdiffHash",
|
|
251
|
+
});
|
|
252
|
+
const op3 = makeOperation({
|
|
253
|
+
hash: "0xAgAinAnotHeRH4sh",
|
|
254
|
+
});
|
|
255
|
+
const tokenAccount1 = {
|
|
256
|
+
...makeTokenAccount(
|
|
257
|
+
"0xkvn",
|
|
258
|
+
getTokenById("ethereum/erc20/usd__coin")
|
|
259
|
+
),
|
|
260
|
+
balance: new BigNumber(1),
|
|
261
|
+
operations: [op1, op2],
|
|
262
|
+
operationsCount: 2,
|
|
263
|
+
};
|
|
264
|
+
const tokenAccount1Bis = {
|
|
265
|
+
...tokenAccount1,
|
|
266
|
+
operations: [op3, op1, op2],
|
|
267
|
+
operationsCount: 3,
|
|
268
|
+
};
|
|
269
|
+
const account = makeAccount(
|
|
270
|
+
"0xkvn",
|
|
271
|
+
getCryptoCurrencyById("ethereum"),
|
|
272
|
+
[tokenAccount1]
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const newSubAccounts = mergeSubAccounts(account, [tokenAccount1Bis]);
|
|
276
|
+
expect(newSubAccounts).not.toBe(account.subAccounts); // shouldn't mutate original account
|
|
277
|
+
expect(account.subAccounts).toEqual([tokenAccount1]); // shouldn't mutate original account
|
|
278
|
+
expect(newSubAccounts[0]).not.toBe(account.subAccounts?.[0]); // changing the ref as change happened
|
|
279
|
+
expect(newSubAccounts[0]?.operations?.[1]).toBe(
|
|
280
|
+
account.subAccounts?.[0]?.operations?.[0]
|
|
281
|
+
); // keeping the reference for the ops though
|
|
282
|
+
expect(newSubAccounts[0]?.operations?.[2]).toBe(
|
|
283
|
+
account.subAccounts?.[0]?.operations?.[1]
|
|
284
|
+
); // keeping the reference for the ops though
|
|
285
|
+
expect(newSubAccounts).toEqual([tokenAccount1Bis]);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("should return only new sub accounts", () => {
|
|
289
|
+
const tokenAccount = {
|
|
290
|
+
...makeTokenAccount(
|
|
291
|
+
"0xkvn",
|
|
292
|
+
getTokenById("ethereum/erc20/usd__coin")
|
|
293
|
+
),
|
|
294
|
+
balance: new BigNumber(1),
|
|
295
|
+
};
|
|
296
|
+
const account = makeAccount("0xkvn", getCryptoCurrencyById("ethereum"));
|
|
297
|
+
delete account.subAccounts;
|
|
298
|
+
|
|
299
|
+
const newSubAccounts = mergeSubAccounts(account, [tokenAccount]);
|
|
300
|
+
expect(newSubAccounts).toEqual([tokenAccount]);
|
|
301
|
+
expect(account.subAccounts).toBe(undefined); // shouldn't mutate original account
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("should dedup sub accounts", () => {
|
|
305
|
+
const tokenAccount = {
|
|
306
|
+
...makeTokenAccount(
|
|
307
|
+
"0xkvn",
|
|
308
|
+
getTokenById("ethereum/erc20/usd__coin")
|
|
309
|
+
),
|
|
310
|
+
balance: new BigNumber(1),
|
|
311
|
+
};
|
|
312
|
+
const account = makeAccount(
|
|
313
|
+
"0xkvn",
|
|
314
|
+
getCryptoCurrencyById("ethereum"),
|
|
315
|
+
[tokenAccount]
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const newSubAccounts = mergeSubAccounts(account, [
|
|
319
|
+
tokenAccount,
|
|
320
|
+
{ ...tokenAccount },
|
|
321
|
+
{ ...tokenAccount },
|
|
322
|
+
]);
|
|
323
|
+
expect(newSubAccounts).toEqual([tokenAccount]);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe("getSyncHash", () => {
|
|
328
|
+
const currency = getCryptoCurrencyById("ethereum");
|
|
329
|
+
|
|
330
|
+
afterEach(() => {
|
|
331
|
+
jest.restoreAllMocks();
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("should provide a valid sha256 hash", () => {
|
|
335
|
+
expect(getSyncHash(currency)).toStrictEqual(
|
|
336
|
+
expect.stringMatching(/^0x[A-Fa-f0-9]{64}$/)
|
|
337
|
+
);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("should provide a hash not dependent on reference", () => {
|
|
341
|
+
jest
|
|
342
|
+
.spyOn(cryptoAssetsTokens, "listTokensForCryptoCurrency")
|
|
343
|
+
.mockImplementationOnce((currency): TokenCurrency[] => {
|
|
344
|
+
const { listTokensForCryptoCurrency } = jest.requireActual(
|
|
345
|
+
"@ledgerhq/cryptoassets/tokens"
|
|
346
|
+
);
|
|
347
|
+
return listTokensForCryptoCurrency(currency).map(
|
|
348
|
+
(t: TokenCurrency) => ({ ...t })
|
|
349
|
+
);
|
|
350
|
+
});
|
|
351
|
+
expect(getSyncHash(currency)).toEqual(getSyncHash(currency));
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("should provide a new hash if a token is removed", () => {
|
|
355
|
+
jest
|
|
356
|
+
.spyOn(cryptoAssetsTokens, "listTokensForCryptoCurrency")
|
|
357
|
+
.mockImplementationOnce((currency) => {
|
|
358
|
+
const { listTokensForCryptoCurrency } = jest.requireActual(
|
|
359
|
+
"@ledgerhq/cryptoassets/tokens"
|
|
360
|
+
);
|
|
361
|
+
const list: TokenCurrency[] = listTokensForCryptoCurrency(currency);
|
|
362
|
+
return list.slice(0, list.length - 2);
|
|
363
|
+
});
|
|
364
|
+
expect(getSyncHash(currency)).not.toEqual(getSyncHash(currency));
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it("should provide a new hash if a token is modified", () => {
|
|
368
|
+
jest
|
|
369
|
+
.spyOn(cryptoAssetsTokens, "listTokensForCryptoCurrency")
|
|
370
|
+
.mockImplementationOnce((currency) => {
|
|
371
|
+
const { listTokensForCryptoCurrency } = jest.requireActual(
|
|
372
|
+
"@ledgerhq/cryptoassets/tokens"
|
|
373
|
+
);
|
|
374
|
+
const [first, ...rest]: TokenCurrency[] =
|
|
375
|
+
listTokensForCryptoCurrency(currency);
|
|
376
|
+
const modifedFirst = { ...first, delisted: !first.delisted };
|
|
377
|
+
return [modifedFirst, ...rest];
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
expect(getSyncHash(currency)).not.toEqual(getSyncHash(currency));
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("should provide a new hash if a token is added", () => {
|
|
384
|
+
jest
|
|
385
|
+
.spyOn(cryptoAssetsTokens, "listTokensForCryptoCurrency")
|
|
386
|
+
.mockImplementationOnce((currency) => {
|
|
387
|
+
const { listTokensForCryptoCurrency } = jest.requireActual(
|
|
388
|
+
"@ledgerhq/cryptoassets/tokens"
|
|
389
|
+
);
|
|
390
|
+
return [
|
|
391
|
+
...listTokensForCryptoCurrency(currency),
|
|
392
|
+
{
|
|
393
|
+
type: "TokenCurrency",
|
|
394
|
+
id: "test",
|
|
395
|
+
ledgerSignature: "string",
|
|
396
|
+
contractAddress: "0x123",
|
|
397
|
+
parentCurrency: currency,
|
|
398
|
+
tokenType: "erc20",
|
|
399
|
+
} as TokenCurrency,
|
|
400
|
+
];
|
|
401
|
+
});
|
|
402
|
+
expect(getSyncHash(currency)).not.toEqual(getSyncHash(currency));
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets";
|
|
2
|
+
import evms from "@ledgerhq/cryptoassets/data/evm/index";
|
|
3
|
+
import * as CALTokensAPI from "@ledgerhq/cryptoassets/tokens";
|
|
4
|
+
import { ERC20Token } from "@ledgerhq/cryptoassets/types";
|
|
5
|
+
import network from "@ledgerhq/live-network/network";
|
|
6
|
+
import { fetchERC20Tokens, hydrate, preload } from "../preload";
|
|
7
|
+
|
|
8
|
+
const usdcDefinition: ERC20Token = [
|
|
9
|
+
"ethereum",
|
|
10
|
+
"usd__coin",
|
|
11
|
+
"USDC",
|
|
12
|
+
6,
|
|
13
|
+
"USD Coin",
|
|
14
|
+
"3045022100b2e358726e4e6a6752cf344017c0e9d45b9a904120758d45f61b2804f9ad5299022015161ef28d8c4481bd9432c13562def9cce688bcfec896ef244c9a213f106cdd",
|
|
15
|
+
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
16
|
+
false,
|
|
17
|
+
false,
|
|
18
|
+
undefined,
|
|
19
|
+
undefined,
|
|
20
|
+
];
|
|
21
|
+
const usdtDefinition: ERC20Token = [
|
|
22
|
+
"ethereum",
|
|
23
|
+
"usd_tether__erc20_",
|
|
24
|
+
"USDT",
|
|
25
|
+
6,
|
|
26
|
+
"Tether USD",
|
|
27
|
+
"3044022078c66ccea3e4dedb15a24ec3c783d7b582cd260daf62fd36afe9a8212a344aed0220160ba8c1c4b6a8aa6565bed20632a091aeeeb7bfdac67fc6589a6031acbf511c",
|
|
28
|
+
"0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
|
29
|
+
false,
|
|
30
|
+
false,
|
|
31
|
+
undefined,
|
|
32
|
+
undefined,
|
|
33
|
+
];
|
|
34
|
+
const currency1 = getCryptoCurrencyById("ethereum"); // chain id 1
|
|
35
|
+
|
|
36
|
+
jest.mock("@ledgerhq/live-network/network");
|
|
37
|
+
jest.mock("@ledgerhq/cryptoassets/data/evm/index", () => ({
|
|
38
|
+
get tokens() {
|
|
39
|
+
return {
|
|
40
|
+
1: [usdcDefinition],
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
describe("EVM Family", () => {
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
// @ts-expect-error not casted as jest mock
|
|
48
|
+
network.mockResolvedValue({});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
jest.restoreAllMocks();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("preload.ts", () => {
|
|
56
|
+
describe("fetchERC20Tokens", () => {
|
|
57
|
+
it("should load dynamically the tokens", async () => {
|
|
58
|
+
// @ts-expect-error not casted as jest mock
|
|
59
|
+
network.mockResolvedValue({ data: [usdtDefinition] });
|
|
60
|
+
|
|
61
|
+
const tokens = await fetchERC20Tokens(currency1);
|
|
62
|
+
expect(tokens).toEqual([usdtDefinition]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should fallback on local CAL on dynamic CAL error", async () => {
|
|
66
|
+
// @ts-expect-error not casted as jest mock
|
|
67
|
+
network.mockImplementationOnce(async () => {
|
|
68
|
+
throw new Error();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const tokens = await fetchERC20Tokens(currency1);
|
|
72
|
+
expect(tokens).toEqual([usdcDefinition]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should load erc20 tokens from local CAL when dynamic CAL undefined", async () => {
|
|
76
|
+
const tokens = await fetchERC20Tokens(currency1);
|
|
77
|
+
expect(tokens).toEqual([usdcDefinition]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should load erc20 tokens from local CAL when dynamic CAL is empty []", async () => {
|
|
81
|
+
// @ts-expect-error not casted as jest mock
|
|
82
|
+
network.mockResolvedValue({
|
|
83
|
+
data: {
|
|
84
|
+
tokens: { 1: [] },
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const tokens = await fetchERC20Tokens(currency1);
|
|
89
|
+
expect(tokens).toEqual([usdcDefinition]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should return empty [] if dynamic CAL fails and local CAL fails", async () => {
|
|
93
|
+
jest
|
|
94
|
+
.spyOn(evms, "tokens", "get")
|
|
95
|
+
.mockImplementationOnce(() => ({} as any));
|
|
96
|
+
|
|
97
|
+
const tokens = await fetchERC20Tokens(currency1);
|
|
98
|
+
expect(tokens).toEqual([]);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("preload", () => {
|
|
103
|
+
it("should register tokens", async () => {
|
|
104
|
+
jest
|
|
105
|
+
.spyOn(CALTokensAPI, "addTokens")
|
|
106
|
+
.mockImplementationOnce(() => null);
|
|
107
|
+
|
|
108
|
+
const tokens = await preload(currency1);
|
|
109
|
+
|
|
110
|
+
expect(tokens).toEqual([usdcDefinition]);
|
|
111
|
+
expect(CALTokensAPI.addTokens).toHaveBeenCalledWith([
|
|
112
|
+
CALTokensAPI.convertERC20(usdcDefinition),
|
|
113
|
+
]);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("hydrate", () => {
|
|
118
|
+
afterEach(() => {
|
|
119
|
+
jest.restoreAllMocks();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should return void", () => {
|
|
123
|
+
expect(hydrate(undefined)).toBe(undefined);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should register tokens", async () => {
|
|
127
|
+
jest
|
|
128
|
+
.spyOn(CALTokensAPI, "addTokens")
|
|
129
|
+
.mockImplementationOnce(() => null);
|
|
130
|
+
|
|
131
|
+
await hydrate([usdcDefinition]);
|
|
132
|
+
|
|
133
|
+
expect(CALTokensAPI.addTokens).toHaveBeenCalledWith([
|
|
134
|
+
CALTokensAPI.convertERC20(usdcDefinition),
|
|
135
|
+
]);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|