@ledgerhq/live-common 34.42.2-nightly.0 → 34.43.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 (80) hide show
  1. package/lib/e2e/index.d.ts +1 -0
  2. package/lib/e2e/index.d.ts.map +1 -1
  3. package/lib/e2e/speculos.d.ts.map +1 -1
  4. package/lib/e2e/speculos.js +45 -15
  5. package/lib/e2e/speculos.js.map +1 -1
  6. package/lib/e2e/speculosCI.d.ts.map +1 -1
  7. package/lib/e2e/speculosCI.js +22 -4
  8. package/lib/e2e/speculosCI.js.map +1 -1
  9. package/lib/env.react.d.ts +1 -1
  10. package/lib/env.react.d.ts.map +1 -1
  11. package/lib/families/solana/bridge/mock-data.d.ts.map +1 -1
  12. package/lib/families/solana/bridge/mock-data.js +46 -316
  13. package/lib/families/solana/bridge/mock-data.js.map +1 -1
  14. package/lib/families/solana/bridge/mock.js +2 -2
  15. package/lib/families/solana/bridge/mock.js.map +1 -1
  16. package/lib/modularDrawer/__mocks__/accounts.mock.d.ts +18 -0
  17. package/lib/modularDrawer/__mocks__/accounts.mock.d.ts.map +1 -0
  18. package/lib/modularDrawer/__mocks__/accounts.mock.js +48 -0
  19. package/lib/modularDrawer/__mocks__/accounts.mock.js.map +1 -0
  20. package/lib/utils/__tests__/composeHooks.test.d.ts +2 -0
  21. package/lib/utils/__tests__/composeHooks.test.d.ts.map +1 -0
  22. package/lib/utils/__tests__/composeHooks.test.js +35 -0
  23. package/lib/utils/__tests__/composeHooks.test.js.map +1 -0
  24. package/lib/utils/__tests__/getAccountTuplesForCurrency.test.d.ts +2 -0
  25. package/lib/utils/__tests__/getAccountTuplesForCurrency.test.d.ts.map +1 -0
  26. package/lib/utils/__tests__/getAccountTuplesForCurrency.test.js +104 -0
  27. package/lib/utils/__tests__/getAccountTuplesForCurrency.test.js.map +1 -0
  28. package/lib/utils/composeHooks.d.ts +11 -0
  29. package/lib/utils/composeHooks.d.ts.map +1 -0
  30. package/lib/utils/composeHooks.js +25 -0
  31. package/lib/utils/composeHooks.js.map +1 -0
  32. package/lib/utils/getAccountTuplesForCurrency.d.ts +8 -0
  33. package/lib/utils/getAccountTuplesForCurrency.d.ts.map +1 -0
  34. package/lib/utils/getAccountTuplesForCurrency.js +19 -0
  35. package/lib/utils/getAccountTuplesForCurrency.js.map +1 -0
  36. package/lib-es/e2e/index.d.ts +1 -0
  37. package/lib-es/e2e/index.d.ts.map +1 -1
  38. package/lib-es/e2e/speculos.d.ts.map +1 -1
  39. package/lib-es/e2e/speculos.js +45 -15
  40. package/lib-es/e2e/speculos.js.map +1 -1
  41. package/lib-es/e2e/speculosCI.d.ts.map +1 -1
  42. package/lib-es/e2e/speculosCI.js +22 -4
  43. package/lib-es/e2e/speculosCI.js.map +1 -1
  44. package/lib-es/env.react.d.ts +1 -1
  45. package/lib-es/env.react.d.ts.map +1 -1
  46. package/lib-es/families/solana/bridge/mock-data.d.ts.map +1 -1
  47. package/lib-es/families/solana/bridge/mock-data.js +46 -316
  48. package/lib-es/families/solana/bridge/mock-data.js.map +1 -1
  49. package/lib-es/families/solana/bridge/mock.js +3 -3
  50. package/lib-es/families/solana/bridge/mock.js.map +1 -1
  51. package/lib-es/modularDrawer/__mocks__/accounts.mock.d.ts +18 -0
  52. package/lib-es/modularDrawer/__mocks__/accounts.mock.d.ts.map +1 -0
  53. package/lib-es/modularDrawer/__mocks__/accounts.mock.js +42 -0
  54. package/lib-es/modularDrawer/__mocks__/accounts.mock.js.map +1 -0
  55. package/lib-es/utils/__tests__/composeHooks.test.d.ts +2 -0
  56. package/lib-es/utils/__tests__/composeHooks.test.d.ts.map +1 -0
  57. package/lib-es/utils/__tests__/composeHooks.test.js +33 -0
  58. package/lib-es/utils/__tests__/composeHooks.test.js.map +1 -0
  59. package/lib-es/utils/__tests__/getAccountTuplesForCurrency.test.d.ts +2 -0
  60. package/lib-es/utils/__tests__/getAccountTuplesForCurrency.test.d.ts.map +1 -0
  61. package/lib-es/utils/__tests__/getAccountTuplesForCurrency.test.js +102 -0
  62. package/lib-es/utils/__tests__/getAccountTuplesForCurrency.test.js.map +1 -0
  63. package/lib-es/utils/composeHooks.d.ts +11 -0
  64. package/lib-es/utils/composeHooks.d.ts.map +1 -0
  65. package/lib-es/utils/composeHooks.js +21 -0
  66. package/lib-es/utils/composeHooks.js.map +1 -0
  67. package/lib-es/utils/getAccountTuplesForCurrency.d.ts +8 -0
  68. package/lib-es/utils/getAccountTuplesForCurrency.d.ts.map +1 -0
  69. package/lib-es/utils/getAccountTuplesForCurrency.js +15 -0
  70. package/lib-es/utils/getAccountTuplesForCurrency.js.map +1 -0
  71. package/package.json +44 -44
  72. package/src/e2e/speculos.ts +77 -21
  73. package/src/e2e/speculosCI.ts +32 -10
  74. package/src/families/solana/bridge/mock-data.ts +52 -316
  75. package/src/families/solana/bridge/mock.ts +3 -3
  76. package/src/modularDrawer/__mocks__/accounts.mock.ts +43 -0
  77. package/src/utils/__tests__/composeHooks.test.ts +46 -0
  78. package/src/utils/__tests__/getAccountTuplesForCurrency.test.ts +138 -0
  79. package/src/utils/composeHooks.ts +26 -0
  80. package/src/utils/getAccountTuplesForCurrency.ts +34 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=composeHooks.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composeHooks.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/composeHooks.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,33 @@
1
+ import { composeHooks } from "../composeHooks";
2
+ describe("composeHooks", () => {
3
+ it("should compose hooks and merge their results", () => {
4
+ const hook1 = (items) => items.map(item => ({ id: item.id, extra: item.id * 2 }));
5
+ const hook2 = (items) => items.map(item => ({ ...item, final: item.extra + 1 }));
6
+ const composed = composeHooks(hook1, hook2);
7
+ const input = [{ id: 1 }, { id: 2 }];
8
+ const processedInput = input.map(item => ({ id: item.id, extra: item.id * 2 }));
9
+ const result = composed(processedInput);
10
+ expect(result).toEqual([
11
+ { id: 1, extra: 2, final: 3 },
12
+ { id: 2, extra: 4, final: 5 },
13
+ ]);
14
+ });
15
+ it("should handle hooks that return undefined", () => {
16
+ const hook1 = (items) => items.map(item => ({ id: item.id, extra: item.id * 2 }));
17
+ const hook2 = () => undefined;
18
+ const composed = composeHooks(hook1, hook2);
19
+ const input = [{ id: 1 }, { id: 2 }];
20
+ const result = composed(input);
21
+ expect(result).toEqual([
22
+ { id: 1, extra: 2 },
23
+ { id: 2, extra: 4 },
24
+ ]);
25
+ });
26
+ it("should return the original items if no hooks are provided", () => {
27
+ const composed = composeHooks();
28
+ const input = [{ id: 1 }, { id: 2 }];
29
+ const result = composed(input);
30
+ expect(result).toEqual(input);
31
+ });
32
+ });
33
+ //# sourceMappingURL=composeHooks.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composeHooks.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/composeHooks.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,KAAK,GAAG,CAAC,KAA4B,EAAE,EAAE,CAC7C,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,CAAC,KAA2C,EAAE,EAAE,CAC5D,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAE1D,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAE5C,MAAM,KAAK,GAA0B,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QAC5D,MAAM,cAAc,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAChF,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;QAExC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;YACrB,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;YAC7B,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;SAC9B,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,KAAK,GAAG,CAAC,KAA4B,EAAE,EAAE,CAC7C,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC;QAE9B,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAE5C,MAAM,KAAK,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QACrC,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAE/B,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;YACrB,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;YACnB,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;SACpB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;QAEhC,MAAM,KAAK,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QACrC,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAE/B,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=getAccountTuplesForCurrency.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getAccountTuplesForCurrency.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/getAccountTuplesForCurrency.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,102 @@
1
+ import { getCryptoCurrencyById, getTokenById } from "@ledgerhq/cryptoassets";
2
+ import { genAccount } from "@ledgerhq/live-common/mock/account";
3
+ import { getAccountTuplesForCurrency } from "../getAccountTuplesForCurrency";
4
+ function* accountGenerator(currency) {
5
+ let id = 0;
6
+ while (true) {
7
+ id += 1;
8
+ yield genAccount(`mocked-account-${id}`, { currency, operationsSize: 0 });
9
+ }
10
+ }
11
+ const getAccountCreator = (currencyId) => {
12
+ const generator = accountGenerator(getCryptoCurrencyById(currencyId));
13
+ return () => generator.next().value;
14
+ };
15
+ describe("getAccountTuplesForCurrency", () => {
16
+ const getEthAccount = getAccountCreator("ethereum");
17
+ const getBtcAccount = getAccountCreator("bitcoin");
18
+ const getPolkadotAccount = getAccountCreator("polkadot");
19
+ const getCosmosAccount = getAccountCreator("cosmos");
20
+ describe("CryptoCurrency", () => {
21
+ test("returns all accounts associated to the CryptoCurrency", () => {
22
+ const ethCurrency = getCryptoCurrencyById("ethereum");
23
+ const ethAccounts = [getEthAccount(), getEthAccount()];
24
+ const allAccounts = [
25
+ getCosmosAccount(),
26
+ ...ethAccounts,
27
+ getBtcAccount(),
28
+ getPolkadotAccount(),
29
+ ];
30
+ const results = getAccountTuplesForCurrency(ethCurrency, allAccounts);
31
+ expect(results).toHaveLength(2);
32
+ results.forEach((result, index) => {
33
+ expect(result.account).toEqual(ethAccounts[index]);
34
+ expect(result.subAccount).toBeNull();
35
+ });
36
+ });
37
+ test("returns an empty array if the CryptoCurrency passed has no associated account", () => {
38
+ const ethCurrency = getCryptoCurrencyById("ethereum");
39
+ const allAccounts = [getCosmosAccount(), getBtcAccount(), getPolkadotAccount()];
40
+ const results = getAccountTuplesForCurrency(ethCurrency, allAccounts);
41
+ expect(results).toHaveLength(0);
42
+ });
43
+ test("filters based on the accountId map", () => {
44
+ const ethCurrency = getCryptoCurrencyById("ethereum");
45
+ const ethAccounts = [getEthAccount(), getEthAccount(), getEthAccount(), getEthAccount()];
46
+ const results = getAccountTuplesForCurrency(ethCurrency, ethAccounts, new Map([[ethAccounts[0].id, true]]));
47
+ expect(results).toHaveLength(1);
48
+ });
49
+ });
50
+ describe("TokenCurrency", () => {
51
+ const aaveToken = getTokenById("ethereum/erc20/aave");
52
+ test("returns correct parent accounts including a new subAccount when a TokenCurrency is provided", () => {
53
+ const ethAccounts = [
54
+ { ...getEthAccount(), subAccounts: [] },
55
+ { ...getEthAccount(), subAccounts: [] },
56
+ ];
57
+ const allAccounts = [
58
+ getCosmosAccount(),
59
+ ...ethAccounts,
60
+ getBtcAccount(),
61
+ getPolkadotAccount(),
62
+ ];
63
+ const results = getAccountTuplesForCurrency(aaveToken, allAccounts);
64
+ expect(results).toHaveLength(ethAccounts.length);
65
+ results.forEach((result, index) => {
66
+ expect(result.account).toEqual(ethAccounts[index]);
67
+ expect(result.subAccount.token).toEqual(aaveToken);
68
+ });
69
+ });
70
+ test("returns correct parent accounts including already existing subAccounts when a TokenCurrency is provided", () => {
71
+ const ethAccounts = [{ ...getEthAccount(), subAccounts: [aaveToken] }];
72
+ const allAccounts = [
73
+ getCosmosAccount(),
74
+ ...ethAccounts,
75
+ getBtcAccount(),
76
+ getPolkadotAccount(),
77
+ ];
78
+ const results = getAccountTuplesForCurrency(aaveToken, allAccounts);
79
+ expect(results).toHaveLength(ethAccounts.length);
80
+ results.forEach((result, index) => {
81
+ expect(result.account).toEqual(ethAccounts[index]);
82
+ expect(result.subAccount.token).toEqual(aaveToken);
83
+ });
84
+ });
85
+ test("returns an empty array when a TokenCurrency is provided but the accounts list is empty", () => {
86
+ const allAccounts = [];
87
+ const results = getAccountTuplesForCurrency(aaveToken, allAccounts);
88
+ expect(results).toHaveLength(0);
89
+ });
90
+ test("does not filter based on the accountId map", () => {
91
+ const aaveAccounts = [
92
+ { ...getEthAccount(), subAccounts: [aaveToken] },
93
+ { ...getEthAccount(), subAccounts: [aaveToken] },
94
+ { ...getEthAccount(), subAccounts: [aaveToken] },
95
+ { ...getEthAccount(), subAccounts: [aaveToken] },
96
+ ];
97
+ const results = getAccountTuplesForCurrency(aaveToken, aaveAccounts, new Map([[aaveAccounts[0].id, true]]));
98
+ expect(results).toHaveLength(4);
99
+ });
100
+ });
101
+ });
102
+ //# sourceMappingURL=getAccountTuplesForCurrency.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getAccountTuplesForCurrency.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/getAccountTuplesForCurrency.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ *
3
+ * @param hooks - An array of functions that take an array of items of type T and return an array of items of type U or undefined.
4
+ * @template T - The type of the items in the input array.
5
+ * @template U - The type of the items in the output array.
6
+ * @description This function composes multiple hooks into a single hook that processes an array of items of type T and returns an array of items of type T & U.
7
+ * @returns A function that takes an array of items of type T and returns an array of items of type T & U.
8
+ *
9
+ */
10
+ export declare function composeHooks<T, U>(...hooks: Array<(items: T[]) => U[] | undefined>): (items: T[]) => (T & U)[];
11
+ //# sourceMappingURL=composeHooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composeHooks.d.ts","sourceRoot":"","sources":["../../src/utils/composeHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,wBAAgB,YAAY,CAAC,CAAC,EAAE,CAAC,EAC/B,GAAG,KAAK,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,GAAG,SAAS,CAAC,GAC/C,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAa3B"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ *
3
+ * @param hooks - An array of functions that take an array of items of type T and return an array of items of type U or undefined.
4
+ * @template T - The type of the items in the input array.
5
+ * @template U - The type of the items in the output array.
6
+ * @description This function composes multiple hooks into a single hook that processes an array of items of type T and returns an array of items of type T & U.
7
+ * @returns A function that takes an array of items of type T and returns an array of items of type T & U.
8
+ *
9
+ */
10
+ export function composeHooks(...hooks) {
11
+ return (items) => {
12
+ return hooks.reduce((acc, hook) => {
13
+ const result = hook?.(acc) ?? [];
14
+ return acc.map((item, i) => ({
15
+ ...item,
16
+ ...result[i],
17
+ }));
18
+ }, items);
19
+ };
20
+ }
21
+ //# sourceMappingURL=composeHooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composeHooks.js","sourceRoot":"","sources":["../../src/utils/composeHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,UAAU,YAAY,CAC1B,GAAG,KAA6C;IAEhD,OAAO,CAAC,KAAU,EAAa,EAAE;QAC/B,OAAO,KAAK,CAAC,MAAM,CACjB,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;YACZ,MAAM,MAAM,GAAG,IAAI,EAAE,CAAC,GAAU,CAAC,IAAI,EAAE,CAAC;YACxC,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC3B,GAAG,IAAI;gBACP,GAAG,MAAM,CAAC,CAAC,CAAC;aACb,CAAC,CAAC,CAAC;QACN,CAAC,EACD,KAAkB,CACnB,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Account, TokenAccount } from "@ledgerhq/types-live";
2
+ import { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets";
3
+ export type AccountTuple = {
4
+ account: Account;
5
+ subAccount: TokenAccount | null;
6
+ };
7
+ export declare function getAccountTuplesForCurrency(currency: CryptoCurrency | TokenCurrency, allAccounts: Account[], accountIds?: Map<string, boolean>): AccountTuple[];
8
+ //# sourceMappingURL=getAccountTuplesForCurrency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getAccountTuplesForCurrency.d.ts","sourceRoot":"","sources":["../../src/utils/getAccountTuplesForCurrency.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAG7E,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,YAAY,GAAG,IAAI,CAAC;CACjC,CAAC;AAEF,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,cAAc,GAAG,aAAa,EACxC,WAAW,EAAE,OAAO,EAAE,EACtB,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,YAAY,EAAE,CAoBhB"}
@@ -0,0 +1,15 @@
1
+ import { makeEmptyTokenAccount } from "@ledgerhq/coin-framework/account/helpers";
2
+ export function getAccountTuplesForCurrency(currency, allAccounts, accountIds) {
3
+ const isToken = currency.type === "TokenCurrency";
4
+ const targetCurrencyId = isToken ? currency.parentCurrency.id : currency.id;
5
+ return allAccounts
6
+ .filter(account => account.currency.id === targetCurrencyId &&
7
+ (isToken ? true : accountIds ? accountIds.has(account.id) : true))
8
+ .map(account => {
9
+ const subAccount = isToken
10
+ ? account.subAccounts?.find((subAcc) => subAcc.type === "TokenAccount" && subAcc.token.id === currency.id) || makeEmptyTokenAccount(account, currency)
11
+ : null;
12
+ return { account, subAccount };
13
+ });
14
+ }
15
+ //# sourceMappingURL=getAccountTuplesForCurrency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getAccountTuplesForCurrency.js","sourceRoot":"","sources":["../../src/utils/getAccountTuplesForCurrency.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AAOjF,MAAM,UAAU,2BAA2B,CACzC,QAAwC,EACxC,WAAsB,EACtB,UAAiC;IAEjC,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,KAAK,eAAe,CAAC;IAClD,MAAM,gBAAgB,GAAG,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;IAE5E,OAAO,WAAW;SACf,MAAM,CACL,OAAO,CAAC,EAAE,CACR,OAAO,CAAC,QAAQ,CAAC,EAAE,KAAK,gBAAgB;QACxC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CACpE;SACA,GAAG,CAAC,OAAO,CAAC,EAAE;QACb,MAAM,UAAU,GAAG,OAAO;YACxB,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CACvB,CAAC,MAAM,EAA0B,EAAE,CACjC,MAAM,CAAC,IAAI,KAAK,cAAc,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,EAAE,CACpE,IAAI,qBAAqB,CAAC,OAAO,EAAE,QAAQ,CAAC;YAC/C,CAAC,CAAC,IAAI,CAAC;QAET,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;IACjC,CAAC,CAAC,CAAC;AACP,CAAC"}
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.42.2-nightly.0",
4
+ "version": "34.43.0-nightly.1",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/LedgerHQ/ledger-live.git"
@@ -162,43 +162,42 @@
162
162
  "xstate": "^5.19.2",
163
163
  "yargs": "^17.0.0",
164
164
  "zod": "^3.22.4",
165
- "@ledgerhq/coin-algorand": "^0.9.15-nightly.0",
166
- "@ledgerhq/coin-aptos": "^2.6.1-nightly.0",
167
- "@ledgerhq/coin-bitcoin": "^0.18.1-nightly.0",
168
- "@ledgerhq/coin-cardano": "^0.10.1-nightly.0",
169
- "@ledgerhq/coin-casper": "^2.0.6-nightly.0",
170
- "@ledgerhq/coin-celo": "^1.1.13-nightly.0",
171
- "@ledgerhq/coin-cosmos": "^0.16.4-nightly.0",
172
- "@ledgerhq/coin-evm": "^2.24.1-nightly.0",
173
- "@ledgerhq/coin-filecoin": "^1.9.15-nightly.0",
174
- "@ledgerhq/coin-framework": "^5.7.1-nightly.0",
175
- "@ledgerhq/coin-hedera": "^1.9.1-nightly.0",
176
- "@ledgerhq/coin-icon": "^0.11.2-nightly.0",
177
- "@ledgerhq/coin-internet_computer": "^1.7.15-nightly.0",
178
- "@ledgerhq/coin-mina": "^1.1.14-nightly.0",
179
- "@ledgerhq/coin-multiversx": "^0.4.15-nightly.0",
180
- "@ledgerhq/coin-near": "^0.11.15-nightly.0",
181
- "@ledgerhq/coin-polkadot": "^6.4.1-nightly.0",
182
- "@ledgerhq/coin-solana": "^0.28.1-nightly.0",
183
- "@ledgerhq/coin-stacks": "^0.8.15-nightly.0",
184
- "@ledgerhq/coin-stellar": "^5.3.3-nightly.0",
185
- "@ledgerhq/coin-sui": "^0.7.1-nightly.0",
186
- "@ledgerhq/coin-tezos": "^5.6.2-nightly.0",
187
- "@ledgerhq/coin-ton": "^0.13.4-nightly.0",
188
- "@ledgerhq/coin-tron": "^4.4.3-nightly.0",
189
- "@ledgerhq/coin-vechain": "^2.7.15-nightly.0",
190
- "@ledgerhq/coin-xrp": "^6.4.2-nightly.0",
165
+ "@ledgerhq/coin-algorand": "^0.9.15-nightly.1",
166
+ "@ledgerhq/coin-aptos": "^2.6.1-nightly.1",
167
+ "@ledgerhq/coin-bitcoin": "^0.18.1-nightly.1",
168
+ "@ledgerhq/coin-cardano": "^0.10.1-nightly.1",
169
+ "@ledgerhq/coin-celo": "^1.1.13-nightly.1",
170
+ "@ledgerhq/coin-cosmos": "^0.16.4-nightly.1",
171
+ "@ledgerhq/coin-evm": "^2.24.1-nightly.1",
172
+ "@ledgerhq/coin-filecoin": "^1.9.15-nightly.1",
173
+ "@ledgerhq/coin-framework": "^5.7.1-nightly.1",
174
+ "@ledgerhq/coin-hedera": "^1.9.1-nightly.1",
175
+ "@ledgerhq/coin-icon": "^0.11.2-nightly.1",
176
+ "@ledgerhq/coin-internet_computer": "^1.7.15-nightly.1",
177
+ "@ledgerhq/coin-mina": "^1.1.14-nightly.1",
178
+ "@ledgerhq/coin-multiversx": "^0.4.15-nightly.1",
179
+ "@ledgerhq/coin-near": "^0.11.15-nightly.1",
180
+ "@ledgerhq/coin-polkadot": "^6.4.1-nightly.1",
181
+ "@ledgerhq/coin-solana": "^0.29.0-nightly.1",
182
+ "@ledgerhq/coin-stacks": "^0.8.15-nightly.1",
183
+ "@ledgerhq/coin-stellar": "^5.3.3-nightly.1",
184
+ "@ledgerhq/coin-sui": "^0.7.1-nightly.1",
185
+ "@ledgerhq/coin-tezos": "^5.6.2-nightly.1",
186
+ "@ledgerhq/coin-ton": "^0.13.4-nightly.1",
187
+ "@ledgerhq/coin-tron": "^4.4.3-nightly.1",
188
+ "@ledgerhq/coin-vechain": "^2.7.15-nightly.1",
189
+ "@ledgerhq/coin-xrp": "^6.4.2-nightly.1",
191
190
  "@ledgerhq/crypto-icons-ui": "^1.16.0",
192
- "@ledgerhq/cryptoassets": "^13.22.0",
193
- "@ledgerhq/device-core": "^0.5.4-nightly.0",
191
+ "@ledgerhq/cryptoassets": "^13.22.1-nightly.0",
192
+ "@ledgerhq/device-core": "^0.5.4-nightly.1",
194
193
  "@ledgerhq/devices": "8.4.8",
195
194
  "@ledgerhq/errors": "^6.23.0",
196
195
  "@ledgerhq/hw-app-algorand": "^6.31.4",
197
196
  "@ledgerhq/hw-app-aptos": "^6.34.4",
198
197
  "@ledgerhq/hw-app-btc": "^10.10.0",
199
- "@ledgerhq/hw-app-celo": "^6.33.11-nightly.0",
198
+ "@ledgerhq/hw-app-celo": "^6.33.11-nightly.1",
200
199
  "@ledgerhq/hw-app-cosmos": "^6.32.4",
201
- "@ledgerhq/hw-app-eth": "^6.45.13-nightly.0",
200
+ "@ledgerhq/hw-app-eth": "^6.45.13-nightly.1",
202
201
  "@ledgerhq/hw-app-exchange": "^0.10.4",
203
202
  "@ledgerhq/hw-app-hedera": "^1.2.4",
204
203
  "@ledgerhq/hw-app-icon": "^1.3.4",
@@ -209,27 +208,28 @@
209
208
  "@ledgerhq/hw-app-sui": "^1.2.0",
210
209
  "@ledgerhq/hw-app-tezos": "^6.31.4",
211
210
  "@ledgerhq/hw-app-trx": "^6.31.4",
212
- "@ledgerhq/hw-app-vet": "^0.5.10",
211
+ "@ledgerhq/hw-app-vet": "^0.5.11-nightly.0",
213
212
  "@ledgerhq/hw-app-xrp": "^6.32.2",
214
213
  "@ledgerhq/hw-transport": "^6.31.8",
215
214
  "@ledgerhq/hw-transport-mocker": "^6.29.8",
216
- "@ledgerhq/ledger-cal-service": "^1.2.1",
215
+ "@ledgerhq/ledger-cal-service": "^1.2.2-nightly.0",
217
216
  "@ledgerhq/live-config": "^3.1.0",
218
- "@ledgerhq/live-countervalues": "^0.5.15-nightly.0",
219
- "@ledgerhq/live-countervalues-react": "^0.2.44-nightly.0",
217
+ "@ledgerhq/live-countervalues": "^0.5.15-nightly.1",
218
+ "@ledgerhq/live-countervalues-react": "^0.2.44-nightly.1",
220
219
  "@ledgerhq/live-dmk-shared": "^0.11.1",
221
- "@ledgerhq/live-env": "^2.12.0",
220
+ "@ledgerhq/live-env": "^2.13.0-nightly.0",
222
221
  "@ledgerhq/live-hooks": "0.1.0",
223
- "@ledgerhq/live-network": "^2.0.13",
224
- "@ledgerhq/live-nft": "^0.8.15-nightly.0",
222
+ "@ledgerhq/live-network": "^2.0.14-nightly.0",
223
+ "@ledgerhq/live-nft": "^0.8.15-nightly.1",
225
224
  "@ledgerhq/live-promise": "^0.1.1",
226
- "@ledgerhq/live-signer-evm": "^0.6.2-nightly.0",
227
- "@ledgerhq/live-signer-solana": "^0.5.0-nightly.0",
228
- "@ledgerhq/live-wallet": "^0.11.6-nightly.0",
225
+ "@ledgerhq/live-signer-evm": "^0.6.2-nightly.1",
226
+ "@ledgerhq/live-signer-solana": "^0.5.0-nightly.1",
227
+ "@ledgerhq/live-wallet": "^0.11.6-nightly.1",
229
228
  "@ledgerhq/logs": "^6.13.0",
230
- "@ledgerhq/speculos-transport": "^0.2.6",
229
+ "@ledgerhq/speculos-transport": "^0.2.7-nightly.0",
231
230
  "@ledgerhq/wallet-api-acre-module": "^0.5.0",
232
- "@ledgerhq/wallet-api-exchange-module": "^0.14.0"
231
+ "@ledgerhq/wallet-api-exchange-module": "^0.14.0",
232
+ "@ledgerhq/coin-casper": "^2.0.6-nightly.1"
233
233
  },
234
234
  "devDependencies": {
235
235
  "@solana/web3.js": "1.95.4",
@@ -277,7 +277,7 @@
277
277
  "undici": "6.19.2",
278
278
  "uuid": "^8.3.2",
279
279
  "ws": "7",
280
- "@ledgerhq/device-react": "^0.2.39-nightly.0",
280
+ "@ledgerhq/device-react": "^0.2.39-nightly.1",
281
281
  "@ledgerhq/types-cryptoassets": "^7.24.0-nightly.0",
282
282
  "@ledgerhq/types-devices": "^6.25.3",
283
283
  "@ledgerhq/types-live": "^6.78.1-nightly.0"
@@ -11,7 +11,7 @@ import { createSpeculosDeviceCI, releaseSpeculosDeviceCI } from "./speculosCI";
11
11
  import type { AppCandidate } from "@ledgerhq/coin-framework/bot/types";
12
12
  import { DeviceModelId } from "@ledgerhq/devices";
13
13
  import { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
14
- import axios from "axios";
14
+ import axios, { AxiosError, AxiosResponse } from "axios";
15
15
  import { getEnv } from "@ledgerhq/live-env";
16
16
  import { getCryptoCurrencyById } from "../currencies";
17
17
  import { DeviceLabels } from "../e2e/enum/DeviceLabels";
@@ -415,13 +415,59 @@ interface ResponseData {
415
415
  events: Event[];
416
416
  }
417
417
 
418
+ function getSpeculosAddress(): string {
419
+ const speculosAddress = process.env.SPECULOS_ADDRESS;
420
+ return speculosAddress || "http://127.0.0.1";
421
+ }
422
+
423
+ async function retryAxiosRequest<T>(
424
+ requestFn: () => Promise<AxiosResponse<T>>,
425
+ maxRetries: number = 5,
426
+ baseDelay: number = 1000,
427
+ retryableStatusCodes: number[] = [500, 502, 503, 504],
428
+ ): Promise<AxiosResponse<T>> {
429
+ let lastError: AxiosError | Error;
430
+
431
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
432
+ try {
433
+ return await requestFn();
434
+ } catch (error) {
435
+ lastError = error as AxiosError | Error;
436
+
437
+ const isRetryable =
438
+ axios.isAxiosError(error) &&
439
+ error.response &&
440
+ retryableStatusCodes.includes(error.response.status);
441
+
442
+ const isNetworkError = axios.isAxiosError(error) && !error.response;
443
+
444
+ if ((isRetryable || isNetworkError) && attempt < maxRetries) {
445
+ const delay = baseDelay * (attempt + 1);
446
+ console.warn(
447
+ `Axios request failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${delay}ms...`,
448
+ {
449
+ status: axios.isAxiosError(error) ? error.response?.status : "network error",
450
+ message: error.message,
451
+ },
452
+ );
453
+ await new Promise(resolve => setTimeout(resolve, delay));
454
+ continue;
455
+ }
456
+
457
+ throw lastError;
458
+ }
459
+ }
460
+
461
+ throw lastError!;
462
+ }
463
+
418
464
  export async function waitFor(text: string, maxAttempts = 15): Promise<string[]> {
419
465
  const port = getEnv("SPECULOS_API_PORT");
420
- const address = process.env.SPECULOS_ADDRESS || "http://127.0.0.1";
466
+ const address = getSpeculosAddress();
421
467
  const url = `${address}:${port}/events?stream=false&currentscreenonly=true`;
422
468
 
423
469
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
424
- const { data } = await axios.get<ResponseData>(url);
470
+ const { data } = await retryAxiosRequest(() => axios.get<ResponseData>(url));
425
471
  const texts = data.events.map(event => event.text);
426
472
 
427
473
  if (texts?.some(t => t?.toLowerCase().includes(text.toLowerCase()))) {
@@ -436,10 +482,12 @@ export async function waitFor(text: string, maxAttempts = 15): Promise<string[]>
436
482
 
437
483
  export async function pressBoth() {
438
484
  const speculosApiPort = getEnv("SPECULOS_API_PORT");
439
- const speculosAddress = process.env.SPECULOS_ADDRESS || "http://127.0.0.1";
440
- await axios.post(`${speculosAddress}:${speculosApiPort}/button/both`, {
441
- action: "press-and-release",
442
- });
485
+ const speculosAddress = getSpeculosAddress();
486
+ await retryAxiosRequest(() =>
487
+ axios.post(`${speculosAddress}:${speculosApiPort}/button/both`, {
488
+ action: "press-and-release",
489
+ }),
490
+ );
443
491
  }
444
492
 
445
493
  export async function pressUntilTextFound(
@@ -465,27 +513,33 @@ export async function pressUntilTextFound(
465
513
  }
466
514
 
467
515
  async function fetchCurrentScreenTexts(speculosApiPort: number): Promise<string> {
468
- const speculosAddress = process.env.SPECULOS_ADDRESS || "http://127.0.0.1";
469
- const response = await axios.get<ResponseData>(
470
- `${speculosAddress}:${speculosApiPort}/events?stream=false&currentscreenonly=true`,
516
+ const speculosAddress = getSpeculosAddress();
517
+ const response = await retryAxiosRequest(() =>
518
+ axios.get<ResponseData>(
519
+ `${speculosAddress}:${speculosApiPort}/events?stream=false&currentscreenonly=true`,
520
+ ),
471
521
  );
472
522
  return response.data.events.map(event => event.text).join("");
473
523
  }
474
524
 
475
525
  async function fetchAllEvents(speculosApiPort: number): Promise<string[]> {
476
- const speculosAddress = process.env.SPECULOS_ADDRESS || "http://127.0.0.1";
477
- const response = await axios.get<ResponseData>(
478
- `${speculosAddress}:${speculosApiPort}/events?stream=false&currentscreenonly=false`,
526
+ const speculosAddress = getSpeculosAddress();
527
+ const response = await retryAxiosRequest(() =>
528
+ axios.get<ResponseData>(
529
+ `${speculosAddress}:${speculosApiPort}/events?stream=false&currentscreenonly=false`,
530
+ ),
479
531
  );
480
532
  return response.data.events.map(event => event.text);
481
533
  }
482
534
 
483
535
  export async function pressRightButton(): Promise<void> {
484
536
  const speculosApiPort = getEnv("SPECULOS_API_PORT");
485
- const speculosAddress = process.env.SPECULOS_ADDRESS || "http://127.0.0.1";
486
- await axios.post(`${speculosAddress}:${speculosApiPort}/button/right`, {
487
- action: "press-and-release",
488
- });
537
+ const speculosAddress = getSpeculosAddress();
538
+ await retryAxiosRequest(() =>
539
+ axios.post(`${speculosAddress}:${speculosApiPort}/button/right`, {
540
+ action: "press-and-release",
541
+ }),
542
+ );
489
543
  }
490
544
 
491
545
  export function containsSubstringInEvent(targetString: string, events: string[]): boolean {
@@ -503,12 +557,14 @@ export function containsSubstringInEvent(targetString: string, events: string[])
503
557
  }
504
558
 
505
559
  export async function takeScreenshot(port?: number): Promise<Buffer | undefined> {
506
- const speculosAddress = process.env.SPECULOS_ADDRESS || "http://127.0.0.1";
560
+ const speculosAddress = getSpeculosAddress();
507
561
  const speculosApiPort = port ?? getEnv("SPECULOS_API_PORT");
508
562
  try {
509
- const response = await axios.get(`${speculosAddress}:${speculosApiPort}/screenshot`, {
510
- responseType: "arraybuffer",
511
- });
563
+ const response = await retryAxiosRequest(() =>
564
+ axios.get(`${speculosAddress}:${speculosApiPort}/screenshot`, {
565
+ responseType: "arraybuffer",
566
+ }),
567
+ );
512
568
  return response.data;
513
569
  } catch (error) {
514
570
  console.error("Error downloading speculos screenshot:", error);
@@ -61,22 +61,44 @@ async function githubApiRequest<T = unknown>({
61
61
  function waitForSpeculosReady(url: string, { interval = 2000, timeout = 300_000 } = {}) {
62
62
  return new Promise((resolve, reject) => {
63
63
  const startTime = Date.now();
64
+ let currentRequest: ReturnType<typeof https.get> | null = null;
65
+
66
+ function cleanup() {
67
+ if (currentRequest) {
68
+ currentRequest.destroy();
69
+ currentRequest = null;
70
+ }
71
+ }
64
72
 
65
73
  function check() {
66
- https
67
- .get(url, res => {
68
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 400) {
69
- process.env.SPECULOS_ADDRESS = url;
70
- resolve(true);
71
- } else {
72
- retry();
73
- }
74
- })
75
- .on("error", retry);
74
+ cleanup();
75
+
76
+ currentRequest = https.get(url, { timeout: 10000 }, res => {
77
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 400) {
78
+ process.env.SPECULOS_ADDRESS = url;
79
+ cleanup();
80
+ console.warn(`Speculos is ready at ${url}`);
81
+ resolve(true);
82
+ } else {
83
+ console.warn(`Speculos not ready yet, status: ${res.statusCode}`);
84
+ retry();
85
+ }
86
+ });
87
+
88
+ currentRequest.on("error", error => {
89
+ console.error(`Request error: ${error.message}`);
90
+ retry();
91
+ });
92
+
93
+ currentRequest.on("timeout", () => {
94
+ console.error("Request timeout");
95
+ retry();
96
+ });
76
97
  }
77
98
 
78
99
  function retry() {
79
100
  if (Date.now() - startTime >= timeout) {
101
+ cleanup();
80
102
  reject(new Error(`Timeout: ${url} did not become available within ${timeout}ms`));
81
103
  } else {
82
104
  setTimeout(check, interval);