@ledgerhq/live-common 34.54.0-nightly.20251127130943 → 34.54.0-nightly.20251128145758

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 (120) hide show
  1. package/lib/bridge/generic-alpaca/accountBridge.d.ts +1 -1
  2. package/lib/bridge/generic-alpaca/accountBridge.d.ts.map +1 -1
  3. package/lib/bridge/generic-alpaca/accountBridge.js +2 -1
  4. package/lib/bridge/generic-alpaca/accountBridge.js.map +1 -1
  5. package/lib/bridge/generic-alpaca/currencyBridge.d.ts.map +1 -1
  6. package/lib/bridge/generic-alpaca/currencyBridge.js +2 -0
  7. package/lib/bridge/generic-alpaca/currencyBridge.js.map +1 -1
  8. package/lib/bridge/generic-alpaca/postSync.d.ts +16 -0
  9. package/lib/bridge/generic-alpaca/postSync.d.ts.map +1 -0
  10. package/lib/bridge/generic-alpaca/postSync.js +44 -0
  11. package/lib/bridge/generic-alpaca/postSync.js.map +1 -0
  12. package/lib/bridge/generic-alpaca/utils.d.ts.map +1 -1
  13. package/lib/bridge/generic-alpaca/utils.js +1 -0
  14. package/lib/bridge/generic-alpaca/utils.js.map +1 -1
  15. package/lib/e2e/data/deviceLabelsData.d.ts.map +1 -1
  16. package/lib/e2e/data/deviceLabelsData.js +1 -0
  17. package/lib/e2e/data/deviceLabelsData.js.map +1 -1
  18. package/lib/e2e/data/regexes.d.ts +2 -0
  19. package/lib/e2e/data/regexes.d.ts.map +1 -0
  20. package/lib/e2e/data/regexes.js +5 -0
  21. package/lib/e2e/data/regexes.js.map +1 -0
  22. package/lib/e2e/enum/Device.d.ts +1 -0
  23. package/lib/e2e/enum/Device.d.ts.map +1 -1
  24. package/lib/e2e/enum/Device.js +1 -0
  25. package/lib/e2e/enum/Device.js.map +1 -1
  26. package/lib/e2e/families/cardano.d.ts.map +1 -1
  27. package/lib/e2e/families/cardano.js +29 -14
  28. package/lib/e2e/families/cardano.js.map +1 -1
  29. package/lib/e2e/index.d.ts +4 -0
  30. package/lib/e2e/index.d.ts.map +1 -1
  31. package/lib/e2e/index.js +21 -1
  32. package/lib/e2e/index.js.map +1 -1
  33. package/lib/e2e/speculos.d.ts.map +1 -1
  34. package/lib/e2e/speculos.js +31 -4
  35. package/lib/e2e/speculos.js.map +1 -1
  36. package/lib/e2e/speculosAppVersion.d.ts.map +1 -1
  37. package/lib/e2e/speculosAppVersion.js +6 -2
  38. package/lib/e2e/speculosAppVersion.js.map +1 -1
  39. package/lib/e2e/swap.d.ts.map +1 -1
  40. package/lib/e2e/swap.js +7 -6
  41. package/lib/e2e/swap.js.map +1 -1
  42. package/lib/featureFlags/defaultFeatures.d.ts.map +1 -1
  43. package/lib/featureFlags/defaultFeatures.js +13 -0
  44. package/lib/featureFlags/defaultFeatures.js.map +1 -1
  45. package/lib/featureFlags/stakePrograms/index.js +4 -4
  46. package/lib/featureFlags/stakePrograms/index.js.map +1 -1
  47. package/lib/featureFlags/useFeature.d.ts +1 -1
  48. package/lib/featureFlags/useFeature.d.ts.map +1 -1
  49. package/lib/load/speculos.d.ts.map +1 -1
  50. package/lib/load/speculos.js +1 -0
  51. package/lib/load/speculos.js.map +1 -1
  52. package/lib-es/bridge/generic-alpaca/accountBridge.d.ts +1 -1
  53. package/lib-es/bridge/generic-alpaca/accountBridge.d.ts.map +1 -1
  54. package/lib-es/bridge/generic-alpaca/accountBridge.js +2 -1
  55. package/lib-es/bridge/generic-alpaca/accountBridge.js.map +1 -1
  56. package/lib-es/bridge/generic-alpaca/currencyBridge.d.ts.map +1 -1
  57. package/lib-es/bridge/generic-alpaca/currencyBridge.js +2 -0
  58. package/lib-es/bridge/generic-alpaca/currencyBridge.js.map +1 -1
  59. package/lib-es/bridge/generic-alpaca/postSync.d.ts +16 -0
  60. package/lib-es/bridge/generic-alpaca/postSync.d.ts.map +1 -0
  61. package/lib-es/bridge/generic-alpaca/postSync.js +37 -0
  62. package/lib-es/bridge/generic-alpaca/postSync.js.map +1 -0
  63. package/lib-es/bridge/generic-alpaca/utils.d.ts.map +1 -1
  64. package/lib-es/bridge/generic-alpaca/utils.js +1 -0
  65. package/lib-es/bridge/generic-alpaca/utils.js.map +1 -1
  66. package/lib-es/e2e/data/deviceLabelsData.d.ts.map +1 -1
  67. package/lib-es/e2e/data/deviceLabelsData.js +1 -0
  68. package/lib-es/e2e/data/deviceLabelsData.js.map +1 -1
  69. package/lib-es/e2e/data/regexes.d.ts +2 -0
  70. package/lib-es/e2e/data/regexes.d.ts.map +1 -0
  71. package/lib-es/e2e/data/regexes.js +2 -0
  72. package/lib-es/e2e/data/regexes.js.map +1 -0
  73. package/lib-es/e2e/enum/Device.d.ts +1 -0
  74. package/lib-es/e2e/enum/Device.d.ts.map +1 -1
  75. package/lib-es/e2e/enum/Device.js +1 -0
  76. package/lib-es/e2e/enum/Device.js.map +1 -1
  77. package/lib-es/e2e/families/cardano.d.ts.map +1 -1
  78. package/lib-es/e2e/families/cardano.js +29 -14
  79. package/lib-es/e2e/families/cardano.js.map +1 -1
  80. package/lib-es/e2e/index.d.ts +4 -0
  81. package/lib-es/e2e/index.d.ts.map +1 -1
  82. package/lib-es/e2e/index.js +16 -0
  83. package/lib-es/e2e/index.js.map +1 -1
  84. package/lib-es/e2e/speculos.d.ts.map +1 -1
  85. package/lib-es/e2e/speculos.js +31 -4
  86. package/lib-es/e2e/speculos.js.map +1 -1
  87. package/lib-es/e2e/speculosAppVersion.d.ts.map +1 -1
  88. package/lib-es/e2e/speculosAppVersion.js +6 -2
  89. package/lib-es/e2e/speculosAppVersion.js.map +1 -1
  90. package/lib-es/e2e/swap.d.ts.map +1 -1
  91. package/lib-es/e2e/swap.js +7 -6
  92. package/lib-es/e2e/swap.js.map +1 -1
  93. package/lib-es/featureFlags/defaultFeatures.d.ts.map +1 -1
  94. package/lib-es/featureFlags/defaultFeatures.js +13 -0
  95. package/lib-es/featureFlags/defaultFeatures.js.map +1 -1
  96. package/lib-es/featureFlags/stakePrograms/index.js +4 -4
  97. package/lib-es/featureFlags/stakePrograms/index.js.map +1 -1
  98. package/lib-es/featureFlags/useFeature.d.ts +1 -1
  99. package/lib-es/featureFlags/useFeature.d.ts.map +1 -1
  100. package/lib-es/load/speculos.d.ts.map +1 -1
  101. package/lib-es/load/speculos.js +1 -0
  102. package/lib-es/load/speculos.js.map +1 -1
  103. package/package.json +58 -58
  104. package/src/bridge/generic-alpaca/accountBridge.ts +3 -2
  105. package/src/bridge/generic-alpaca/currencyBridge.ts +2 -0
  106. package/src/bridge/generic-alpaca/postSync.test.ts +97 -0
  107. package/src/bridge/generic-alpaca/postSync.ts +42 -0
  108. package/src/bridge/generic-alpaca/utils.test.ts +3 -0
  109. package/src/bridge/generic-alpaca/utils.ts +1 -0
  110. package/src/e2e/data/deviceLabelsData.ts +1 -0
  111. package/src/e2e/data/regexes.ts +1 -0
  112. package/src/e2e/enum/Device.ts +1 -0
  113. package/src/e2e/families/cardano.ts +32 -14
  114. package/src/e2e/index.ts +20 -0
  115. package/src/e2e/speculos.ts +35 -4
  116. package/src/e2e/speculosAppVersion.ts +8 -2
  117. package/src/e2e/swap.ts +8 -7
  118. package/src/featureFlags/defaultFeatures.ts +13 -0
  119. package/src/featureFlags/stakePrograms/index.ts +4 -4
  120. package/src/load/speculos.ts +1 -0
@@ -0,0 +1,97 @@
1
+ import type { Account } from "@ledgerhq/types-live";
2
+ import { postSync } from "./postSync";
3
+ import BigNumber from "bignumber.js";
4
+
5
+ describe("postSync", () => {
6
+ it("removes confirmed and outdated native operations from the pending pool", () => {
7
+ const initialAccount = {
8
+ operations: [{ hash: "hash0", transactionSequenceNumber: new BigNumber(4), type: "OUT" }],
9
+ pendingOperations: [
10
+ { hash: "outdated", transactionSequenceNumber: new BigNumber(3), type: "IN" },
11
+ { hash: "hash1", transactionSequenceNumber: new BigNumber(5), type: "OUT" },
12
+ { hash: "hash2", transactionSequenceNumber: new BigNumber(6), type: "IN" },
13
+ ],
14
+ } as Account;
15
+ const synced = {
16
+ operations: [
17
+ { hash: "hash1", transactionSequenceNumber: new BigNumber(5), type: "OUT" },
18
+ { hash: "hash0", transactionSequenceNumber: new BigNumber(4), type: "OUT" },
19
+ ],
20
+ pendingOperations: [
21
+ { hash: "outdated", transactionSequenceNumber: new BigNumber(3), type: "IN" },
22
+ { hash: "hash1", transactionSequenceNumber: new BigNumber(5), type: "OUT" },
23
+ { hash: "hash2", transactionSequenceNumber: new BigNumber(6), type: "IN" },
24
+ ],
25
+ } as Account;
26
+
27
+ expect(postSync(initialAccount, synced)).toMatchObject({
28
+ operations: [
29
+ { hash: "hash1", transactionSequenceNumber: new BigNumber(5), type: "OUT" },
30
+ { hash: "hash0", transactionSequenceNumber: new BigNumber(4), type: "OUT" },
31
+ ],
32
+ pendingOperations: [
33
+ { hash: "hash2", transactionSequenceNumber: new BigNumber(6), type: "IN" },
34
+ ],
35
+ });
36
+ });
37
+
38
+ it("removes confirmed and outdated token operations from the pending pool", () => {
39
+ const initialAccount = {
40
+ operations: [{ hash: "hash0", transactionSequenceNumber: new BigNumber(4), type: "OUT" }],
41
+ pendingOperations: [
42
+ { hash: "outdated", transactionSequenceNumber: new BigNumber(3), type: "NONE" },
43
+ { hash: "hash1", transactionSequenceNumber: new BigNumber(5), type: "FEES" },
44
+ { hash: "hash2", transactionSequenceNumber: new BigNumber(6), type: "IN" },
45
+ ],
46
+ subAccounts: [
47
+ {
48
+ pendingOperations: [
49
+ { hash: "outdated", transactionSequenceNumber: new BigNumber(3), type: "IN" },
50
+ { hash: "hash1", transactionSequenceNumber: new BigNumber(5), type: "FEES" },
51
+ ],
52
+ },
53
+ ],
54
+ } as Account;
55
+ const synced = {
56
+ operations: [
57
+ { hash: "hash1", transactionSequenceNumber: new BigNumber(5), type: "FEES" },
58
+ { hash: "hash0", transactionSequenceNumber: new BigNumber(4), type: "OUT" },
59
+ ],
60
+ pendingOperations: [
61
+ { hash: "outdated", transactionSequenceNumber: new BigNumber(3), type: "NONE" },
62
+ { hash: "hash1", transactionSequenceNumber: new BigNumber(5), type: "FEES" },
63
+ { hash: "hash2", transactionSequenceNumber: new BigNumber(6), type: "IN" },
64
+ ],
65
+ subAccounts: [
66
+ {
67
+ operations: [{ hash: "hash1", transactionSequenceNumber: new BigNumber(5), type: "OUT" }],
68
+ pendingOperations: [
69
+ { hash: "outdated", transactionSequenceNumber: new BigNumber(3), type: "IN" },
70
+ ],
71
+ },
72
+ ],
73
+ } as Account;
74
+
75
+ expect(postSync(initialAccount, synced)).toMatchObject({
76
+ operations: [
77
+ { hash: "hash1", transactionSequenceNumber: new BigNumber(5), type: "FEES" },
78
+ { hash: "hash0", transactionSequenceNumber: new BigNumber(4), type: "OUT" },
79
+ ],
80
+ pendingOperations: [
81
+ { hash: "hash2", transactionSequenceNumber: new BigNumber(6), type: "IN" },
82
+ ],
83
+ subAccounts: [
84
+ {
85
+ operations: [
86
+ {
87
+ hash: "hash1",
88
+ transactionSequenceNumber: new BigNumber(5),
89
+ type: "OUT",
90
+ },
91
+ ],
92
+ pendingOperations: [],
93
+ },
94
+ ],
95
+ });
96
+ });
97
+ });
@@ -0,0 +1,42 @@
1
+ import type { Account, AccountLike, Operation } from "@ledgerhq/types-live";
2
+ import BigNumber from "bignumber.js";
3
+
4
+ /**
5
+ * After each sync or scan, remove operations from the pending pools if necessary
6
+ * Operations stay pending if and only if
7
+ * - they are confirmed, i.e. their hash appear in the operation list
8
+ * - they are not outdated, i.e. their sequence number is at least greater than the
9
+ * sequence number of the latest transaction
10
+ * NOTE Compared to the default behaviour
11
+ * - pending operations of token accounts are cleaned up, so we don't see both pending and completed
12
+ * sub operations in the operation details drawer
13
+ * - pending operations are cleaned if their hash already belong to the completed operations, preventing
14
+ * undesired replacement (ex: optimistic operation for self token sending on EVM is incomplete, since
15
+ * it only contains the OUT sub operation)
16
+ */
17
+ export function postSync(initial: Account, synced: Account): Account {
18
+ const lastOperation = synced.operations.find(op => ["OUT", "FEES"].includes(op.type));
19
+ const latestSequence = lastOperation?.transactionSequenceNumber || new BigNumber(-1);
20
+
21
+ function isPending(account: AccountLike, op: Operation): boolean {
22
+ return (
23
+ // Operation is not confirmed
24
+ !account.operations.some(o => o.hash === op.hash) &&
25
+ // Operation is not outdated
26
+ op.transactionSequenceNumber !== undefined &&
27
+ op.transactionSequenceNumber.gt(latestSequence)
28
+ );
29
+ }
30
+
31
+ const pendingOperations = initial.pendingOperations.length
32
+ ? initial.pendingOperations.filter(op => isPending(synced, op))
33
+ : [];
34
+ const subAccounts = synced.subAccounts?.length
35
+ ? synced.subAccounts.map(subAccount => ({
36
+ ...subAccount,
37
+ pendingOperations: subAccount.pendingOperations.filter(op => isPending(subAccount, op)),
38
+ }))
39
+ : [];
40
+
41
+ return { ...synced, pendingOperations, subAccounts };
42
+ }
@@ -166,10 +166,12 @@ describe("Alpaca utils", () => {
166
166
  },
167
167
  ...params,
168
168
  } as GenericTransaction,
169
+ 3n,
169
170
  );
170
171
 
171
172
  expect(operation).toMatchObject({
172
173
  id: `parent-account-id--${expected.parentType}`,
174
+ transactionSequenceNumber: new BigNumber(3),
173
175
  type: expected.parentType,
174
176
  value: expected.parentValue,
175
177
  accountId: "parent-account-id",
@@ -194,6 +196,7 @@ describe("Alpaca utils", () => {
194
196
  subOperations: [
195
197
  {
196
198
  id: `sub-account-id--${expected.subType}`,
199
+ transactionSequenceNumber: new BigNumber(3),
197
200
  accountId: "sub-account-id",
198
201
  type: expected.subType,
199
202
  senders: ["account-address"],
@@ -412,6 +412,7 @@ export const buildOptimisticOperation = (
412
412
  blockHeight: null,
413
413
  senders: [account.freshAddress],
414
414
  recipients: [transaction.recipient],
415
+ transactionSequenceNumber: new BigNumber(sequenceNumber?.toString() ?? 0),
415
416
  accountId: subAccountId,
416
417
  date: new Date(),
417
418
  transactionRaw: toGenericTransactionRaw({
@@ -112,6 +112,7 @@ export const DEVICE_LABELS_CONFIG: DeviceLabelsConfig = {
112
112
  },
113
113
  [DeviceModelId.stax]: TOUCHSCREEN_DEVICE_CONFIG,
114
114
  [DeviceModelId.europa]: TOUCHSCREEN_DEVICE_CONFIG,
115
+ [DeviceModelId.apex]: TOUCHSCREEN_DEVICE_CONFIG,
115
116
  default: {
116
117
  receiveVerify: {
117
118
  [AppInfos.BNB_CHAIN.name]: DeviceLabels.VERIFY_BSC,
@@ -0,0 +1 @@
1
+ export const floatNumberRegex = /^\d+\.?\d+$/;
@@ -9,4 +9,5 @@ export class Device {
9
9
  static readonly LNSP = new Device("nanoSP", 856686596);
10
10
  static readonly STAX = new Device("stax", 857735172);
11
11
  static readonly FLEX = new Device("flex", 858783748);
12
+ static readonly NANO_GEN_5 = new Device("nanoGen5", 859832324);
12
13
  }
@@ -22,7 +22,7 @@ function validateTransactionData(tx: Transaction, events: string[]) {
22
22
 
23
23
  async function sendCardanoTouchDevices(tx: Transaction) {
24
24
  await waitFor(DeviceLabels.REVIEW_TRANSACTION);
25
- const events = await pressUntilTextFound(DeviceLabels.TO);
25
+ const events = await pressUntilTextFound(DeviceLabels.AMOUNT);
26
26
  validateTransactionData(tx, events);
27
27
  await pressAndRelease(DeviceLabels.TAP_TO_CONTINUE);
28
28
  await waitFor(DeviceLabels.FEES);
@@ -77,18 +77,22 @@ export async function sendCardano(tx: Transaction) {
77
77
  return sendCardanoButtonDevice(tx);
78
78
  }
79
79
 
80
+ const TOUCH_DELEGATE_STEPS = [
81
+ [DeviceLabels.REVIEW_TRANSACTION, "swipe"],
82
+ [DeviceLabels.TAP_TO_CONTINUE, "tap"],
83
+ [DeviceLabels.REGISTER, "swipe"],
84
+ [DeviceLabels.TAP_TO_CONTINUE, "tap"],
85
+ [DeviceLabels.CONFIRM, "confirm"],
86
+ [DeviceLabels.DELEGATE_STAKE, "swipe"],
87
+ [DeviceLabels.TAP_TO_CONTINUE, "tap"],
88
+ [DeviceLabels.CONFIRM, "confirm"],
89
+ [DeviceLabels.HOLD_TO_SIGN, "hold"],
90
+ ] as const;
91
+
80
92
  const DELEGATE_STEPS_CONFIG = {
81
- [DeviceModelId.stax || DeviceModelId.europa]: [
82
- [DeviceLabels.REVIEW_TRANSACTION, "swipe"],
83
- [DeviceLabels.TAP_TO_CONTINUE, "tap"],
84
- [DeviceLabels.REGISTER, "swipe"],
85
- [DeviceLabels.TAP_TO_CONTINUE, "tap"],
86
- [DeviceLabels.CONFIRM, "confirm"],
87
- [DeviceLabels.DELEGATE_STAKE, "swipe"],
88
- [DeviceLabels.TAP_TO_CONTINUE, "tap"],
89
- [DeviceLabels.CONFIRM, "confirm"],
90
- [DeviceLabels.HOLD_TO_SIGN, "hold"],
91
- ] as const,
93
+ [DeviceModelId.stax]: TOUCH_DELEGATE_STEPS,
94
+ [DeviceModelId.europa]: TOUCH_DELEGATE_STEPS,
95
+ [DeviceModelId.apex]: TOUCH_DELEGATE_STEPS,
92
96
  [DeviceModelId.nanoS]: [
93
97
  [DeviceLabels.NEW_ORDINARY, "right"],
94
98
  [DeviceLabels.TRANSACTION_FEE, "both"],
@@ -115,8 +119,22 @@ const DELEGATE_STEPS_CONFIG = {
115
119
  ] as const,
116
120
  };
117
121
 
122
+ function getConfirmButtonCoords(): { x: number; y: number } {
123
+ const speculosModel = getSpeculosModel();
124
+
125
+ switch (speculosModel) {
126
+ case DeviceModelId.stax:
127
+ return { x: 152, y: 532 };
128
+ case DeviceModelId.apex:
129
+ return { x: 114, y: 305 };
130
+ case DeviceModelId.europa:
131
+ default:
132
+ return { x: 186, y: 446 };
133
+ }
134
+ }
135
+
118
136
  async function delegateTouchDevicesAction(label: DeviceLabels) {
119
- const CONFIRM_BUTTON_COORDS = { x: 139, y: 532 };
137
+ const CONFIRM_BUTTON_COORDS = getConfirmButtonCoords();
120
138
  await waitFor(label);
121
139
  switch (label) {
122
140
  case DeviceLabels.TAP_TO_CONTINUE:
@@ -164,7 +182,7 @@ async function executeDelegateStep(label: DeviceLabels, action: ActionType) {
164
182
  export async function delegateCardano() {
165
183
  const speculosModel = getSpeculosModel();
166
184
  const steps =
167
- speculosModel === DeviceModelId.stax || speculosModel === DeviceModelId.europa
185
+ isTouchDevice() && DELEGATE_STEPS_CONFIG[speculosModel]
168
186
  ? DELEGATE_STEPS_CONFIG[speculosModel]
169
187
  : speculosModel === DeviceModelId.nanoS
170
188
  ? DELEGATE_STEPS_CONFIG[DeviceModelId.nanoS]
package/src/e2e/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { EnvName } from "@ledgerhq/live-env";
2
2
  import { Feature, FeatureId } from "@ledgerhq/types-live";
3
3
  import { getFeature, DEFAULT_FEATURES } from "../featureFlags";
4
+ import axios, { AxiosError } from "axios";
4
5
 
5
6
  export const getAllFeatureFlags = (
6
7
  appLanguage?: string,
@@ -53,3 +54,22 @@ export const formatEnvData = (data: { [key in EnvName]: unknown }) => {
53
54
  }
54
55
  return allureData;
55
56
  };
57
+
58
+ export const sanitizeError = (error: unknown): Error => {
59
+ if (!axios.isAxiosError(error)) {
60
+ return error instanceof Error ? error : new Error(String(error ?? "Unknown error"));
61
+ }
62
+
63
+ const err = error as AxiosError;
64
+ const sanitized = new Error(err.message || "Axios request failed");
65
+
66
+ Object.assign(sanitized, {
67
+ name: err.name,
68
+ code: err.code,
69
+ url: err.config?.url,
70
+ method: err.config?.method,
71
+ status: err.response?.status,
72
+ });
73
+
74
+ return sanitized;
75
+ };
@@ -49,6 +49,7 @@ import {
49
49
  swipeRight,
50
50
  } from "./deviceInteraction/TouchDeviceSimulator";
51
51
  import { withDeviceController } from "./deviceInteraction/DeviceController";
52
+ import { sanitizeError } from ".";
52
53
 
53
54
  const isSpeculosRemote = process.env.REMOTE_SPECULOS === "true";
54
55
 
@@ -428,7 +429,7 @@ export async function startSpeculos(
428
429
  };
429
430
  });
430
431
  } catch (e: unknown) {
431
- console.error(e);
432
+ console.error(sanitizeError(e));
432
433
  log("engine", `test ${testName} failed with ${String(e)}`);
433
434
  }
434
435
  }
@@ -610,7 +611,7 @@ export async function takeScreenshot(port?: number): Promise<Buffer | undefined>
610
611
  );
611
612
  return response.data;
612
613
  } catch (error) {
613
- console.error("Error downloading speculos screenshot:", error);
614
+ console.error("Error downloading speculos screenshot:", sanitizeError(error));
614
615
  }
615
616
  }
616
617
 
@@ -668,12 +669,42 @@ export const activateLedgerSync = withDeviceController(({ getButtonsController }
668
669
  }
669
670
  });
670
671
 
672
+ const getSettingsToggle1Coordinates = () => {
673
+ const deviceModel = getSpeculosModel();
674
+
675
+ switch (deviceModel) {
676
+ case DeviceModelId.stax:
677
+ return { x: 345, y: 136 };
678
+ case DeviceModelId.europa:
679
+ return { x: 420, y: 140 };
680
+ case DeviceModelId.apex:
681
+ return { x: 263, y: 100 };
682
+ default:
683
+ return { x: 420, y: 140 };
684
+ }
685
+ };
686
+
687
+ const getSettingsCogwheelCoordinates = () => {
688
+ const deviceModel = getSpeculosModel();
689
+
690
+ switch (deviceModel) {
691
+ case DeviceModelId.stax:
692
+ return { x: 362, y: 43 };
693
+ case DeviceModelId.europa:
694
+ return { x: 400, y: 80 };
695
+ case DeviceModelId.apex:
696
+ return { x: 253, y: 58 };
697
+ default:
698
+ return { x: 400, y: 80 };
699
+ }
700
+ };
701
+
671
702
  export const activateExpertMode = withDeviceController(({ getButtonsController }) => async () => {
672
703
  const buttons = getButtonsController();
673
704
 
674
705
  if (isTouchDevice()) {
675
706
  await goToSettings();
676
- const SettingsToggle1Coordinates = { x: 344, y: 136 };
707
+ const SettingsToggle1Coordinates = getSettingsToggle1Coordinates();
677
708
  await pressAndRelease(
678
709
  DeviceLabels.SETTINGS_TOGGLE_1,
679
710
  SettingsToggle1Coordinates.x,
@@ -698,7 +729,7 @@ export const goToSettings = withDeviceController(({ getButtonsController }) => a
698
729
  const buttons = getButtonsController();
699
730
 
700
731
  if (isTouchDevice()) {
701
- const SettingsCogwheelCoordinates = { x: 400, y: 75 };
732
+ const SettingsCogwheelCoordinates = getSettingsCogwheelCoordinates();
702
733
  await pressAndRelease(
703
734
  DeviceLabels.SETTINGS,
704
735
  SettingsCogwheelCoordinates.x,
@@ -3,6 +3,7 @@ import { version } from "../../package.json";
3
3
  import { getEnv } from "@ledgerhq/live-env";
4
4
  import { DeviceModelId } from "@ledgerhq/devices";
5
5
  import { Device as CryptoWallet } from "./enum/Device";
6
+ import { sanitizeError } from "./index";
6
7
  import * as fs from "fs";
7
8
  import * as path from "path";
8
9
 
@@ -18,6 +19,8 @@ export function getSpeculosModel(): DeviceModelId {
18
19
  case CryptoWallet.FLEX.name:
19
20
  case DeviceModelId.europa:
20
21
  return DeviceModelId.europa;
22
+ case CryptoWallet.NANO_GEN_5.name:
23
+ return DeviceModelId.apex;
21
24
  case CryptoWallet.LNSP.name:
22
25
  default:
23
26
  return DeviceModelId.nanoSP;
@@ -26,7 +29,9 @@ export function getSpeculosModel(): DeviceModelId {
26
29
 
27
30
  export function isTouchDevice(): boolean {
28
31
  const model = getSpeculosModel();
29
- return model === DeviceModelId.stax || model === DeviceModelId.europa;
32
+ return (
33
+ model === DeviceModelId.stax || model === DeviceModelId.europa || model === DeviceModelId.apex
34
+ );
30
35
  }
31
36
 
32
37
  function getDeviceTargetId(device: DeviceModelId): number {
@@ -36,6 +41,7 @@ function getDeviceTargetId(device: DeviceModelId): number {
36
41
  [DeviceModelId.nanoSP]: CryptoWallet.LNSP.targetId,
37
42
  [DeviceModelId.stax]: CryptoWallet.STAX.targetId,
38
43
  [DeviceModelId.europa]: CryptoWallet.FLEX.targetId,
44
+ [DeviceModelId.apex]: CryptoWallet.NANO_GEN_5.targetId,
39
45
  };
40
46
  return modelToTargetIdMap[device];
41
47
  }
@@ -73,7 +79,7 @@ export async function createNanoAppJsonFile(nanoAppFilePath: string): Promise<vo
73
79
  }
74
80
  fs.writeFileSync(jsonFilePath, JSON.stringify(appCatalog, null, 2), "utf8");
75
81
  } catch (error) {
76
- console.error("Unable to create app version file:", error);
82
+ console.error("Unable to create app version file:", sanitizeError(error));
77
83
  }
78
84
  }
79
85
 
package/src/e2e/swap.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { Account } from "./enum/Account";
2
+ import { sanitizeError } from "./index";
2
3
  import axios from "axios";
3
4
 
4
5
  export async function getMinimumSwapAmount(AccountFrom: Account, AccountTo: Account) {
5
6
  try {
6
7
  const requestConfig = {
7
8
  method: "GET",
8
- url: `https://swap-stg.ledger-test.com/v5/quote`,
9
+ url: "https://swap-stg.ledger-test.com/v5/quote",
9
10
  params: {
10
11
  from: AccountFrom.currency.id,
11
12
  to: AccountTo.currency.id,
@@ -21,9 +22,7 @@ export async function getMinimumSwapAmount(AccountFrom: Account, AccountTo: Acco
21
22
  tradeType: "INPUT",
22
23
  uniswapOrderType: "uniswapxv1",
23
24
  },
24
- headers: {
25
- accept: "application/json",
26
- },
25
+ headers: { accept: "application/json" },
27
26
  };
28
27
 
29
28
  const { data } = await axios(requestConfig);
@@ -38,8 +37,10 @@ export async function getMinimumSwapAmount(AccountFrom: Account, AccountTo: Acco
38
37
  }
39
38
 
40
39
  return Math.max(...minimumAmounts);
41
- } catch (error) {
42
- console.error("Error fetching swap minimum amount:", error);
43
- throw error;
40
+ } catch (error: any) {
41
+ const sanitizedError = sanitizeError(error);
42
+ console.error("Error fetching swap minimum amount:", sanitizedError);
43
+ // throw the sanitized error, not the original circular Axios error
44
+ throw sanitizedError;
44
45
  }
45
46
  }
@@ -8,6 +8,7 @@ import {
8
8
  import reduce from "lodash/reduce";
9
9
  import { BUY_SELL_UI_APP_ID } from "../wallet-api/constants";
10
10
  import { formatToFirebaseFeatureId } from "./firebaseFeatureFlags";
11
+ import { DeviceModelId } from "@ledgerhq/types-devices";
11
12
 
12
13
  /**
13
14
  * Default disabled feature.
@@ -215,6 +216,18 @@ export const DEFAULT_FEATURES: Features = {
215
216
  protectId: "protect-simu",
216
217
  },
217
218
  },
219
+ recoverUpsellPostOnboarding: {
220
+ ...DEFAULT_FEATURE,
221
+ params: {
222
+ deviceIds: [
223
+ DeviceModelId.nanoSP,
224
+ DeviceModelId.nanoX,
225
+ DeviceModelId.stax,
226
+ DeviceModelId.europa,
227
+ DeviceModelId.apex,
228
+ ],
229
+ },
230
+ },
218
231
 
219
232
  storyly: {
220
233
  enabled: false,
@@ -44,7 +44,7 @@ export const getBitcoinYieldSetting = (stakePrograms: Feature_StakePrograms | nu
44
44
  };
45
45
 
46
46
  export const getEthDepositScreenSetting = (stakePrograms: Feature_StakePrograms | null): string => {
47
- /** Check if Ethereum has "earn" provider configured in redirects with cohort. */
47
+ /** Check if Ethereum has "earn" provider configured in redirects with ethDepositCohort. */
48
48
  const ethereumRedirect = stakePrograms?.enabled
49
49
  ? stakePrograms?.params?.redirects?.["ethereum"]
50
50
  : undefined;
@@ -55,8 +55,8 @@ export const getEthDepositScreenSetting = (stakePrograms: Feature_StakePrograms
55
55
  return "standard";
56
56
  }
57
57
 
58
- // Extract cohort from queryParams
59
- const cohort = ethereumRedirect.queryParams?.cohort;
58
+ // Extract ethDepositCohort from queryParams
59
+ const ethDepositCohort = ethereumRedirect.queryParams?.ethDepositCohort;
60
60
 
61
- return cohort ?? "missing_cohort_value";
61
+ return ethDepositCohort ?? "missing_cohort_value";
62
62
  };
@@ -28,6 +28,7 @@ export { closeAllSpeculosDevices, releaseSpeculosDevice, createSpeculosDevice };
28
28
  export type { SpeculosTransport };
29
29
 
30
30
  const modelMapPriority: Record<string, number> = {
31
+ apex_p: 7,
31
32
  flex: 6,
32
33
  stax: 5,
33
34
  nanos: 4,