@ledgerhq/hw-app-eth 7.0.0-next.0 → 7.0.0-nightly.1

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 (70) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.unimportedrc.json +35 -3
  3. package/CHANGELOG.md +21 -5
  4. package/README.md +2 -1
  5. package/lib/Eth.d.ts.map +1 -1
  6. package/lib/Eth.js +1 -0
  7. package/lib/Eth.js.map +1 -1
  8. package/lib/modules/Uniswap/constants.d.ts +24 -0
  9. package/lib/modules/Uniswap/constants.d.ts.map +1 -0
  10. package/lib/modules/Uniswap/constants.js +59 -0
  11. package/lib/modules/Uniswap/constants.js.map +1 -0
  12. package/lib/modules/Uniswap/decoders.d.ts +3 -0
  13. package/lib/modules/Uniswap/decoders.d.ts.map +1 -0
  14. package/lib/modules/Uniswap/decoders.js +46 -0
  15. package/lib/modules/Uniswap/decoders.js.map +1 -0
  16. package/lib/modules/Uniswap/index.d.ts +44 -0
  17. package/lib/modules/Uniswap/index.d.ts.map +1 -0
  18. package/lib/modules/Uniswap/index.js +143 -0
  19. package/lib/modules/Uniswap/index.js.map +1 -0
  20. package/lib/modules/Uniswap/types.d.ts +3 -0
  21. package/lib/modules/Uniswap/types.d.ts.map +1 -0
  22. package/lib/modules/Uniswap/types.js +3 -0
  23. package/lib/modules/Uniswap/types.js.map +1 -0
  24. package/lib/services/ledger/index.d.ts.map +1 -1
  25. package/lib/services/ledger/index.js +20 -5
  26. package/lib/services/ledger/index.js.map +1 -1
  27. package/lib/services/types.d.ts +1 -0
  28. package/lib/services/types.d.ts.map +1 -1
  29. package/lib/utils.js +1 -1
  30. package/lib/utils.js.map +1 -1
  31. package/lib-es/Eth.d.ts.map +1 -1
  32. package/lib-es/Eth.js +1 -0
  33. package/lib-es/Eth.js.map +1 -1
  34. package/lib-es/modules/Uniswap/constants.d.ts +24 -0
  35. package/lib-es/modules/Uniswap/constants.d.ts.map +1 -0
  36. package/lib-es/modules/Uniswap/constants.js +56 -0
  37. package/lib-es/modules/Uniswap/constants.js.map +1 -0
  38. package/lib-es/modules/Uniswap/decoders.d.ts +3 -0
  39. package/lib-es/modules/Uniswap/decoders.d.ts.map +1 -0
  40. package/lib-es/modules/Uniswap/decoders.js +43 -0
  41. package/lib-es/modules/Uniswap/decoders.js.map +1 -0
  42. package/lib-es/modules/Uniswap/index.d.ts +44 -0
  43. package/lib-es/modules/Uniswap/index.d.ts.map +1 -0
  44. package/lib-es/modules/Uniswap/index.js +137 -0
  45. package/lib-es/modules/Uniswap/index.js.map +1 -0
  46. package/lib-es/modules/Uniswap/types.d.ts +3 -0
  47. package/lib-es/modules/Uniswap/types.d.ts.map +1 -0
  48. package/lib-es/modules/Uniswap/types.js +2 -0
  49. package/lib-es/modules/Uniswap/types.js.map +1 -0
  50. package/lib-es/services/ledger/index.d.ts.map +1 -1
  51. package/lib-es/services/ledger/index.js +20 -5
  52. package/lib-es/services/ledger/index.js.map +1 -1
  53. package/lib-es/services/types.d.ts +1 -0
  54. package/lib-es/services/types.d.ts.map +1 -1
  55. package/lib-es/utils.js +1 -1
  56. package/lib-es/utils.js.map +1 -1
  57. package/package.json +10 -5
  58. package/src/Eth.ts +1 -0
  59. package/src/modules/Uniswap/constants.ts +60 -0
  60. package/src/modules/Uniswap/decoders.ts +62 -0
  61. package/src/modules/Uniswap/index.ts +168 -0
  62. package/src/modules/Uniswap/types.ts +14 -0
  63. package/src/services/ledger/index.ts +22 -5
  64. package/src/services/types.ts +2 -0
  65. package/src/utils.ts +1 -1
  66. package/tests/Eth.unit.test.ts +65 -3
  67. package/tests/Uniswap/decoders.unit.test.ts +101 -0
  68. package/tests/Uniswap/index.unit.test.ts +188 -0
  69. package/tests/fixtures/utils.ts +8 -1
  70. package/tests/ledgerService.unit.test.ts +113 -19
@@ -0,0 +1,188 @@
1
+ import nock from "nock";
2
+ import { ethers } from "ethers";
3
+ import {
4
+ getCommandsAndTokensFromUniswapCalldata,
5
+ isSupported,
6
+ loadInfosForUniswap,
7
+ } from "../../src/modules/Uniswap";
8
+ import {
9
+ UNISWAP_EXECUTE_SELECTOR,
10
+ UNISWAP_UNIVERSAL_ROUTER_ADDRESS,
11
+ } from "../../src/modules/Uniswap/constants";
12
+ import SignatureCALEth from "../fixtures/SignatureCALEth";
13
+
14
+ nock.disableNetConnect();
15
+ jest.mock("@ledgerhq/cryptoassets-evm-signatures/data/evm/index", () => ({
16
+ get signatures() {
17
+ return {
18
+ 1: SignatureCALEth,
19
+ };
20
+ },
21
+ }));
22
+
23
+ describe("Uniswap", () => {
24
+ describe("index", () => {
25
+ describe("isSupported", () => {
26
+ it("should return false for a non-Uniswap transaction", () => {
27
+ expect(isSupported("0x", ethers.constants.AddressZero, 1, [])).toBe(false);
28
+ });
29
+
30
+ it("should return false for a Uniswap transaction with an invalid selector", () => {
31
+ expect(isSupported("0x123456", UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [])).toBe(false);
32
+ });
33
+
34
+ it("should return false for a Uniswap transaction with no commands", () => {
35
+ expect(isSupported(UNISWAP_EXECUTE_SELECTOR, UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [])).toBe(
36
+ false,
37
+ );
38
+ });
39
+
40
+ it("should return false for a transaction of multiple hops with different pool versions", () => {
41
+ expect(
42
+ isSupported(UNISWAP_EXECUTE_SELECTOR, UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [
43
+ ["V2_SWAP_EXACT_IN", ["0x1", "0x2"]],
44
+ ["V3_SWAP_EXACT_IN", ["0x2", "0x3"]],
45
+ ]),
46
+ ).toBe(false);
47
+ });
48
+
49
+ it("should return false for a transaction of multiple hops with non chained assets", () => {
50
+ expect(
51
+ isSupported(UNISWAP_EXECUTE_SELECTOR, UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [
52
+ ["V2_SWAP_EXACT_IN", ["0x0A", "0x0B"]],
53
+ ["V2_SWAP_EXACT_IN", ["0x0A", "0x0C"]],
54
+ ]),
55
+ ).toBe(false);
56
+ });
57
+
58
+ it("should return false for a transaction of multiple hops with non chained assets", () => {
59
+ expect(
60
+ isSupported(UNISWAP_EXECUTE_SELECTOR, UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [
61
+ ["V2_SWAP_EXACT_IN", ["0x0A", "0x0B"]],
62
+ ["V2_SWAP_EXACT_IN", ["0x0A", "0x0C"]],
63
+ ]),
64
+ ).toBe(false);
65
+ });
66
+
67
+ it("should return true for a valid Uniswap transaction", () => {
68
+ expect(
69
+ isSupported(UNISWAP_EXECUTE_SELECTOR, UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [
70
+ ["V2_SWAP_EXACT_IN", ["0x0A", "0x0B"]],
71
+ ["V2_SWAP_EXACT_IN", ["0x0B", "0x0C"]],
72
+ ]),
73
+ ).toBe(true);
74
+ expect(
75
+ isSupported(UNISWAP_EXECUTE_SELECTOR, UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [
76
+ ["V3_SWAP_EXACT_IN", ["0x0A", "0x0B"]],
77
+ ["V3_SWAP_EXACT_IN", ["0x0B", "0x0C"]],
78
+ ]),
79
+ ).toBe(true);
80
+ expect(
81
+ isSupported(UNISWAP_EXECUTE_SELECTOR, UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [
82
+ ["V3_SWAP_EXACT_IN", ["0x0A", "0x0B"]],
83
+ ["V3_SWAP_EXACT_OUT", ["0x0B", "0x0C"]],
84
+ ]),
85
+ ).toBe(true);
86
+ expect(
87
+ isSupported(UNISWAP_EXECUTE_SELECTOR, UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [
88
+ ["V2_SWAP_EXACT_IN", ["0x0A", "0x0B"]],
89
+ ["V2_SWAP_EXACT_OUT", ["0x0B", "0x0C"]],
90
+ ]),
91
+ ).toBe(true);
92
+ });
93
+ });
94
+
95
+ describe("getCommandsAndTokensFromUniswapCalldata", () => {
96
+ it("should return the commands and tokens from a Uniswap calldata", () => {
97
+ // see tx 0xc4df7ccc0527541d0e80856a8f38deedc48c84825e9355469ba02d873502ce2f
98
+ const calldata =
99
+ "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000669b9ec100000000000000000000000000000000000000000000000000000000000000030a010c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000016000000000000000000000000055747be9f9f5beb232ad59fe7af013b81d95fd5e000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000066c32b0d0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b00000000000000000000000000000000000000000000000000000000669b9ec100000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000410d756f55acf289e9754faf91bba0a704b5c7c0aa4b1dfd551115ccbe4c7f290234e1a14265e1da0bc872a23627d997fe37a689c290d519f7b8c9bdde1b79108e1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000030ba49cbff5a00000000000000000000000000000000000000000000000089677c957272141800000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200271055747be9f9f5beb232ad59fe7af013b81d95fd5e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000030ba49cbff5a000";
100
+
101
+ expect(getCommandsAndTokensFromUniswapCalldata(calldata, 1)).toEqual([
102
+ ["PERMIT2_PERMIT", []],
103
+ [
104
+ "V3_SWAP_EXACT_OUT",
105
+ [
106
+ "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
107
+ "0x55747be9f9f5beb232ad59fe7af013b81d95fd5e",
108
+ ],
109
+ ],
110
+ ["UNWRAP_ETH", ["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"]],
111
+ ]);
112
+ });
113
+
114
+ it("should return an undefined command for unsupported commands by the Uniswap plugin", () => {
115
+ // see tx 0xc0668eb799ba8b73a396529a183b67e7905c0f70143680915ee5802b3036257b
116
+ const calldata =
117
+ "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000669b9f4700000000000000000000000000000000000000000000000000000000000000030b000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000004fdf8403a58c8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000ff436c9cd1052265be510e00a661f432c539080000000000000000000000000000000000000000000000000004fdf8403a58c80000000000000000000000000000000000000000000ac736c3566d2e76b2c5978300000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20027103ffeea07a27fab7ad1df5297fa75e77a43cb5790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006f0279540eed3fb666e8aaa4571a47e7478b6e9d000000000000000000000000000000000000000000000000000ce8a624ca9800";
118
+
119
+ expect(getCommandsAndTokensFromUniswapCalldata(calldata, 1)).toEqual([
120
+ ["WRAP_ETH", ["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"]],
121
+ [
122
+ "V3_SWAP_EXACT_IN",
123
+ [
124
+ "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
125
+ "0x3ffeea07a27fab7ad1df5297fa75e77a43cb5790",
126
+ ],
127
+ ],
128
+ [undefined, []],
129
+ ]);
130
+ });
131
+
132
+ it("should return an empty array for an invalid selector", () => {
133
+ const calldata =
134
+ "0x123456000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000669b9ec100000000000000000000000000000000000000000000000000000000000000030a010c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000016000000000000000000000000055747be9f9f5beb232ad59fe7af013b81d95fd5e000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000066c32b0d0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b00000000000000000000000000000000000000000000000000000000669b9ec100000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000410d756f55acf289e9754faf91bba0a704b5c7c0aa4b1dfd551115ccbe4c7f290234e1a14265e1da0bc872a23627d997fe37a689c290d519f7b8c9bdde1b79108e1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000030ba49cbff5a00000000000000000000000000000000000000000000000089677c957272141800000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200271055747be9f9f5beb232ad59fe7af013b81d95fd5e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000030ba49cbff5a000";
135
+ expect(getCommandsAndTokensFromUniswapCalldata(calldata, 1)).toEqual([]);
136
+ });
137
+ });
138
+
139
+ describe("loadInfosForUniswap", () => {
140
+ it("should return ERC20 & plugin descriptors for a valid Uniswap transaction", async () => {
141
+ // see tx 0x88a065f47c82545b0620378d6cb2231713464dd58c0a2b8d6e9485740807b573
142
+ const calldata =
143
+ "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000669bad2800000000000000000000000000000000000000000000000000000000000000040a08060c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000bdaa645097ef80f9d475f341d0d107a45b3a000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000687ce06c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b00000000000000000000000000000000000000000000000000000000687ce06c00000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000004142794d71565541a435d1bc910db84510c4d31ad35ff8c046222b2c232fcd99a6256a8f1abe7d42a44f3d92d65f9232db76df2fbef3dfa76eca64b034ed4df5321c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000001bffcca36d953d12266cb000000000000000000000000000000000000000000000000027c3dea3f90dafd00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000bdaa645097ef80f9d475f341d0d107a45b3a000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006eb940753b4b52fbec8d33c418133fdb0d4405e6000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000001dc813a4ee4410f144ae2122051c1cfc436f24a00000000000000000000000000000000000000000000000000275e122c92b911e";
144
+
145
+ const transaction: ethers.Transaction = {
146
+ to: UNISWAP_UNIVERSAL_ROUTER_ADDRESS,
147
+ data: calldata,
148
+ gasLimit: ethers.BigNumber.from(21_000),
149
+ nonce: 0,
150
+ value: ethers.BigNumber.from(0),
151
+ chainId: 1,
152
+ };
153
+
154
+ const res = await loadInfosForUniswap(transaction, 1);
155
+ expect(res).toEqual({
156
+ pluginData: Buffer.from(
157
+ "07556e69737761703fc91a3afd70395cd496c647d5a6cc9d4b2b7fad3593564c3044022014391e8f355867a57fe88f6a5a4dbcb8bf8f888a9db3ff3449caf72d120396bd02200c13d9c3f79400fe0aa0434ac54d59b79503c9964a4abc3e8cd22763e0242935",
158
+ "hex",
159
+ ),
160
+ tokenDescriptors: [
161
+ Buffer.from(
162
+ "0457455448c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000012000000013045022100b47ee8551c15a2cf681c649651e987d7e527c481d27c38da1f971a8242792bd3022069c3f688ac5493a23dab5798e3c9b07484765069e1d4be14321aae4d92cb8cbe",
163
+ "hex",
164
+ ),
165
+ Buffer.from(
166
+ "0457455448c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000012000000013045022100b47ee8551c15a2cf681c649651e987d7e527c481d27c38da1f971a8242792bd3022069c3f688ac5493a23dab5798e3c9b07484765069e1d4be14321aae4d92cb8cbe",
167
+ "hex",
168
+ ),
169
+ ],
170
+ });
171
+ });
172
+
173
+ it("should return an empty object for an unsupported Uniswap transaction", async () => {
174
+ const transaction: ethers.Transaction = {
175
+ to: ethers.constants.AddressZero,
176
+ data: "0x",
177
+ gasLimit: ethers.BigNumber.from(21_000),
178
+ nonce: 0,
179
+ value: ethers.BigNumber.from(0),
180
+ chainId: 1,
181
+ };
182
+
183
+ const res = await loadInfosForUniswap(transaction, 1);
184
+ expect(res).toEqual({});
185
+ });
186
+ });
187
+ });
188
+ });
@@ -17,6 +17,7 @@ export const transactionContracts = {
17
17
  paraswap: "0xdef171fe48cf0115b1d80b88dc8eab59176fee57",
18
18
  random: "0xc3f95102D5c8F2c83e49Ce3Acfb905eDfb7f37dE", // jesus.eth
19
19
  random2: "0xc3f95102D5c8F2c83e49Ce3Acfb905eDfb7f37dE", // satan.eth
20
+ uniswapUniversaRouter: "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD",
20
21
  };
21
22
 
22
23
  export const transactionData = {
@@ -92,9 +93,15 @@ export const transactionData = {
92
93
  ["0x4de55ce50407b614daff085522d476c5ec5e93a00afb"],
93
94
  ]),
94
95
  },
96
+ uniswap: {
97
+ ["permit2>swap-out-v3>unwrap"]:
98
+ "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000669ba25a00000000000000000000000000000000000000000000000000000000000000030a010c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000016000000000000000000000000055747be9f9f5beb232ad59fe7af013b81d95fd5e000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000066c32ea60000000000000000000000000000000000000000000000000000000000000006000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b00000000000000000000000000000000000000000000000000000000669ba25a00000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000413cbf00ab90b6d1b17401cbf49e00c40f98bcb3f39461ca65e26009f9e9f77029279a4587efa2d792ea61ede56e0fbd7c1305007bc59d09bc60eaba46efa23edd1c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000233a3559d9da00000000000000000000000000000000000000000000000062e76d8ff4b926e800000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200271055747be9f9f5beb232ad59fe7af013b81d95fd5e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000233a3559d9da000",
99
+ ["wrap>swap-in-v3"]:
100
+ "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000669bbbd800000000000000000000000000000000000000000000000000000000000000020b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000019c1d62a9f2000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000019c1d62a9f200000000000000000000000000000000000000000000000004227ffe925d0fc0000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200271055747be9f9f5beb232ad59fe7af013b81d95fd5e000000000000000000000000000000000000000000",
101
+ },
95
102
  };
96
103
 
97
- export const getTransactionHash = (to: string, data: string): string =>
104
+ export const getSerializedTransaction = (to: string, data: string): string =>
98
105
  ethers.utils
99
106
  .serializeTransaction({
100
107
  to,
@@ -1,11 +1,13 @@
1
1
  import axios from "axios";
2
- import { getTransactionHash, transactionData, transactionContracts } from "./fixtures/utils";
2
+ import { getSerializedTransaction, transactionData, transactionContracts } from "./fixtures/utils";
3
3
  import { ERC1155_CLEAR_SIGNED_SELECTORS, ERC721_CLEAR_SIGNED_SELECTORS } from "../src/utils";
4
4
  import partialPluginResponse from "./fixtures/REST/Paraswap-Plugin.json";
5
5
  import * as contractServices from "../src/services/ledger/contracts";
6
6
  import { getLoadConfig } from "../src/services/ledger/loadConfig";
7
7
  import * as erc20Services from "../src/services/ledger/erc20";
8
8
  import * as nftServices from "../src/services/ledger/nfts";
9
+ import signatureCALEth from "./fixtures/SignatureCALEth";
10
+ import * as uniswapModule from "../src/modules/Uniswap";
9
11
  import { ResolutionConfig } from "../src/services/types";
10
12
  import { ledgerService } from "../src/Eth";
11
13
 
@@ -14,6 +16,7 @@ const resolutionConfig: ResolutionConfig = {
14
16
  nft: true,
15
17
  erc20: true,
16
18
  externalPlugins: true,
19
+ uniswapV3: true,
17
20
  };
18
21
 
19
22
  jest.mock("axios");
@@ -23,6 +26,8 @@ jest.spyOn(nftServices, "loadNftPlugin");
23
26
  jest.spyOn(nftServices, "getNFTInfo");
24
27
  jest.spyOn(erc20Services, "findERC20SignaturesInfo");
25
28
  jest.spyOn(erc20Services, "byContractAddressAndChainId");
29
+ jest.spyOn(uniswapModule, "loadInfosForUniswap");
30
+ jest.spyOn(uniswapModule, "getCommandsAndTokensFromUniswapCalldata");
26
31
 
27
32
  describe("Ledger Service", () => {
28
33
  describe("Transaction resolution", () => {
@@ -34,7 +39,7 @@ describe("Ledger Service", () => {
34
39
  it("should resolve an ERC20 approve", async () => {
35
40
  // @ts-expect-error not casted as jest mock
36
41
  axios.get.mockImplementation(async () => null);
37
- const txHash = getTransactionHash(
42
+ const txHash = getSerializedTransaction(
38
43
  transactionContracts.erc20,
39
44
  transactionData.erc20.approve,
40
45
  );
@@ -63,7 +68,7 @@ describe("Ledger Service", () => {
63
68
  it("should resolve an ERC20 transfer", async () => {
64
69
  // @ts-expect-error not casted as jest mock
65
70
  axios.get.mockImplementation(async () => null);
66
- const txHash = getTransactionHash(
71
+ const txHash = getSerializedTransaction(
67
72
  transactionContracts.erc20,
68
73
  transactionData.erc20.transfer,
69
74
  );
@@ -92,7 +97,7 @@ describe("Ledger Service", () => {
92
97
  it("should not resolve an approve with a non ERC20 or NFT contract", async () => {
93
98
  // @ts-expect-error not casted as jest mock
94
99
  axios.get.mockImplementation(async () => null);
95
- const txHash = getTransactionHash(
100
+ const txHash = getSerializedTransaction(
96
101
  transactionContracts.random,
97
102
  transactionData.erc20.approve,
98
103
  );
@@ -119,7 +124,7 @@ describe("Ledger Service", () => {
119
124
  it("should not resolve a transfer with a non ERC20 or NFT contract", async () => {
120
125
  // @ts-expect-error not casted as jest mock
121
126
  axios.get.mockImplementation(async () => null);
122
- const txHash = getTransactionHash(
127
+ const txHash = getSerializedTransaction(
123
128
  transactionContracts.random,
124
129
  transactionData.erc20.transfer,
125
130
  );
@@ -177,7 +182,7 @@ describe("Ledger Service", () => {
177
182
  nftAxiosMocker(url, ERC721_CLEAR_SIGNED_SELECTORS.APPROVE),
178
183
  );
179
184
 
180
- const txHash = getTransactionHash(
185
+ const txHash = getSerializedTransaction(
181
186
  transactionContracts.erc721,
182
187
  transactionData.erc721.approve,
183
188
  );
@@ -207,7 +212,7 @@ describe("Ledger Service", () => {
207
212
  nftAxiosMocker(url, ERC721_CLEAR_SIGNED_SELECTORS.SET_APPROVAL_FOR_ALL),
208
213
  );
209
214
 
210
- const txHash = getTransactionHash(
215
+ const txHash = getSerializedTransaction(
211
216
  transactionContracts.erc721,
212
217
  transactionData.erc721.setApprovalForAll,
213
218
  );
@@ -237,7 +242,7 @@ describe("Ledger Service", () => {
237
242
  nftAxiosMocker(url, ERC721_CLEAR_SIGNED_SELECTORS.TRANSFER_FROM),
238
243
  );
239
244
 
240
- const txHash = getTransactionHash(
245
+ const txHash = getSerializedTransaction(
241
246
  transactionContracts.erc721,
242
247
  transactionData.erc721.transferFrom,
243
248
  );
@@ -267,7 +272,7 @@ describe("Ledger Service", () => {
267
272
  nftAxiosMocker(url, ERC721_CLEAR_SIGNED_SELECTORS.SAFE_TRANSFER_FROM),
268
273
  );
269
274
 
270
- const txHash = getTransactionHash(
275
+ const txHash = getSerializedTransaction(
271
276
  transactionContracts.erc721,
272
277
  transactionData.erc721.safeTransferFrom,
273
278
  );
@@ -297,7 +302,7 @@ describe("Ledger Service", () => {
297
302
  nftAxiosMocker(url, ERC721_CLEAR_SIGNED_SELECTORS.SAFE_TRANSFER_FROM_WITH_DATA),
298
303
  );
299
304
 
300
- const txHash = getTransactionHash(
305
+ const txHash = getSerializedTransaction(
301
306
  transactionContracts.erc721,
302
307
  transactionData.erc721.safeTransferFromWithData,
303
308
  );
@@ -327,7 +332,7 @@ describe("Ledger Service", () => {
327
332
  nftAxiosMocker(url, ERC721_CLEAR_SIGNED_SELECTORS.SAFE_TRANSFER_FROM),
328
333
  );
329
334
 
330
- const txHash = getTransactionHash(
335
+ const txHash = getSerializedTransaction(
331
336
  transactionContracts.erc20,
332
337
  transactionData.erc721.safeTransferFrom,
333
338
  );
@@ -357,7 +362,7 @@ describe("Ledger Service", () => {
357
362
  nftAxiosMocker(url, ERC721_CLEAR_SIGNED_SELECTORS.SAFE_TRANSFER_FROM),
358
363
  );
359
364
 
360
- const txHash = getTransactionHash(
365
+ const txHash = getSerializedTransaction(
361
366
  transactionContracts.random,
362
367
  transactionData.erc721.safeTransferFrom,
363
368
  );
@@ -415,7 +420,7 @@ describe("Ledger Service", () => {
415
420
  nftAxiosMocker(url, ERC1155_CLEAR_SIGNED_SELECTORS.SET_APPROVAL_FOR_ALL),
416
421
  );
417
422
 
418
- const txHash = getTransactionHash(
423
+ const txHash = getSerializedTransaction(
419
424
  transactionContracts.erc1155,
420
425
  transactionData.erc1155.setApprovalForAll,
421
426
  );
@@ -445,7 +450,7 @@ describe("Ledger Service", () => {
445
450
  nftAxiosMocker(url, ERC1155_CLEAR_SIGNED_SELECTORS.SAFE_TRANSFER_FROM),
446
451
  );
447
452
 
448
- const txHash = getTransactionHash(
453
+ const txHash = getSerializedTransaction(
449
454
  transactionContracts.erc1155,
450
455
  transactionData.erc1155.safeTransferFrom,
451
456
  );
@@ -475,7 +480,7 @@ describe("Ledger Service", () => {
475
480
  nftAxiosMocker(url, ERC1155_CLEAR_SIGNED_SELECTORS.SAFE_BATCH_TRANSFER_FROM),
476
481
  );
477
482
 
478
- const txHash = getTransactionHash(
483
+ const txHash = getSerializedTransaction(
479
484
  transactionContracts.erc1155,
480
485
  transactionData.erc1155.safeBatchTransferFrom,
481
486
  );
@@ -505,7 +510,7 @@ describe("Ledger Service", () => {
505
510
  nftAxiosMocker(url, ERC1155_CLEAR_SIGNED_SELECTORS.SAFE_TRANSFER_FROM),
506
511
  );
507
512
 
508
- const txHash = getTransactionHash(
513
+ const txHash = getSerializedTransaction(
509
514
  transactionContracts.erc20,
510
515
  transactionData.erc1155.safeTransferFrom,
511
516
  );
@@ -535,7 +540,7 @@ describe("Ledger Service", () => {
535
540
  nftAxiosMocker(url, ERC1155_CLEAR_SIGNED_SELECTORS.SAFE_TRANSFER_FROM),
536
541
  );
537
542
 
538
- const txHash = getTransactionHash(
543
+ const txHash = getSerializedTransaction(
539
544
  transactionContracts.random,
540
545
  transactionData.erc1155.safeTransferFrom,
541
546
  );
@@ -571,7 +576,7 @@ describe("Ledger Service", () => {
571
576
  return null;
572
577
  });
573
578
 
574
- const txHash = getTransactionHash(
579
+ const txHash = getSerializedTransaction(
575
580
  transactionContracts.paraswap,
576
581
  transactionData.paraswap.simpleSwap,
577
582
  );
@@ -613,7 +618,7 @@ describe("Ledger Service", () => {
613
618
  return null;
614
619
  });
615
620
 
616
- const txHash = getTransactionHash(
621
+ const txHash = getSerializedTransaction(
617
622
  transactionContracts.paraswap,
618
623
  transactionData.paraswap.swapOnUniswapV2Fork,
619
624
  );
@@ -646,5 +651,94 @@ describe("Ledger Service", () => {
646
651
  });
647
652
  });
648
653
  });
654
+
655
+ describe("UNISWAP", () => {
656
+ it("should resolve a Uniswap universal router transaction (permit2>swap-out-v3>unwrap)", async () => {
657
+ // @ts-expect-error not casted as jest mock
658
+ axios.get.mockImplementation(async (uri: string) => {
659
+ if (uri.endsWith("evm/1/erc20-signatures.json")) {
660
+ return { data: signatureCALEth };
661
+ }
662
+ return null;
663
+ });
664
+
665
+ const txHash = getSerializedTransaction(
666
+ transactionContracts.uniswapUniversaRouter,
667
+ transactionData.uniswap["permit2>swap-out-v3>unwrap"],
668
+ );
669
+ const resolution = await ledgerService.resolveTransaction(
670
+ txHash,
671
+ loadConfig,
672
+ resolutionConfig,
673
+ );
674
+
675
+ expect(resolution).toEqual({
676
+ domains: [],
677
+ erc20Tokens: [
678
+ "0457455448c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000012000000013045022100b47ee8551c15a2cf681c649651e987d7e527c481d27c38da1f971a8242792bd3022069c3f688ac5493a23dab5798e3c9b07484765069e1d4be14321aae4d92cb8cbe",
679
+ "0457455448c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000012000000013045022100b47ee8551c15a2cf681c649651e987d7e527c481d27c38da1f971a8242792bd3022069c3f688ac5493a23dab5798e3c9b07484765069e1d4be14321aae4d92cb8cbe",
680
+ ],
681
+ nfts: [],
682
+ externalPlugin: [
683
+ {
684
+ payload:
685
+ "07556e69737761703fc91a3afd70395cd496c647d5a6cc9d4b2b7fad3593564c3044022014391e8f355867a57fe88f6a5a4dbcb8bf8f888a9db3ff3449caf72d120396bd02200c13d9c3f79400fe0aa0434ac54d59b79503c9964a4abc3e8cd22763e0242935",
686
+ signature: "",
687
+ },
688
+ ],
689
+ plugin: [],
690
+ });
691
+ expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(0);
692
+ expect(erc20Services.findERC20SignaturesInfo).toHaveBeenCalledTimes(3);
693
+ expect(erc20Services.byContractAddressAndChainId).toHaveBeenCalledTimes(3);
694
+ expect(uniswapModule.loadInfosForUniswap).toHaveBeenCalledTimes(1);
695
+ expect(uniswapModule.getCommandsAndTokensFromUniswapCalldata).toHaveBeenCalledTimes(1);
696
+ expect(nftServices.getNFTInfo).not.toHaveBeenCalled();
697
+ expect(nftServices.loadNftPlugin).not.toHaveBeenCalled();
698
+ });
699
+ it("should resolve a Uniswap universal router transaction (wrap>swap-in-v3)", async () => {
700
+ // @ts-expect-error not casted as jest mock
701
+ axios.get.mockImplementation(async (uri: string) => {
702
+ if (uri.endsWith("evm/1/erc20-signatures.json")) {
703
+ return { data: signatureCALEth };
704
+ }
705
+ return null;
706
+ });
707
+
708
+ const txHash = getSerializedTransaction(
709
+ transactionContracts.uniswapUniversaRouter,
710
+ transactionData.uniswap["wrap>swap-in-v3"],
711
+ );
712
+ const resolution = await ledgerService.resolveTransaction(
713
+ txHash,
714
+ loadConfig,
715
+ resolutionConfig,
716
+ );
717
+
718
+ expect(resolution).toEqual({
719
+ domains: [],
720
+ erc20Tokens: [
721
+ "0457455448c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000012000000013045022100b47ee8551c15a2cf681c649651e987d7e527c481d27c38da1f971a8242792bd3022069c3f688ac5493a23dab5798e3c9b07484765069e1d4be14321aae4d92cb8cbe",
722
+ "0457455448c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000012000000013045022100b47ee8551c15a2cf681c649651e987d7e527c481d27c38da1f971a8242792bd3022069c3f688ac5493a23dab5798e3c9b07484765069e1d4be14321aae4d92cb8cbe",
723
+ ],
724
+ nfts: [],
725
+ externalPlugin: [
726
+ {
727
+ payload:
728
+ "07556e69737761703fc91a3afd70395cd496c647d5a6cc9d4b2b7fad3593564c3044022014391e8f355867a57fe88f6a5a4dbcb8bf8f888a9db3ff3449caf72d120396bd02200c13d9c3f79400fe0aa0434ac54d59b79503c9964a4abc3e8cd22763e0242935",
729
+ signature: "",
730
+ },
731
+ ],
732
+ plugin: [],
733
+ });
734
+ expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(0);
735
+ expect(erc20Services.findERC20SignaturesInfo).toHaveBeenCalledTimes(3);
736
+ expect(erc20Services.byContractAddressAndChainId).toHaveBeenCalledTimes(3);
737
+ expect(uniswapModule.loadInfosForUniswap).toHaveBeenCalledTimes(1);
738
+ expect(uniswapModule.getCommandsAndTokensFromUniswapCalldata).toHaveBeenCalledTimes(1);
739
+ expect(nftServices.getNFTInfo).not.toHaveBeenCalled();
740
+ expect(nftServices.loadNftPlugin).not.toHaveBeenCalled();
741
+ });
742
+ });
649
743
  });
650
744
  });