@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.
Files changed (53) hide show
  1. package/.eslintrc.js +57 -0
  2. package/.turbo/turbo-build.log +4 -0
  3. package/CHANGELOG.md +18 -0
  4. package/jest.config.js +6 -0
  5. package/package.json +102 -0
  6. package/src/__tests__/adapters.unit.test.ts +527 -0
  7. package/src/__tests__/broadcast.unit.test.ts +181 -0
  8. package/src/__tests__/buildOptimisticOperation.unit.test.ts +182 -0
  9. package/src/__tests__/createTransaction.unit.test.ts +52 -0
  10. package/src/__tests__/deviceTransactionConfig.unit.test.ts +245 -0
  11. package/src/__tests__/estimateMaxSpendable.unit.test.ts +123 -0
  12. package/src/__tests__/getTransactionStatus.unit.test.ts +355 -0
  13. package/src/__tests__/hw-getAddress.unit.test.ts +24 -0
  14. package/src/__tests__/logic.unit.test.ts +406 -0
  15. package/src/__tests__/preload.unit.test.ts +139 -0
  16. package/src/__tests__/prepareTransaction.unit.test.ts +394 -0
  17. package/src/__tests__/rpc.unit.test.ts +532 -0
  18. package/src/__tests__/signOperation.unit.test.ts +157 -0
  19. package/src/__tests__/synchronization.unit.test.ts +832 -0
  20. package/src/__tests__/transaction.unit.test.ts +196 -0
  21. package/src/abis/erc20.abi.json +230 -0
  22. package/src/abis/optimismGasPriceOracle.abi.json +252 -0
  23. package/src/adapters.ts +148 -0
  24. package/src/api/etherscan.ts +124 -0
  25. package/src/api/rpc.common.ts +354 -0
  26. package/src/api/rpc.native.ts +5 -0
  27. package/src/api/rpc.ts +2 -0
  28. package/src/bridge/js.ts +77 -0
  29. package/src/bridge.integration.test.ts +93 -0
  30. package/src/broadcast.ts +40 -0
  31. package/src/buildOptimisticOperation.ts +113 -0
  32. package/src/cli-transaction.ts +11 -0
  33. package/src/createTransaction.ts +25 -0
  34. package/src/datasets/ethereum.scanAccounts.1.ts +48 -0
  35. package/src/datasets/ethereum1.ts +20 -0
  36. package/src/datasets/ethereum2.ts +20 -0
  37. package/src/datasets/ethereum_classic.ts +68 -0
  38. package/src/deviceTransactionConfig.ts +64 -0
  39. package/src/errors.ts +5 -0
  40. package/src/estimateMaxSpendable.ts +19 -0
  41. package/src/getTransactionStatus.ts +186 -0
  42. package/src/hw-getAddress.ts +24 -0
  43. package/src/logic.ts +149 -0
  44. package/src/preload.ts +54 -0
  45. package/src/prepareTransaction.ts +176 -0
  46. package/src/signOperation.ts +127 -0
  47. package/src/specs.ts +344 -0
  48. package/src/speculos-deviceActions.ts +83 -0
  49. package/src/synchronization.ts +317 -0
  50. package/src/testUtils.ts +153 -0
  51. package/src/transaction.ts +193 -0
  52. package/src/types.ts +132 -0
  53. package/tsconfig.json +12 -0
@@ -0,0 +1,394 @@
1
+ import { getCryptoCurrencyById, getTokenById } from "@ledgerhq/cryptoassets";
2
+ import BigNumber from "bignumber.js";
3
+ import { ethers } from "ethers";
4
+ import ERC20ABI from "../abis/erc20.abi.json";
5
+ import * as rpcAPI from "../api/rpc.common";
6
+ import {
7
+ prepareForSignOperation,
8
+ prepareTransaction,
9
+ } from "../prepareTransaction";
10
+ import { makeAccount, makeTokenAccount } from "../testUtils";
11
+ import { Transaction as EvmTransaction } from "../types";
12
+
13
+ const currency = getCryptoCurrencyById("ethereum");
14
+ const tokenAccount = makeTokenAccount(
15
+ "0xkvn",
16
+ getTokenById("ethereum/erc20/usd__coin")
17
+ );
18
+ const account = makeAccount("0xkvn", currency, [tokenAccount]);
19
+ const transaction: EvmTransaction = {
20
+ amount: new BigNumber(100),
21
+ useAllAmount: false,
22
+ subAccountId: "id",
23
+ recipient: "0x6bfD74C0996F269Bcece59191EFf667b3dFD73b9",
24
+ feesStrategy: "custom",
25
+ family: "evm",
26
+ mode: "send",
27
+ gasPrice: new BigNumber(0),
28
+ gasLimit: new BigNumber(21000),
29
+ nonce: 0,
30
+ chainId: 1,
31
+ };
32
+ const tokenTransaction: EvmTransaction = {
33
+ ...transaction,
34
+ subAccountId: tokenAccount.id,
35
+ };
36
+ const expectedData = (recipient: string, amount: BigNumber): Buffer =>
37
+ Buffer.from(
38
+ new ethers.utils.Interface(ERC20ABI)
39
+ .encodeFunctionData("transfer", [recipient, amount.toFixed()])
40
+ .slice(2),
41
+ "hex"
42
+ );
43
+
44
+ describe("EVM Family", () => {
45
+ describe("prepareTransaction.ts", () => {
46
+ beforeEach(() => {
47
+ // These mocks will be overriden in some tests
48
+ jest
49
+ .spyOn(rpcAPI, "getGasEstimation")
50
+ .mockImplementation(async () => new BigNumber(21000));
51
+ // These mocks will be overriden in some tests
52
+ jest.spyOn(rpcAPI, "getFeesEstimation").mockImplementation(async () => ({
53
+ gasPrice: new BigNumber(1),
54
+ maxFeePerGas: new BigNumber(1),
55
+ maxPriorityFeePerGas: new BigNumber(1),
56
+ }));
57
+ });
58
+
59
+ afterEach(() => {
60
+ jest.restoreAllMocks();
61
+ });
62
+
63
+ describe("prepareTransaction", () => {
64
+ describe("Coins", () => {
65
+ it("should have a gasLimit = 0 when recipient has an error", async () => {
66
+ jest
67
+ .spyOn(rpcAPI, "getGasEstimation")
68
+ .mockImplementation(async () => {
69
+ throw new Error();
70
+ });
71
+
72
+ const tx = await prepareTransaction(account, {
73
+ ...transaction,
74
+ recipient: "notValid",
75
+ });
76
+
77
+ expect(tx).toEqual({
78
+ ...transaction,
79
+ recipient: "notValid",
80
+ maxFeePerGas: new BigNumber(1),
81
+ maxPriorityFeePerGas: new BigNumber(1),
82
+ gasPrice: undefined,
83
+ gasLimit: new BigNumber(0),
84
+ type: 2,
85
+ });
86
+ });
87
+
88
+ it("should have a gasLimit = 0 when amount has an error", async () => {
89
+ jest
90
+ .spyOn(rpcAPI, "getGasEstimation")
91
+ .mockImplementation(async () => {
92
+ throw new Error();
93
+ });
94
+
95
+ const tx = await prepareTransaction(account, {
96
+ ...transaction,
97
+ amount: new BigNumber(0),
98
+ });
99
+
100
+ expect(tx).toEqual({
101
+ ...transaction,
102
+ amount: new BigNumber(0),
103
+ maxFeePerGas: new BigNumber(1),
104
+ maxPriorityFeePerGas: new BigNumber(1),
105
+ gasPrice: undefined,
106
+ gasLimit: new BigNumber(0),
107
+ type: 2,
108
+ });
109
+ });
110
+
111
+ it("should return an EIP1559 coin transaction", async () => {
112
+ const tx = await prepareTransaction(account, transaction);
113
+
114
+ expect(tx).toEqual({
115
+ ...transaction,
116
+ maxFeePerGas: new BigNumber(1),
117
+ maxPriorityFeePerGas: new BigNumber(1),
118
+ type: 2,
119
+ });
120
+ });
121
+
122
+ it("should return a legacy coin transaction", async () => {
123
+ jest
124
+ .spyOn(rpcAPI, "getFeesEstimation")
125
+ .mockImplementationOnce(async () => ({
126
+ gasPrice: new BigNumber(1),
127
+ maxFeePerGas: null,
128
+ maxPriorityFeePerGas: null,
129
+ }));
130
+
131
+ const tx = await prepareTransaction(account, transaction);
132
+
133
+ expect(tx).toEqual({
134
+ ...transaction,
135
+ gasPrice: new BigNumber(1),
136
+ type: 0,
137
+ });
138
+ });
139
+
140
+ it("should create a coin transaction using all amount in the account", async () => {
141
+ const accountWithBalance = {
142
+ ...account,
143
+ balance: new BigNumber(4206900),
144
+ };
145
+ const transactionWithUseAllAmount = {
146
+ ...transaction,
147
+ useAllAmount: true,
148
+ };
149
+
150
+ const tx = await prepareTransaction(
151
+ accountWithBalance,
152
+ transactionWithUseAllAmount
153
+ );
154
+ const estimatedFees = new BigNumber(21000); // 21000 gasLimit * 1 maxFeePerGas
155
+
156
+ expect(tx).toEqual({
157
+ ...transactionWithUseAllAmount,
158
+ amount: accountWithBalance.balance.minus(estimatedFees),
159
+ maxFeePerGas: new BigNumber(1),
160
+ maxPriorityFeePerGas: new BigNumber(1),
161
+ type: 2,
162
+ });
163
+ });
164
+
165
+ it("should do a gas estimation when data has been added to the coin transaction", async () => {
166
+ jest
167
+ .spyOn(rpcAPI, "getGasEstimation")
168
+ .mockImplementationOnce(async () => new BigNumber(12));
169
+
170
+ const accountWithBalance = {
171
+ ...account,
172
+ balance: new BigNumber(4206900),
173
+ };
174
+ const tx = await prepareTransaction(accountWithBalance, {
175
+ ...transaction,
176
+ data: Buffer.from("Sm4rTC0ntr4ct", "hex"),
177
+ });
178
+
179
+ expect(tx).toEqual({
180
+ ...transaction,
181
+ data: Buffer.from("Sm4rTC0ntr4ct", "hex"),
182
+ maxFeePerGas: new BigNumber(1),
183
+ maxPriorityFeePerGas: new BigNumber(1),
184
+ gasLimit: new BigNumber(12),
185
+ type: 2,
186
+ });
187
+ });
188
+ });
189
+
190
+ describe("Tokens", () => {
191
+ it("should have a gasLimit = 0 and no data when recipient has an error", async () => {
192
+ jest
193
+ .spyOn(rpcAPI, "getGasEstimation")
194
+ .mockImplementation(async () => {
195
+ throw new Error();
196
+ });
197
+
198
+ const tx = await prepareTransaction(account, {
199
+ ...tokenTransaction,
200
+ recipient: "notValid",
201
+ });
202
+
203
+ expect(tx).toEqual({
204
+ ...tokenTransaction,
205
+ recipient: "notValid",
206
+ maxFeePerGas: new BigNumber(1),
207
+ maxPriorityFeePerGas: new BigNumber(1),
208
+ gasPrice: undefined,
209
+ gasLimit: new BigNumber(0),
210
+ data: undefined,
211
+ type: 2,
212
+ });
213
+ });
214
+
215
+ it("should have a gasLimit = 0 when amount has an error", async () => {
216
+ jest
217
+ .spyOn(rpcAPI, "getGasEstimation")
218
+ .mockImplementation(async () => {
219
+ throw new Error();
220
+ });
221
+
222
+ const tx = await prepareTransaction(account, {
223
+ ...tokenTransaction,
224
+ amount: new BigNumber(0),
225
+ });
226
+
227
+ expect(tx).toEqual({
228
+ ...tokenTransaction,
229
+ amount: new BigNumber(0),
230
+ maxFeePerGas: new BigNumber(1),
231
+ maxPriorityFeePerGas: new BigNumber(1),
232
+ gasPrice: undefined,
233
+ gasLimit: new BigNumber(0),
234
+ data: expectedData(tokenTransaction.recipient, new BigNumber(0)),
235
+ type: 2,
236
+ });
237
+ });
238
+
239
+ it("should create a token transaction using all amount in the token account", async () => {
240
+ jest
241
+ .spyOn(rpcAPI, "getGasEstimation")
242
+ .mockImplementationOnce(async () => new BigNumber(12));
243
+
244
+ const tokenAccountWithBalance = {
245
+ ...tokenAccount,
246
+ balance: new BigNumber(200),
247
+ };
248
+ const account2 = {
249
+ ...account,
250
+ subAccounts: [tokenAccountWithBalance],
251
+ };
252
+
253
+ const tx = await prepareTransaction(account2, {
254
+ ...tokenTransaction,
255
+ amount: new BigNumber(0),
256
+ useAllAmount: true,
257
+ subAccountId: tokenAccountWithBalance.id,
258
+ });
259
+
260
+ expect(tx).toEqual({
261
+ ...tokenTransaction,
262
+ amount: tokenAccountWithBalance.balance,
263
+ useAllAmount: true,
264
+ subAccountId: tokenAccountWithBalance.id,
265
+ data: expectedData(
266
+ tokenTransaction.recipient,
267
+ tokenAccountWithBalance.balance
268
+ ),
269
+ maxFeePerGas: new BigNumber(1),
270
+ maxPriorityFeePerGas: new BigNumber(1),
271
+ gasPrice: undefined,
272
+ gasLimit: new BigNumber(12),
273
+ type: 2,
274
+ });
275
+ });
276
+
277
+ it("should go to gas estimation with a transaction without 0 amount", async () => {
278
+ jest
279
+ .spyOn(rpcAPI, "getGasEstimation")
280
+ .mockImplementationOnce(async () => new BigNumber(12));
281
+
282
+ const tokenAccountWithBalance = {
283
+ ...tokenAccount,
284
+ balance: new BigNumber(200),
285
+ };
286
+ const account2 = {
287
+ ...account,
288
+ subAccounts: [tokenAccountWithBalance],
289
+ };
290
+
291
+ await prepareTransaction(account2, {
292
+ ...tokenTransaction,
293
+ useAllAmount: true,
294
+ subAccountId: tokenAccountWithBalance.id,
295
+ });
296
+
297
+ expect(rpcAPI.getGasEstimation).toBeCalledWith(
298
+ account2,
299
+ expect.objectContaining({
300
+ recipient: tokenAccount.token.contractAddress,
301
+ amount: new BigNumber(0),
302
+ data: expectedData(
303
+ tokenTransaction.recipient,
304
+ tokenAccountWithBalance.balance
305
+ ),
306
+ })
307
+ );
308
+ });
309
+
310
+ it("should return an EIP1559 token transaction", async () => {
311
+ const tokenAccountWithBalance = {
312
+ ...tokenAccount,
313
+ balance: new BigNumber(200),
314
+ };
315
+ const account2 = {
316
+ ...account,
317
+ subAccounts: [tokenAccountWithBalance],
318
+ };
319
+ const tx = await prepareTransaction(account2, tokenTransaction);
320
+
321
+ expect(tx).toEqual({
322
+ ...tokenTransaction,
323
+ data: expectedData(
324
+ tokenTransaction.recipient,
325
+ tokenTransaction.amount
326
+ ),
327
+ maxFeePerGas: new BigNumber(1),
328
+ maxPriorityFeePerGas: new BigNumber(1),
329
+ type: 2,
330
+ });
331
+ });
332
+
333
+ it("should return a legacy token transaction", async () => {
334
+ jest
335
+ .spyOn(rpcAPI, "getFeesEstimation")
336
+ .mockImplementationOnce(async () => ({
337
+ gasPrice: new BigNumber(1),
338
+ maxFeePerGas: null,
339
+ maxPriorityFeePerGas: null,
340
+ }));
341
+
342
+ const tokenAccountWithBalance = {
343
+ ...tokenAccount,
344
+ balance: new BigNumber(200),
345
+ };
346
+ const account2 = {
347
+ ...account,
348
+ subAccounts: [tokenAccountWithBalance],
349
+ };
350
+ const tx = await prepareTransaction(account2, tokenTransaction);
351
+
352
+ expect(tx).toEqual({
353
+ ...tokenTransaction,
354
+ data: expectedData(
355
+ tokenTransaction.recipient,
356
+ tokenTransaction.amount
357
+ ),
358
+ gasPrice: new BigNumber(1),
359
+ type: 0,
360
+ });
361
+ });
362
+ });
363
+ });
364
+
365
+ describe("prepareForSignOperation", () => {
366
+ beforeEach(() => {
367
+ jest
368
+ .spyOn(rpcAPI, "getTransactionCount")
369
+ .mockImplementation(() => Promise.resolve(10));
370
+ });
371
+ afterEach(() => {
372
+ jest.restoreAllMocks();
373
+ });
374
+
375
+ it("should not change a coin transaction", async () => {
376
+ expect(await prepareForSignOperation(account, transaction)).toEqual({
377
+ ...transaction,
378
+ nonce: 10,
379
+ });
380
+ });
381
+
382
+ it("should update a token transaction with the correct recipient", async () => {
383
+ expect(
384
+ await prepareForSignOperation(account, tokenTransaction)
385
+ ).toEqual({
386
+ ...tokenTransaction,
387
+ amount: new BigNumber(0),
388
+ recipient: tokenAccount.token.contractAddress,
389
+ nonce: 10,
390
+ });
391
+ });
392
+ });
393
+ });
394
+ });