@ledgerhq/live-common 34.55.0-nightly.20251219024040 → 34.55.0-nightly.20251223024125

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 (34) hide show
  1. package/lib/account/recentAddresses.d.ts +2 -1
  2. package/lib/account/recentAddresses.d.ts.map +1 -1
  3. package/lib/account/recentAddresses.js +27 -9
  4. package/lib/account/recentAddresses.js.map +1 -1
  5. package/lib/e2e/enum/Provider.d.ts.map +1 -1
  6. package/lib/e2e/enum/Provider.js +3 -1
  7. package/lib/e2e/enum/Provider.js.map +1 -1
  8. package/lib/exchange/providers/swap.d.ts.map +1 -1
  9. package/lib/exchange/providers/swap.js +2 -0
  10. package/lib/exchange/providers/swap.js.map +1 -1
  11. package/lib/families/canton/react.d.ts.map +1 -1
  12. package/lib/families/canton/react.js +11 -9
  13. package/lib/families/canton/react.js.map +1 -1
  14. package/lib-es/account/recentAddresses.d.ts +2 -1
  15. package/lib-es/account/recentAddresses.d.ts.map +1 -1
  16. package/lib-es/account/recentAddresses.js +27 -9
  17. package/lib-es/account/recentAddresses.js.map +1 -1
  18. package/lib-es/e2e/enum/Provider.d.ts.map +1 -1
  19. package/lib-es/e2e/enum/Provider.js +3 -1
  20. package/lib-es/e2e/enum/Provider.js.map +1 -1
  21. package/lib-es/exchange/providers/swap.d.ts.map +1 -1
  22. package/lib-es/exchange/providers/swap.js +2 -0
  23. package/lib-es/exchange/providers/swap.js.map +1 -1
  24. package/lib-es/families/canton/react.d.ts.map +1 -1
  25. package/lib-es/families/canton/react.js +11 -9
  26. package/lib-es/families/canton/react.js.map +1 -1
  27. package/package.json +75 -75
  28. package/src/account/recentAddresses.test.ts +115 -15
  29. package/src/account/recentAddresses.ts +37 -13
  30. package/src/e2e/enum/Provider.ts +3 -1
  31. package/src/exchange/providers/swap.ts +2 -0
  32. package/src/families/canton/react.test.ts +24 -20
  33. package/src/families/canton/react.ts +12 -7
  34. package/src/families/hedera/__snapshots__/bridge.integration.test.ts.snap +758 -34
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ledgerhq/live-common",
3
3
  "description": "Common ground for the Ledger Live apps",
4
- "version": "34.55.0-nightly.20251219024040",
4
+ "version": "34.55.0-nightly.20251223024125",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/LedgerHQ/ledger-live.git"
@@ -145,7 +145,7 @@
145
145
  "@zondax/ledger-mina-js": "0.0.2",
146
146
  "@zondax/ledger-stacks": "^1.0.2",
147
147
  "async": "^3.2.3",
148
- "axios": "1.12.2",
148
+ "axios": "1.13.2",
149
149
  "bech32": "^1.1.3",
150
150
  "bignumber.js": "^9.1.2",
151
151
  "bip32": "^2.0.6",
@@ -178,79 +178,79 @@
178
178
  "xstate": "^5.19.2",
179
179
  "yargs": "^17.0.0",
180
180
  "zod": "^3.22.4",
181
- "@ledgerhq/coin-algorand": "^0.15.0-nightly.20251219024040",
182
- "@ledgerhq/coin-aptos": "^3.8.0-nightly.20251219024040",
183
- "@ledgerhq/coin-bitcoin": "^0.27.0-nightly.20251219024040",
184
- "@ledgerhq/coin-cardano": "^0.16.0-nightly.20251219024040",
185
- "@ledgerhq/coin-casper": "^2.5.0-nightly.20251219024040",
186
- "@ledgerhq/coin-canton": "^0.12.0-nightly.20251219024040",
187
- "@ledgerhq/coin-cosmos": "^0.21.0-nightly.20251219024040",
188
- "@ledgerhq/coin-celo": "^1.8.0-nightly.20251219024040",
189
- "@ledgerhq/coin-evm": "^2.37.0-nightly.20251219024040",
190
- "@ledgerhq/coin-filecoin": "^1.16.0-nightly.20251219024040",
191
- "@ledgerhq/coin-framework": "^6.11.0-nightly.20251219024040",
192
- "@ledgerhq/coin-hedera": "^1.16.0-nightly.20251219024040",
193
- "@ledgerhq/coin-icon": "^0.16.0-nightly.20251219024040",
194
- "@ledgerhq/coin-internet_computer": "^1.12.0-nightly.20251219024040",
195
- "@ledgerhq/coin-mina": "^1.5.0-nightly.20251219024040",
196
- "@ledgerhq/coin-near": "^0.17.0-nightly.20251219024040",
197
- "@ledgerhq/coin-multiversx": "^0.9.0-nightly.20251219024040",
198
- "@ledgerhq/coin-solana": "^0.39.0-nightly.20251219024040",
199
- "@ledgerhq/coin-kaspa": "^1.6.0-nightly.20251219024040",
200
- "@ledgerhq/coin-polkadot": "^6.14.0-nightly.20251219024040",
201
- "@ledgerhq/coin-stacks": "^0.13.0-nightly.20251219024040",
202
- "@ledgerhq/coin-stellar": "^6.9.0-nightly.20251219024040",
203
- "@ledgerhq/coin-tezos": "^6.11.0-nightly.20251219024040",
204
- "@ledgerhq/coin-sui": "^0.19.0-nightly.20251219024040",
205
- "@ledgerhq/coin-ton": "^0.18.0-nightly.20251219024040",
206
- "@ledgerhq/coin-tron": "^5.8.0-nightly.20251219024040",
207
- "@ledgerhq/coin-vechain": "^2.14.0-nightly.20251219024040",
208
- "@ledgerhq/coin-xrp": "^7.10.0-nightly.20251219024040",
209
- "@ledgerhq/cryptoassets": "^13.35.0-nightly.20251219024040",
210
- "@ledgerhq/device-core": "^0.6.11-nightly.20251219024040",
211
- "@ledgerhq/devices": "8.8.0-nightly.20251219024040",
212
- "@ledgerhq/errors": "^6.28.0-nightly.20251219024040",
213
- "@ledgerhq/hw-app-algorand": "^6.31.11-nightly.20251219024040",
214
- "@ledgerhq/hw-app-aptos": "^6.34.11-nightly.20251219024040",
215
- "@ledgerhq/hw-app-btc": "^10.14.0-nightly.20251219024040",
216
- "@ledgerhq/hw-app-celo": "^6.35.6-nightly.20251219024040",
217
- "@ledgerhq/hw-app-eth": "^7.1.0-nightly.20251219024040",
218
- "@ledgerhq/hw-app-cosmos": "^6.32.11-nightly.20251219024040",
219
- "@ledgerhq/hw-app-exchange": "^0.18.2-nightly.20251219024040",
220
- "@ledgerhq/hw-app-hedera": "^1.2.11-nightly.20251219024040",
221
- "@ledgerhq/hw-app-icon": "^1.3.11-nightly.20251219024040",
222
- "@ledgerhq/hw-app-kaspa": "^1.3.4-nightly.20251219024040",
223
- "@ledgerhq/hw-app-multiversx": "^6.26.2-nightly.20251219024040",
224
- "@ledgerhq/hw-app-near": "^6.31.11-nightly.20251219024040",
225
- "@ledgerhq/hw-app-polkadot": "^6.34.11-nightly.20251219024040",
226
- "@ledgerhq/hw-app-str": "^7.3.0-nightly.20251219024040",
181
+ "@ledgerhq/coin-algorand": "^0.15.0-nightly.20251223024125",
182
+ "@ledgerhq/coin-aptos": "^3.8.0-nightly.20251223024125",
183
+ "@ledgerhq/coin-bitcoin": "^0.27.0-nightly.20251223024125",
184
+ "@ledgerhq/coin-canton": "^0.12.0-nightly.20251223024125",
185
+ "@ledgerhq/coin-cardano": "^0.16.0-nightly.20251223024125",
186
+ "@ledgerhq/coin-casper": "^2.5.0-nightly.20251223024125",
187
+ "@ledgerhq/coin-celo": "^1.8.0-nightly.20251223024125",
188
+ "@ledgerhq/coin-cosmos": "^0.21.0-nightly.20251223024125",
189
+ "@ledgerhq/coin-evm": "^2.37.0-nightly.20251223024125",
190
+ "@ledgerhq/coin-filecoin": "^1.16.0-nightly.20251223024125",
191
+ "@ledgerhq/coin-framework": "^6.11.0-nightly.20251223024125",
192
+ "@ledgerhq/coin-hedera": "^1.16.0-nightly.20251223024125",
193
+ "@ledgerhq/coin-icon": "^0.16.0-nightly.20251223024125",
194
+ "@ledgerhq/coin-internet_computer": "^1.12.0-nightly.20251223024125",
195
+ "@ledgerhq/coin-kaspa": "^1.6.0-nightly.20251223024125",
196
+ "@ledgerhq/coin-mina": "^1.5.0-nightly.20251223024125",
197
+ "@ledgerhq/coin-multiversx": "^0.9.0-nightly.20251223024125",
198
+ "@ledgerhq/coin-near": "^0.17.0-nightly.20251223024125",
199
+ "@ledgerhq/coin-polkadot": "^6.14.0-nightly.20251223024125",
200
+ "@ledgerhq/coin-solana": "^0.39.0-nightly.20251223024125",
201
+ "@ledgerhq/coin-stacks": "^0.13.0-nightly.20251223024125",
202
+ "@ledgerhq/coin-stellar": "^6.9.0-nightly.20251223024125",
203
+ "@ledgerhq/coin-sui": "^0.19.0-nightly.20251223024125",
204
+ "@ledgerhq/coin-tezos": "^6.11.0-nightly.20251223024125",
205
+ "@ledgerhq/coin-ton": "^0.18.0-nightly.20251223024125",
206
+ "@ledgerhq/coin-tron": "^5.8.0-nightly.20251223024125",
207
+ "@ledgerhq/coin-vechain": "^2.14.0-nightly.20251223024125",
208
+ "@ledgerhq/coin-xrp": "^7.10.0-nightly.20251223024125",
209
+ "@ledgerhq/cryptoassets": "^13.35.0-nightly.20251223024125",
210
+ "@ledgerhq/device-core": "^0.6.11-nightly.20251223024125",
211
+ "@ledgerhq/devices": "8.8.0-nightly.20251223024125",
212
+ "@ledgerhq/errors": "^6.28.0-nightly.20251223024125",
213
+ "@ledgerhq/hw-app-algorand": "^6.31.11-nightly.20251223024125",
214
+ "@ledgerhq/hw-app-aptos": "^6.34.11-nightly.20251223024125",
215
+ "@ledgerhq/hw-app-btc": "^10.14.0-nightly.20251223024125",
216
+ "@ledgerhq/hw-app-celo": "^6.35.6-nightly.20251223024125",
217
+ "@ledgerhq/hw-app-cosmos": "^6.32.11-nightly.20251223024125",
218
+ "@ledgerhq/hw-app-eth": "^7.1.0-nightly.20251223024125",
219
+ "@ledgerhq/hw-app-exchange": "^0.18.2-nightly.20251223024125",
220
+ "@ledgerhq/hw-app-hedera": "^1.2.11-nightly.20251223024125",
221
+ "@ledgerhq/hw-app-icon": "^1.3.11-nightly.20251223024125",
222
+ "@ledgerhq/hw-app-kaspa": "^1.3.4-nightly.20251223024125",
223
+ "@ledgerhq/hw-app-multiversx": "^6.26.2-nightly.20251223024125",
224
+ "@ledgerhq/hw-app-near": "^6.31.11-nightly.20251223024125",
225
+ "@ledgerhq/hw-app-polkadot": "^6.34.11-nightly.20251223024125",
226
+ "@ledgerhq/hw-app-str": "^7.3.0-nightly.20251223024125",
227
227
  "@ledgerhq/hw-app-sui": "^1.4.0",
228
- "@ledgerhq/hw-app-tezos": "^6.31.11-nightly.20251219024040",
229
- "@ledgerhq/hw-app-trx": "^6.31.11-nightly.20251219024040",
230
- "@ledgerhq/hw-app-vet": "^0.8.2-nightly.20251219024040",
231
- "@ledgerhq/hw-bolos": "^6.32.11-nightly.20251219024040",
232
- "@ledgerhq/hw-transport-mocker": "^6.30.0-nightly.20251219024040",
233
- "@ledgerhq/hw-transport": "6.31.15-nightly.20251219024040",
234
- "@ledgerhq/hw-app-xrp": "^6.32.9-nightly.20251219024040",
235
- "@ledgerhq/client-ids": "^0.2.0-nightly.20251219024040",
236
- "@ledgerhq/ledger-cal-service": "^1.9.3-nightly.20251219024040",
237
- "@ledgerhq/live-config": "^3.3.0-nightly.20251219024040",
238
- "@ledgerhq/ledger-trust-service": "^0.4.5-nightly.20251219024040",
239
- "@ledgerhq/live-countervalues": "^0.10.3-nightly.20251219024040",
240
- "@ledgerhq/live-countervalues-react": "^0.7.5-nightly.20251219024040",
241
- "@ledgerhq/live-dmk-shared": "^0.16.0-nightly.20251219024040",
242
- "@ledgerhq/live-env": "^2.23.0-nightly.20251219024040",
243
- "@ledgerhq/live-hooks": "0.3.0-nightly.20251219024040",
244
- "@ledgerhq/live-network": "^2.1.4-nightly.20251219024040",
228
+ "@ledgerhq/hw-app-tezos": "^6.31.11-nightly.20251223024125",
229
+ "@ledgerhq/hw-app-trx": "^6.31.11-nightly.20251223024125",
230
+ "@ledgerhq/hw-app-vet": "^0.8.2-nightly.20251223024125",
231
+ "@ledgerhq/hw-app-xrp": "^6.32.9-nightly.20251223024125",
232
+ "@ledgerhq/hw-bolos": "^6.32.11-nightly.20251223024125",
233
+ "@ledgerhq/hw-transport": "6.31.15-nightly.20251223024125",
234
+ "@ledgerhq/hw-transport-mocker": "^6.30.0-nightly.20251223024125",
235
+ "@ledgerhq/client-ids": "^0.2.0-nightly.20251223024125",
236
+ "@ledgerhq/ledger-cal-service": "^1.9.3-nightly.20251223024125",
237
+ "@ledgerhq/ledger-trust-service": "^0.4.5-nightly.20251223024125",
238
+ "@ledgerhq/live-config": "^3.3.0-nightly.20251223024125",
239
+ "@ledgerhq/live-countervalues": "^0.10.3-nightly.20251223024125",
240
+ "@ledgerhq/live-countervalues-react": "^0.7.5-nightly.20251223024125",
241
+ "@ledgerhq/live-dmk-shared": "^0.16.0-nightly.20251223024125",
242
+ "@ledgerhq/live-env": "^2.23.0-nightly.20251223024125",
243
+ "@ledgerhq/live-hooks": "0.3.0-nightly.20251223024125",
244
+ "@ledgerhq/live-network": "^2.1.4-nightly.20251223024125",
245
245
  "@ledgerhq/live-promise": "^0.1.1",
246
- "@ledgerhq/live-signer-evm": "^0.11.0-nightly.20251219024040",
247
- "@ledgerhq/live-signer-canton": "^0.5.4-nightly.20251219024040",
248
- "@ledgerhq/live-signer-solana": "^0.8.0-nightly.20251219024040",
249
- "@ledgerhq/live-wallet": "^0.17.0-nightly.20251219024040",
246
+ "@ledgerhq/live-signer-canton": "^0.5.4-nightly.20251223024125",
247
+ "@ledgerhq/live-signer-evm": "^0.11.0-nightly.20251223024125",
248
+ "@ledgerhq/live-signer-solana": "^0.8.0-nightly.20251223024125",
249
+ "@ledgerhq/live-wallet": "^0.17.0-nightly.20251223024125",
250
250
  "@ledgerhq/logs": "^6.13.0",
251
- "@ledgerhq/speculos-transport": "^0.4.0-nightly.20251219024040",
252
- "@ledgerhq/wallet-api-acre-module": "^0.11.0-nightly.20251219024040",
253
- "@ledgerhq/wallet-api-exchange-module": "^0.20.0-nightly.20251219024040"
251
+ "@ledgerhq/speculos-transport": "^0.4.0-nightly.20251223024125",
252
+ "@ledgerhq/wallet-api-acre-module": "^0.11.0-nightly.20251223024125",
253
+ "@ledgerhq/wallet-api-exchange-module": "^0.20.0-nightly.20251223024125"
254
254
  },
255
255
  "devDependencies": {
256
256
  "@solana/web3.js": "1.95.4",
@@ -297,10 +297,10 @@
297
297
  "undici": "6.19.2",
298
298
  "uuid": "^8.3.2",
299
299
  "ws": "8.18.3",
300
- "@ledgerhq/device-react": "^0.3.5-nightly.20251219024040",
301
- "@ledgerhq/types-cryptoassets": "^7.31.0-nightly.20251219024040",
300
+ "@ledgerhq/device-react": "^0.3.5-nightly.20251223024125",
301
+ "@ledgerhq/types-cryptoassets": "^7.31.0-nightly.20251223024125",
302
302
  "@ledgerhq/types-devices": "^6.27.0",
303
- "@ledgerhq/types-live": "^6.91.0-nightly.20251219024040"
303
+ "@ledgerhq/types-live": "^6.91.0-nightly.20251223024125"
304
304
  },
305
305
  "scripts": {
306
306
  "build": "zx ./scripts/build-ts.mjs",
@@ -17,7 +17,13 @@ describe("RecentAddressesStore", () => {
17
17
  const addresses = store.getAddresses("ethereum");
18
18
  expect(addresses).toEqual([newAddress]);
19
19
  expect(onAddAddressCompleteMock).toHaveBeenCalledTimes(1);
20
- expect(onAddAddressCompleteMock).toHaveBeenCalledWith({ ethereum: [newAddress] });
20
+ expect(onAddAddressCompleteMock).toHaveBeenCalledWith({
21
+ ethereum: [
22
+ expect.objectContaining({
23
+ address: newAddress,
24
+ }),
25
+ ],
26
+ });
21
27
  });
22
28
 
23
29
  it("should add a second address and return addresses sorted by insertion", async () => {
@@ -26,14 +32,21 @@ describe("RecentAddressesStore", () => {
26
32
  let addresses = store.getAddresses("ethereum");
27
33
  expect(addresses).toEqual([newAddress]);
28
34
  expect(onAddAddressCompleteMock).toHaveBeenCalledTimes(1);
29
- expect(onAddAddressCompleteMock).toHaveBeenCalledWith({ ethereum: [newAddress] });
35
+ expect(onAddAddressCompleteMock).toHaveBeenCalledWith({
36
+ ethereum: [expect.objectContaining({ address: newAddress })],
37
+ });
30
38
 
31
39
  const newAddress2 = "0xB69B37A4Fb4A18b3258f974ff6e9f529AD2647b1";
32
40
  await store.addAddress("ethereum", newAddress2);
33
41
  addresses = store.getAddresses("ethereum");
34
42
  expect(addresses).toEqual([newAddress2, newAddress]);
35
43
  expect(onAddAddressCompleteMock).toHaveBeenCalledTimes(2);
36
- expect(onAddAddressCompleteMock).toHaveBeenCalledWith({ ethereum: [newAddress2, newAddress] });
44
+ expect(onAddAddressCompleteMock).toHaveBeenCalledWith({
45
+ ethereum: [
46
+ expect.objectContaining({ address: newAddress2 }),
47
+ expect.objectContaining({ address: newAddress }),
48
+ ],
49
+ });
37
50
  });
38
51
 
39
52
  it("should replace at first place when an address is already saved", async () => {
@@ -42,43 +55,61 @@ describe("RecentAddressesStore", () => {
42
55
  let addresses = store.getAddresses("ethereum");
43
56
  expect(addresses).toEqual([newAddress]);
44
57
  expect(onAddAddressCompleteMock).toHaveBeenCalledTimes(1);
45
- expect(onAddAddressCompleteMock).toHaveBeenCalledWith({ ethereum: [newAddress] });
58
+ expect(onAddAddressCompleteMock).toHaveBeenCalledWith({
59
+ ethereum: [expect.objectContaining({ address: newAddress })],
60
+ });
46
61
 
47
62
  const newAddress2 = "0xB69B37A4Fb4A18b3258f974ff6e9f529AD2647b1";
48
63
  await store.addAddress("ethereum", newAddress2);
49
64
  addresses = store.getAddresses("ethereum");
50
65
  expect(addresses).toEqual([newAddress2, newAddress]);
51
66
  expect(onAddAddressCompleteMock).toHaveBeenCalledTimes(2);
52
- expect(onAddAddressCompleteMock).toHaveBeenCalledWith({ ethereum: [newAddress2, newAddress] });
67
+ expect(onAddAddressCompleteMock).toHaveBeenCalledWith({
68
+ ethereum: [
69
+ expect.objectContaining({ address: newAddress2 }),
70
+ expect.objectContaining({ address: newAddress }),
71
+ ],
72
+ });
53
73
 
54
74
  await store.addAddress("ethereum", newAddress);
55
75
  addresses = store.getAddresses("ethereum");
56
76
  expect(addresses).toEqual([newAddress, newAddress2]);
57
77
  expect(onAddAddressCompleteMock).toHaveBeenCalledTimes(3);
58
- expect(onAddAddressCompleteMock).toHaveBeenCalledWith({ ethereum: [newAddress, newAddress2] });
78
+ expect(onAddAddressCompleteMock).toHaveBeenCalledWith({
79
+ ethereum: [
80
+ expect.objectContaining({ address: newAddress }),
81
+ expect.objectContaining({ address: newAddress2 }),
82
+ ],
83
+ });
59
84
  });
60
85
 
61
86
  it("should replace at first place and remove last element when addresses exceed count limit", async () => {
62
- let expectedAddresses: string[] = [];
87
+ const expectedAddresses: string[] = [];
88
+ const expectedObjects: any[] = [];
63
89
  for (let index = 0; index < RECENT_ADDRESSES_COUNT_LIMIT; index++) {
64
- await store.addAddress("ethereum", `0x66c4371aE8FFeD2ec1c2EBbbcCfb7E494181E1E3${index}`);
65
- expectedAddresses.unshift(`0x66c4371aE8FFeD2ec1c2EBbbcCfb7E494181E1E3${index}`);
90
+ const addr = `0x66c4371aE8FFeD2ec1c2EBbbcCfb7E494181E1E3${index}`;
91
+ await store.addAddress("ethereum", addr);
92
+ expectedAddresses.unshift(addr);
93
+ expectedObjects.unshift(expect.objectContaining({ address: addr }));
66
94
  }
67
95
 
68
96
  let addresses = store.getAddresses("ethereum");
69
97
  expect(addresses).toEqual(expectedAddresses);
70
98
  expect(onAddAddressCompleteMock).toHaveBeenCalledTimes(RECENT_ADDRESSES_COUNT_LIMIT);
71
- expect(onAddAddressCompleteMock).toHaveBeenCalledWith({ ethereum: expectedAddresses });
99
+ expect(onAddAddressCompleteMock).toHaveBeenCalledWith({ ethereum: expectedObjects });
72
100
 
73
101
  const newAddress2 = "0xB69B37A4Fb4A18b3258f974ff6e9f529AD2647b1";
74
102
  expectedAddresses.splice(expectedAddresses.length - 1, 1);
75
- expectedAddresses = [newAddress2, ...expectedAddresses];
103
+ expectedAddresses.unshift(newAddress2);
104
+
105
+ expectedObjects.splice(expectedObjects.length - 1, 1);
106
+ expectedObjects.unshift(expect.objectContaining({ address: newAddress2 }));
76
107
 
77
108
  await store.addAddress("ethereum", newAddress2);
78
109
  addresses = store.getAddresses("ethereum");
79
110
  expect(addresses).toEqual(expectedAddresses);
80
111
  expect(onAddAddressCompleteMock).toHaveBeenCalledTimes(RECENT_ADDRESSES_COUNT_LIMIT + 1);
81
- expect(onAddAddressCompleteMock).toHaveBeenCalledWith({ ethereum: expectedAddresses });
112
+ expect(onAddAddressCompleteMock).toHaveBeenCalledWith({ ethereum: expectedObjects });
82
113
  });
83
114
 
84
115
  it("should add an address of a different currency", async () => {
@@ -88,7 +119,9 @@ describe("RecentAddressesStore", () => {
88
119
  let addresses = store.getAddresses("ethereum");
89
120
  expect(addresses).toEqual([newAddress]);
90
121
  expect(onAddAddressCompleteMock).toHaveBeenCalledTimes(1);
91
- expect(onAddAddressCompleteMock).toHaveBeenCalledWith({ ethereum: [newAddress] });
122
+ expect(onAddAddressCompleteMock).toHaveBeenCalledWith({
123
+ ethereum: [expect.objectContaining({ address: newAddress })],
124
+ });
92
125
 
93
126
  const newAddress2 = "bc1pxlmrudqyq8qd8pfsc4mpmlaw56x6vtcr9m8nvp8kj3gckefc4kmqhkg4l7";
94
127
  await store.addAddress("bitcoin", newAddress2);
@@ -97,8 +130,75 @@ describe("RecentAddressesStore", () => {
97
130
  expect(addresses).toEqual([newAddress2]);
98
131
  expect(onAddAddressCompleteMock).toHaveBeenCalledTimes(2);
99
132
  expect(onAddAddressCompleteMock).toHaveBeenCalledWith({
100
- ethereum: [newAddress],
101
- bitcoin: [newAddress2],
133
+ ethereum: [expect.objectContaining({ address: newAddress })],
134
+ bitcoin: [expect.objectContaining({ address: newAddress2 })],
135
+ });
136
+ });
137
+
138
+ it("should update timestamp and move to top when re-adding an existing address", async () => {
139
+ const address1 = "0x66c4371aE8FFeD2ec1c2EBbbcCfb7E494181E1E3";
140
+ const address2 = "0xB69B37A4Fb4A18b3258f974ff6e9f529AD2647b1";
141
+ const address3 = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0";
142
+
143
+ const now = Date.now();
144
+ const oneYear = 365 * 24 * 60 * 60 * 1000;
145
+ const threeYearsAgo = now - 3 * oneYear;
146
+ const oneYearAgo = now - oneYear;
147
+
148
+ const dateNowSpy = jest.spyOn(Date, "now");
149
+
150
+ // Add address1 3 years ago
151
+ dateNowSpy.mockReturnValue(threeYearsAgo);
152
+ await store.addAddress("ethereum", address1);
153
+
154
+ // Add address2 1 year ago
155
+ dateNowSpy.mockReturnValue(oneYearAgo);
156
+ await store.addAddress("ethereum", address2);
157
+
158
+ // Add address3 now
159
+ dateNowSpy.mockReturnValue(now);
160
+ await store.addAddress("ethereum", address3);
161
+
162
+ let addresses = store.getAddresses("ethereum");
163
+ expect(addresses).toEqual([address3, address2, address1]);
164
+
165
+ // Re-add address1 (the oldest one) now
166
+ const reAddTime = now + 1000;
167
+ dateNowSpy.mockReturnValue(reAddTime);
168
+ await store.addAddress("ethereum", address1);
169
+
170
+ addresses = store.getAddresses("ethereum");
171
+ expect(addresses).toEqual([address1, address3, address2]);
172
+
173
+ expect(onAddAddressCompleteMock).toHaveBeenLastCalledWith({
174
+ ethereum: [
175
+ expect.objectContaining({ address: address1, lastUsed: reAddTime }),
176
+ expect.objectContaining({ address: address3, lastUsed: now }),
177
+ expect.objectContaining({ address: address2, lastUsed: oneYearAgo }),
178
+ ],
102
179
  });
103
180
  });
181
+
182
+ it("should migrate legacy string addresses to RecentAddress objects on setup", async () => {
183
+ const legacyAddress1 = "0x66c4371aE8FFeD2ec1c2EBbbcCfb7E494181E1E3";
184
+ const legacyAddress2 = "0xB69B37A4Fb4A18b3258f974ff6e9f529AD2647b1";
185
+ const modernAddress = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0";
186
+ const modernTimestamp = 1234567890;
187
+
188
+ const legacyCache: any = {
189
+ ethereum: [
190
+ legacyAddress1,
191
+ { address: modernAddress, lastUsed: modernTimestamp },
192
+ legacyAddress2,
193
+ ],
194
+ };
195
+
196
+ setupRecentAddressesStore(legacyCache, onAddAddressCompleteMock);
197
+ store = getRecentAddressesStore();
198
+
199
+ const addresses = store.getAddresses("ethereum");
200
+
201
+ // Order should be preserved: legacy1, modern, legacy2
202
+ expect(addresses).toEqual([legacyAddress1, modernAddress, legacyAddress2]);
203
+ });
104
204
  });
@@ -1,6 +1,8 @@
1
+ import type { RecentAddressesState, RecentAddress } from "@ledgerhq/types-live";
2
+
1
3
  export const RECENT_ADDRESSES_COUNT_LIMIT = 12;
2
4
 
3
- export type RecentAddressesCache = Record<string, string[]>;
5
+ export type RecentAddressesCache = RecentAddressesState;
4
6
 
5
7
  export interface RecentAddressesStore {
6
8
  addAddress(currency: string, address: string): void;
@@ -8,8 +10,6 @@ export interface RecentAddressesStore {
8
10
  getAddresses(currency: string): string[];
9
11
  }
10
12
 
11
- type CallbackMode = "triggerCallback" | "skipCallback";
12
-
13
13
  let recentAddressesStore: RecentAddressesStore | null = null;
14
14
 
15
15
  export function getRecentAddressesStore(): RecentAddressesStore {
@@ -30,26 +30,43 @@ export function setupRecentAddressesStore(
30
30
 
31
31
  class RecentAddressesStoreImpl implements RecentAddressesStore {
32
32
  private addressesByCurrency: RecentAddressesCache = {};
33
- private readonly onAddAddressComplete: (addressesByCurrency: Record<string, string[]>) => void;
33
+ private readonly onAddAddressComplete: (addressesByCurrency: RecentAddressesCache) => void;
34
34
 
35
35
  constructor(
36
36
  addressesByCurrency: RecentAddressesCache,
37
37
  onAddAddressComplete: (addressesByCurrency: RecentAddressesCache) => void,
38
38
  ) {
39
- this.addressesByCurrency = { ...addressesByCurrency };
39
+ this.addressesByCurrency = this.sanitizeCache(addressesByCurrency);
40
40
  this.onAddAddressComplete = onAddAddressComplete;
41
41
  }
42
42
 
43
+ private sanitizeCache(cache: RecentAddressesCache): RecentAddressesCache {
44
+ const sanitized: RecentAddressesCache = {};
45
+ for (const currency in cache) {
46
+ const entries = cache[currency] as (RecentAddress | string)[];
47
+ sanitized[currency] = entries.map(entry => {
48
+ if (typeof entry === "string") {
49
+ return { address: entry, lastUsed: Date.now() };
50
+ }
51
+ return entry;
52
+ });
53
+ }
54
+ return sanitized;
55
+ }
56
+
43
57
  addAddress(currency: string, address: string): void {
44
- this.addAddressToCache(currency, address, "triggerCallback");
58
+ this.addAddressToCache(currency, address, Date.now(), true);
45
59
  }
46
60
 
47
61
  syncAddresses(cache: RecentAddressesCache): void {
48
62
  const previousAddresses = { ...this.addressesByCurrency };
49
63
  this.addressesByCurrency = { ...cache };
50
64
  for (const currency in previousAddresses) {
51
- for (const address of previousAddresses[currency]) {
52
- this.addAddressToCache(currency, address, "skipCallback");
65
+ const entries = previousAddresses[currency] as (RecentAddress | string)[];
66
+ for (const entry of entries) {
67
+ const address = typeof entry === "string" ? entry : entry.address;
68
+ const timestamp = typeof entry === "string" ? undefined : entry.lastUsed;
69
+ this.addAddressToCache(currency, address, timestamp ?? Date.now(), false);
53
70
  }
54
71
  }
55
72
 
@@ -58,26 +75,33 @@ class RecentAddressesStoreImpl implements RecentAddressesStore {
58
75
 
59
76
  getAddresses(currency: string): string[] {
60
77
  const addresses = this.addressesByCurrency[currency];
61
- return addresses ?? [];
78
+ if (!addresses) return [];
79
+ return addresses.map(entry => entry.address);
62
80
  }
63
81
 
64
- private addAddressToCache(currency: string, address: string, callbackMode: CallbackMode): void {
82
+ private addAddressToCache(
83
+ currency: string,
84
+ address: string,
85
+ timestamp: number,
86
+ shouldTriggerCallback: boolean,
87
+ ): void {
65
88
  if (!this.addressesByCurrency[currency]) {
66
89
  this.addressesByCurrency[currency] = [];
67
90
  }
68
91
 
69
92
  const addresses = this.addressesByCurrency[currency];
70
- const addressIndex = addresses.indexOf(address);
93
+ const addressIndex = addresses.findIndex(entry => entry.address === address);
94
+
71
95
  if (addressIndex !== -1) {
72
96
  addresses.splice(addressIndex, 1);
73
97
  } else if (addresses.length >= RECENT_ADDRESSES_COUNT_LIMIT) {
74
98
  addresses.pop();
75
99
  }
76
100
 
77
- addresses.unshift(address);
101
+ addresses.unshift({ address, lastUsed: timestamp });
78
102
  this.addressesByCurrency[currency] = [...addresses];
79
103
 
80
- if (callbackMode === "triggerCallback") {
104
+ if (shouldTriggerCallback) {
81
105
  this.onAddAddressComplete(this.addressesByCurrency);
82
106
  }
83
107
  }
@@ -11,7 +11,9 @@ export class Provider {
11
11
  static readonly ONE_INCH = new Provider("oneinch", "1inch", false, false, true);
12
12
  static readonly VELORA = new Provider("velora", "Velora", false, false, true);
13
13
  static readonly MOONPAY = new Provider("moonpay", "MoonPay", true, false, true);
14
- static readonly THORCHAIN = new Provider("thorswap", "THORChain", false, true, false);
14
+ //TODO: THORCHAIN is actually kyc: false but due to the speculos crashing with native flow the bug needs to be fixed first before activating
15
+ //and adapting the flow accordingly
16
+ static readonly THORCHAIN = new Provider("thorswap", "THORChain", true, true, false);
15
17
  static readonly UNISWAP = new Provider("uniswap", "Uniswap", false, false, false);
16
18
  static readonly LIFI = new Provider("lifi", "LI.FI", false, true, false);
17
19
  static readonly CIC = new Provider("cic", "CIC", false, true, true);
@@ -248,12 +248,14 @@ const DEFAULT_SWAP_PROVIDERS: Record<string, ProviderConfig & Partial<Additional
248
248
  export const dexProvidersContractAddress: { [key: string]: string } = {
249
249
  "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad": "Uniswap",
250
250
  "0x111111125421ca6dc452d289314280a0f8842a65": "1inch",
251
+ "0x6a000f20005980200259b80c5102003040001068": "velora",
251
252
  };
252
253
 
253
254
  export const termsOfUse: { [key: string]: string } = {
254
255
  paraswap: "https://paraswap.io/tos",
255
256
  "1inch": "https://1inch.com/assets/Widget_1inch.com_Terms_of_Use.pdf",
256
257
  Uniswap: "https://uniswap.org/terms-of-service",
258
+ velora: "https://www.velora.xyz/terms/terms-of-use",
257
259
  };
258
260
 
259
261
  export const privacyPolicy: { [key: string]: string } = {
@@ -26,35 +26,39 @@ describe("getRemainingTime", () => {
26
26
  test("should format seconds only", () => {
27
27
  expect(getRemainingTime(30 * SECOND)).toBe("30s");
28
28
  expect(getRemainingTime(59 * SECOND)).toBe("59s");
29
- expect(getRemainingTime(1 * SECOND)).toBe("1s");
29
+ expect(getRemainingTime(1 * SECOND)).toBe("01s");
30
30
  });
31
31
 
32
32
  test("should format minutes and seconds", () => {
33
- expect(getRemainingTime(1 * MINUTE + 30 * SECOND)).toBe("1m 30s");
34
- expect(getRemainingTime(5 * MINUTE + 15 * SECOND)).toBe("5m 15s");
33
+ expect(getRemainingTime(1 * MINUTE + 30 * SECOND)).toBe("01m 30s");
34
+ expect(getRemainingTime(5 * MINUTE + 15 * SECOND)).toBe("05m 15s");
35
35
  expect(getRemainingTime(59 * MINUTE + 59 * SECOND)).toBe("59m 59s");
36
36
  });
37
37
 
38
38
  test("should format hours, minutes and seconds", () => {
39
- expect(getRemainingTime(1 * HOUR + 30 * MINUTE + 15 * SECOND)).toBe("1h 30m 15s");
40
- expect(getRemainingTime(2 * HOUR + 5 * MINUTE + 10 * SECOND)).toBe("2h 5m 10s");
39
+ expect(getRemainingTime(1 * HOUR + 30 * MINUTE + 15 * SECOND)).toBe("01h 30m 15s");
40
+ expect(getRemainingTime(2 * HOUR + 5 * MINUTE + 10 * SECOND)).toBe("02h 05m 10s");
41
41
  expect(getRemainingTime(23 * HOUR + 59 * MINUTE + 59 * SECOND)).toBe("23h 59m 59s");
42
42
  });
43
43
 
44
44
  test("should format days, hours, minutes and seconds", () => {
45
- expect(getRemainingTime(1 * DAY + 2 * HOUR + 30 * MINUTE + 15 * SECOND)).toBe("1d 2h 30m 15s");
46
- expect(getRemainingTime(5 * DAY + 10 * HOUR + 5 * MINUTE + 10 * SECOND)).toBe("5d 10h 5m 10s");
45
+ expect(getRemainingTime(1 * DAY + 2 * HOUR + 30 * MINUTE + 15 * SECOND)).toBe(
46
+ "01d 02h 30m 15s",
47
+ );
48
+ expect(getRemainingTime(5 * DAY + 10 * HOUR + 5 * MINUTE + 10 * SECOND)).toBe(
49
+ "05d 10h 05m 10s",
50
+ );
47
51
  expect(getRemainingTime(10 * DAY + 23 * HOUR + 59 * MINUTE + 59 * SECOND)).toBe(
48
52
  "10d 23h 59m 59s",
49
53
  );
50
54
  });
51
55
 
52
56
  test("should skip zero values", () => {
53
- expect(getRemainingTime(1 * DAY)).toBe("1d 0s");
54
- expect(getRemainingTime(1 * HOUR)).toBe("1h 0s");
55
- expect(getRemainingTime(1 * MINUTE)).toBe("1m 0s");
56
- expect(getRemainingTime(1 * DAY + 1 * MINUTE)).toBe("1d 1m 0s");
57
- expect(getRemainingTime(1 * DAY + 1 * HOUR)).toBe("1d 1h 0s");
57
+ expect(getRemainingTime(1 * DAY)).toBe("01d 00h 00m 00s");
58
+ expect(getRemainingTime(1 * HOUR)).toBe("01h 00m 00s");
59
+ expect(getRemainingTime(1 * MINUTE)).toBe("01m 00s");
60
+ expect(getRemainingTime(1 * DAY + 1 * MINUTE)).toBe("01d 00h 01m 00s");
61
+ expect(getRemainingTime(1 * DAY + 1 * HOUR)).toBe("01d 01h 00m 00s");
58
62
  });
59
63
  });
60
64
 
@@ -89,41 +93,41 @@ describe("useTimeRemaining", () => {
89
93
  test("should return formatted time remaining for valid proposal", () => {
90
94
  const expiresAt = Date.now() + 2 * HOUR + 30 * MINUTE + 15 * SECOND;
91
95
  const { result } = renderHook(() => useTimeRemaining(expiresAt * 1000));
92
- expect(result.current).toBe("2h 30m 15s");
96
+ expect(result.current).toBe("02h 30m 15s");
93
97
  });
94
98
 
95
99
  test("should update time remaining every second", () => {
96
100
  const expiresAt = Date.now() + 2 * MINUTE + 30 * SECOND;
97
101
  const { result } = renderHook(() => useTimeRemaining(expiresAt * 1000));
98
102
 
99
- expect(result.current).toBe("2m 30s");
103
+ expect(result.current).toBe("02m 30s");
100
104
 
101
105
  act(() => {
102
106
  jest.advanceTimersByTime(1 * SECOND);
103
107
  });
104
- expect(result.current).toBe("2m 29s");
108
+ expect(result.current).toBe("02m 29s");
105
109
 
106
110
  act(() => {
107
111
  jest.advanceTimersByTime(1 * SECOND);
108
112
  });
109
- expect(result.current).toBe("2m 28s");
113
+ expect(result.current).toBe("02m 28s");
110
114
 
111
115
  act(() => {
112
116
  jest.advanceTimersByTime(30 * SECOND);
113
117
  });
114
- expect(result.current).toBe("1m 58s");
118
+ expect(result.current).toBe("01m 58s");
115
119
  });
116
120
 
117
121
  test("should return empty string when time expires", () => {
118
122
  const expiresAt = Date.now() + 2 * SECOND;
119
123
  const { result } = renderHook(() => useTimeRemaining(expiresAt * 1000));
120
124
 
121
- expect(result.current).toBe("2s");
125
+ expect(result.current).toBe("02s");
122
126
 
123
127
  act(() => {
124
128
  jest.advanceTimersByTime(1 * SECOND);
125
129
  });
126
- expect(result.current).toBe("1s");
130
+ expect(result.current).toBe("01s");
127
131
 
128
132
  act(() => {
129
133
  jest.advanceTimersByTime(1 * SECOND);
@@ -135,7 +139,7 @@ describe("useTimeRemaining", () => {
135
139
  const expiresAt = Date.now() + 1 * HOUR;
136
140
  const { result, unmount } = renderHook(() => useTimeRemaining(expiresAt * 1000));
137
141
 
138
- expect(result.current).toBe("1h 0s");
142
+ expect(result.current).toBe("01h 00m 00s");
139
143
 
140
144
  unmount();
141
145