@ledgerhq/live-common 34.49.0-nightly.4 → 34.49.0-nightly.6

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 (167) hide show
  1. package/lib/apps/config.js +1 -1
  2. package/lib/bridge/react/BridgeSync.d.ts.map +1 -1
  3. package/lib/bridge/react/BridgeSync.js +17 -4
  4. package/lib/bridge/react/BridgeSync.js.map +1 -1
  5. package/lib/bridge/syncSessionManager/index.d.ts +18 -0
  6. package/lib/bridge/syncSessionManager/index.d.ts.map +1 -0
  7. package/lib/bridge/syncSessionManager/index.js +75 -0
  8. package/lib/bridge/syncSessionManager/index.js.map +1 -0
  9. package/lib/bridge/syncSessionManager/index.test.d.ts +2 -0
  10. package/lib/bridge/syncSessionManager/index.test.d.ts.map +1 -0
  11. package/lib/bridge/syncSessionManager/index.test.js +118 -0
  12. package/lib/bridge/syncSessionManager/index.test.js.map +1 -0
  13. package/lib/e2e/data/deviceLabelsData.js +2 -2
  14. package/lib/e2e/data/deviceLabelsData.js.map +1 -1
  15. package/lib/e2e/enum/Account.d.ts +1 -0
  16. package/lib/e2e/enum/Account.d.ts.map +1 -1
  17. package/lib/e2e/enum/Account.js +1 -0
  18. package/lib/e2e/enum/Account.js.map +1 -1
  19. package/lib/e2e/enum/DeviceLabels.d.ts +1 -9
  20. package/lib/e2e/enum/DeviceLabels.d.ts.map +1 -1
  21. package/lib/e2e/enum/DeviceLabels.js +0 -8
  22. package/lib/e2e/enum/DeviceLabels.js.map +1 -1
  23. package/lib/e2e/families/tezos.d.ts.map +1 -1
  24. package/lib/e2e/families/tezos.js +5 -3
  25. package/lib/e2e/families/tezos.js.map +1 -1
  26. package/lib/e2e/index.d.ts +4 -2
  27. package/lib/e2e/index.d.ts.map +1 -1
  28. package/lib/e2e/models/Swap.d.ts +2 -0
  29. package/lib/e2e/models/Swap.d.ts.map +1 -1
  30. package/lib/e2e/models/Swap.js +6 -0
  31. package/lib/e2e/models/Swap.js.map +1 -1
  32. package/lib/e2e/speculos.d.ts +8 -0
  33. package/lib/e2e/speculos.d.ts.map +1 -1
  34. package/lib/e2e/speculos.js +17 -31
  35. package/lib/e2e/speculos.js.map +1 -1
  36. package/lib/exchange/providers/swap.js +2 -2
  37. package/lib/exchange/providers/swap.js.map +1 -1
  38. package/lib/featureFlags/defaultFeatures.d.ts.map +1 -1
  39. package/lib/featureFlags/defaultFeatures.js +2 -1
  40. package/lib/featureFlags/defaultFeatures.js.map +1 -1
  41. package/lib/featureFlags/useFeature.d.ts +1 -1
  42. package/lib/featureFlags/useFeature.d.ts.map +1 -1
  43. package/lib/modularDrawer/hooks/modules/useLeftApyModule.d.ts +1 -0
  44. package/lib/modularDrawer/hooks/modules/useLeftApyModule.d.ts.map +1 -1
  45. package/lib/modularDrawer/hooks/modules/useLeftMarketTrendModule.d.ts +1 -0
  46. package/lib/modularDrawer/hooks/modules/useLeftMarketTrendModule.d.ts.map +1 -1
  47. package/lib/modularDrawer/hooks/modules/useRightMarketTrendModule.d.ts +1 -0
  48. package/lib/modularDrawer/hooks/modules/useRightMarketTrendModule.d.ts.map +1 -1
  49. package/lib/modularDrawer/hooks/useRightBalanceAsset.d.ts +1 -0
  50. package/lib/modularDrawer/hooks/useRightBalanceAsset.d.ts.map +1 -1
  51. package/lib/modularDrawer/hooks/useRightBalanceNetwork.d.ts +1 -0
  52. package/lib/modularDrawer/hooks/useRightBalanceNetwork.d.ts.map +1 -1
  53. package/lib/notifications/ServiceStatusProvider/entry-points.d.ts +3 -0
  54. package/lib/notifications/ServiceStatusProvider/entry-points.d.ts.map +1 -0
  55. package/lib/notifications/ServiceStatusProvider/entry-points.js +3 -0
  56. package/lib/notifications/ServiceStatusProvider/entry-points.js.map +1 -0
  57. package/lib/notifications/ServiceStatusProvider/index.d.ts +18 -1
  58. package/lib/notifications/ServiceStatusProvider/index.d.ts.map +1 -1
  59. package/lib/notifications/ServiceStatusProvider/index.js +47 -9
  60. package/lib/notifications/ServiceStatusProvider/index.js.map +1 -1
  61. package/lib/notifications/ServiceStatusProvider/index.test.d.ts +2 -0
  62. package/lib/notifications/ServiceStatusProvider/index.test.d.ts.map +1 -0
  63. package/lib/notifications/ServiceStatusProvider/index.test.js +88 -0
  64. package/lib/notifications/ServiceStatusProvider/index.test.js.map +1 -0
  65. package/lib/notifications/ServiceStatusProvider/ledger-components.d.ts +2 -0
  66. package/lib/notifications/ServiceStatusProvider/ledger-components.d.ts.map +1 -0
  67. package/lib/notifications/ServiceStatusProvider/ledger-components.js +17 -0
  68. package/lib/notifications/ServiceStatusProvider/ledger-components.js.map +1 -0
  69. package/lib/notifications/ServiceStatusProvider/mocks/ledgerStatus.d.ts +154 -0
  70. package/lib/notifications/ServiceStatusProvider/mocks/ledgerStatus.d.ts.map +1 -0
  71. package/lib/notifications/ServiceStatusProvider/mocks/ledgerStatus.js +1192 -0
  72. package/lib/notifications/ServiceStatusProvider/mocks/ledgerStatus.js.map +1 -0
  73. package/lib/notifications/ServiceStatusProvider/types.d.ts +3 -1
  74. package/lib/notifications/ServiceStatusProvider/types.d.ts.map +1 -1
  75. package/lib-es/apps/config.js +1 -1
  76. package/lib-es/bridge/react/BridgeSync.d.ts.map +1 -1
  77. package/lib-es/bridge/react/BridgeSync.js +17 -4
  78. package/lib-es/bridge/react/BridgeSync.js.map +1 -1
  79. package/lib-es/bridge/syncSessionManager/index.d.ts +18 -0
  80. package/lib-es/bridge/syncSessionManager/index.d.ts.map +1 -0
  81. package/lib-es/bridge/syncSessionManager/index.js +68 -0
  82. package/lib-es/bridge/syncSessionManager/index.js.map +1 -0
  83. package/lib-es/bridge/syncSessionManager/index.test.d.ts +2 -0
  84. package/lib-es/bridge/syncSessionManager/index.test.d.ts.map +1 -0
  85. package/lib-es/bridge/syncSessionManager/index.test.js +116 -0
  86. package/lib-es/bridge/syncSessionManager/index.test.js.map +1 -0
  87. package/lib-es/e2e/data/deviceLabelsData.js +2 -2
  88. package/lib-es/e2e/data/deviceLabelsData.js.map +1 -1
  89. package/lib-es/e2e/enum/Account.d.ts +1 -0
  90. package/lib-es/e2e/enum/Account.d.ts.map +1 -1
  91. package/lib-es/e2e/enum/Account.js +1 -0
  92. package/lib-es/e2e/enum/Account.js.map +1 -1
  93. package/lib-es/e2e/enum/DeviceLabels.d.ts +1 -9
  94. package/lib-es/e2e/enum/DeviceLabels.d.ts.map +1 -1
  95. package/lib-es/e2e/enum/DeviceLabels.js +0 -8
  96. package/lib-es/e2e/enum/DeviceLabels.js.map +1 -1
  97. package/lib-es/e2e/families/tezos.d.ts.map +1 -1
  98. package/lib-es/e2e/families/tezos.js +6 -4
  99. package/lib-es/e2e/families/tezos.js.map +1 -1
  100. package/lib-es/e2e/index.d.ts +4 -2
  101. package/lib-es/e2e/index.d.ts.map +1 -1
  102. package/lib-es/e2e/models/Swap.d.ts +2 -0
  103. package/lib-es/e2e/models/Swap.d.ts.map +1 -1
  104. package/lib-es/e2e/models/Swap.js +6 -0
  105. package/lib-es/e2e/models/Swap.js.map +1 -1
  106. package/lib-es/e2e/speculos.d.ts +8 -0
  107. package/lib-es/e2e/speculos.d.ts.map +1 -1
  108. package/lib-es/e2e/speculos.js +16 -31
  109. package/lib-es/e2e/speculos.js.map +1 -1
  110. package/lib-es/exchange/providers/swap.js +2 -2
  111. package/lib-es/exchange/providers/swap.js.map +1 -1
  112. package/lib-es/featureFlags/defaultFeatures.d.ts.map +1 -1
  113. package/lib-es/featureFlags/defaultFeatures.js +2 -1
  114. package/lib-es/featureFlags/defaultFeatures.js.map +1 -1
  115. package/lib-es/featureFlags/useFeature.d.ts +1 -1
  116. package/lib-es/featureFlags/useFeature.d.ts.map +1 -1
  117. package/lib-es/modularDrawer/hooks/modules/useLeftApyModule.d.ts +1 -0
  118. package/lib-es/modularDrawer/hooks/modules/useLeftApyModule.d.ts.map +1 -1
  119. package/lib-es/modularDrawer/hooks/modules/useLeftMarketTrendModule.d.ts +1 -0
  120. package/lib-es/modularDrawer/hooks/modules/useLeftMarketTrendModule.d.ts.map +1 -1
  121. package/lib-es/modularDrawer/hooks/modules/useRightMarketTrendModule.d.ts +1 -0
  122. package/lib-es/modularDrawer/hooks/modules/useRightMarketTrendModule.d.ts.map +1 -1
  123. package/lib-es/modularDrawer/hooks/useRightBalanceAsset.d.ts +1 -0
  124. package/lib-es/modularDrawer/hooks/useRightBalanceAsset.d.ts.map +1 -1
  125. package/lib-es/modularDrawer/hooks/useRightBalanceNetwork.d.ts +1 -0
  126. package/lib-es/modularDrawer/hooks/useRightBalanceNetwork.d.ts.map +1 -1
  127. package/lib-es/notifications/ServiceStatusProvider/entry-points.d.ts +3 -0
  128. package/lib-es/notifications/ServiceStatusProvider/entry-points.d.ts.map +1 -0
  129. package/lib-es/notifications/ServiceStatusProvider/entry-points.js +2 -0
  130. package/lib-es/notifications/ServiceStatusProvider/entry-points.js.map +1 -0
  131. package/lib-es/notifications/ServiceStatusProvider/index.d.ts +18 -1
  132. package/lib-es/notifications/ServiceStatusProvider/index.d.ts.map +1 -1
  133. package/lib-es/notifications/ServiceStatusProvider/index.js +47 -9
  134. package/lib-es/notifications/ServiceStatusProvider/index.js.map +1 -1
  135. package/lib-es/notifications/ServiceStatusProvider/index.test.d.ts +2 -0
  136. package/lib-es/notifications/ServiceStatusProvider/index.test.d.ts.map +1 -0
  137. package/lib-es/notifications/ServiceStatusProvider/index.test.js +86 -0
  138. package/lib-es/notifications/ServiceStatusProvider/index.test.js.map +1 -0
  139. package/lib-es/notifications/ServiceStatusProvider/ledger-components.d.ts +2 -0
  140. package/lib-es/notifications/ServiceStatusProvider/ledger-components.d.ts.map +1 -0
  141. package/lib-es/notifications/ServiceStatusProvider/ledger-components.js +14 -0
  142. package/lib-es/notifications/ServiceStatusProvider/ledger-components.js.map +1 -0
  143. package/lib-es/notifications/ServiceStatusProvider/mocks/ledgerStatus.d.ts +154 -0
  144. package/lib-es/notifications/ServiceStatusProvider/mocks/ledgerStatus.d.ts.map +1 -0
  145. package/lib-es/notifications/ServiceStatusProvider/mocks/ledgerStatus.js +1189 -0
  146. package/lib-es/notifications/ServiceStatusProvider/mocks/ledgerStatus.js.map +1 -0
  147. package/lib-es/notifications/ServiceStatusProvider/types.d.ts +3 -1
  148. package/lib-es/notifications/ServiceStatusProvider/types.d.ts.map +1 -1
  149. package/package.json +67 -67
  150. package/src/apps/config.ts +1 -1
  151. package/src/bridge/react/BridgeSync.tsx +17 -3
  152. package/src/bridge/syncSessionManager/index.test.ts +151 -0
  153. package/src/bridge/syncSessionManager/index.ts +93 -0
  154. package/src/e2e/data/deviceLabelsData.ts +2 -2
  155. package/src/e2e/enum/Account.ts +6 -0
  156. package/src/e2e/enum/DeviceLabels.ts +0 -8
  157. package/src/e2e/families/tezos.ts +13 -4
  158. package/src/e2e/models/Swap.ts +8 -0
  159. package/src/e2e/speculos.ts +19 -34
  160. package/src/exchange/providers/swap.ts +2 -2
  161. package/src/featureFlags/defaultFeatures.ts +2 -1
  162. package/src/notifications/ServiceStatusProvider/entry-points.ts +3 -0
  163. package/src/notifications/ServiceStatusProvider/index.test.ts +102 -0
  164. package/src/notifications/ServiceStatusProvider/index.tsx +61 -13
  165. package/src/notifications/ServiceStatusProvider/ledger-components.ts +13 -0
  166. package/src/notifications/ServiceStatusProvider/mocks/ledgerStatus.ts +1188 -0
  167. package/src/notifications/ServiceStatusProvider/types.ts +4 -1
@@ -7,9 +7,7 @@ export enum DeviceLabels {
7
7
  AMOUNT = "Amount",
8
8
  APPROVE = "Approve",
9
9
  BITCOIN_IS_READY = "Bitcoin is ready",
10
- CANCEL = "Cancel",
11
10
  CAPS_APPROVE = "APPROVE",
12
- CAPS_REJECT = "REJECT",
13
11
  CHAIN_ID = "Chain ID",
14
12
  CONFIRM = "Confirm",
15
13
  CONNECT = "Connect",
@@ -20,19 +18,14 @@ export enum DeviceLabels {
20
18
  DELEGATE_FROM = "Delegate from",
21
19
  DELEGATE_STAKE = "Delegate stake",
22
20
  DEPOSIT = "Deposit",
23
- DEST = "Dest",
24
- DESTINATION = "Destination",
25
21
  EXPERT_MODE = "Expert mode",
26
22
  FEES = "Fees",
27
- FINALIZE = "Finalize",
28
23
  GET = "Get",
29
24
  I_UNDERSTAND = "I understand",
30
25
  LEDGER_LIVE_WILL_BE = "Ledger Live will be",
31
- METHOD_NAME = "Method name",
32
26
  NEW_ORDINARY = "New ordinary",
33
27
  PLEASE_REVIEW = "Please",
34
28
  PUBKEY = "Pubkey",
35
- PUBLICKEY = "Public Key Hash",
36
29
  READY = "Ready",
37
30
  RECEIVE_ADDRESS_DOES_NOT_BELONG = "Receive address does",
38
31
  RECEIVER = "Receiver",
@@ -65,5 +58,4 @@ export enum DeviceLabels {
65
58
  VERIFY_SOLANA_ADDRESS = "Verify Solana address",
66
59
  VIEW_ACTION = "View action",
67
60
  VIEW_HEADER = "View Header",
68
- WALLET_ID = "Wallet ID",
69
61
  }
@@ -1,14 +1,23 @@
1
- import { getDelegateEvents, getSpeculosModel, pressBoth, pressUntilTextFound } from "../speculos";
2
- import { DeviceLabels } from "../enum/DeviceLabels";
1
+ import {
2
+ getDelegateEvents,
3
+ getDeviceLabels,
4
+ getSpeculosModel,
5
+ pressBoth,
6
+ pressUntilTextFound,
7
+ } from "../speculos";
3
8
  import { Delegate } from "../models/Delegate";
4
9
  import { DeviceModelId } from "@ledgerhq/types-devices";
10
+ import { DeviceLabels } from "../enum/DeviceLabels";
5
11
 
6
12
  export async function delegateTezos(delegatingAccount: Delegate) {
13
+ const { delegateConfirmLabel } = getDeviceLabels(delegatingAccount.account.currency.speculosApp);
14
+
7
15
  await getDelegateEvents(delegatingAccount);
16
+ await pressUntilTextFound(delegateConfirmLabel);
8
17
  await pressBoth();
9
18
 
10
- if (getSpeculosModel() !== DeviceModelId.nanoS) {
11
- await pressUntilTextFound(DeviceLabels.ACCEPT);
19
+ if (getSpeculosModel() == DeviceModelId.nanoS) {
20
+ await pressUntilTextFound(DeviceLabels.ACCEPT_AND_SEND);
12
21
  await pressBoth();
13
22
  }
14
23
  }
@@ -27,4 +27,12 @@ export class Swap extends Transaction {
27
27
  public get getAmount(): string {
28
28
  return this.amount;
29
29
  }
30
+
31
+ public get getAccountToDebit(): Account {
32
+ return this.accountToDebit;
33
+ }
34
+
35
+ public get getAccountToCredit(): Account {
36
+ return this.accountToCredit;
37
+ }
30
38
  }
@@ -630,7 +630,7 @@ type DeviceLabelsReturn = {
630
630
  receiveVerifyLabel: string;
631
631
  };
632
632
 
633
- function getDeviceLabels(appInfo: AppInfos): DeviceLabelsReturn {
633
+ export function getDeviceLabels(appInfo: AppInfos): DeviceLabelsReturn {
634
634
  const deviceModel = getSpeculosModel();
635
635
  const deviceConfig = DEVICE_LABELS_CONFIG[deviceModel] ?? DEVICE_LABELS_CONFIG.default;
636
636
 
@@ -765,6 +765,7 @@ export async function getDelegateEvents(delegatingAccount: Delegate): Promise<st
765
765
  );
766
766
 
767
767
  await waitFor(delegateVerifyLabel);
768
+
768
769
  return await pressUntilTextFound(delegateConfirmLabel);
769
770
  }
770
771
 
@@ -774,54 +775,38 @@ export async function verifyAmountsAndAcceptSwap(swap: Swap, amount: string) {
774
775
  getSpeculosModel() === DeviceModelId.nanoS
775
776
  ? await pressUntilTextFound(DeviceLabels.ACCEPT_AND_SEND)
776
777
  : await pressUntilTextFound(DeviceLabels.SIGN_TRANSACTION);
777
- await verifySwapData(swap, events, amount);
778
+ verifySwapData(swap, events, amount);
778
779
  await pressBoth();
779
780
  }
780
781
 
781
782
  export async function verifyAmountsAndAcceptSwapForDifferentSeed(swap: Swap, amount: string) {
782
- await waitFor(DeviceLabels.RECEIVE_ADDRESS_DOES_NOT_BELONG);
783
- await pressUntilTextFound(DeviceLabels.I_UNDERSTAND);
784
- await pressBoth();
783
+ await waitFor(DeviceLabels.REVIEW_TRANSACTION);
785
784
  const events = await pressUntilTextFound(DeviceLabels.SIGN_TRANSACTION);
786
- await verifySwapData(swap, events, amount);
785
+ verifySwapData(swap, events, amount);
787
786
  await pressBoth();
788
787
  }
789
788
 
790
789
  export async function verifyAmountsAndRejectSwap(swap: Swap, amount: string) {
791
790
  await waitFor(DeviceLabels.REVIEW_TRANSACTION);
792
791
  const events = await pressUntilTextFound(DeviceLabels.REJECT);
793
- await verifySwapData(swap, events, amount);
792
+ verifySwapData(swap, events, amount);
794
793
  await pressBoth();
795
794
  }
796
795
 
797
- async function verifySwapData(swap: Swap, events: string[], amount: string) {
798
- const sendAmountScreen = containsSubstringInEvent(amount, events);
799
- expect(sendAmountScreen).toBeTruthy();
800
- verifySwapGetAmountScreen(swap, events);
801
- verifySwapFeesAmountScreen(swap, events);
802
- }
803
-
804
- function verifySwapGetAmountScreen(swap: Swap, events: string[]) {
805
- const parsedAmountToReceive = extractNumberFromString(swap.amountToReceive);
806
- swap.amountToReceive =
807
- parsedAmountToReceive.length < 19
808
- ? parsedAmountToReceive
809
- : parsedAmountToReceive.substring(0, 18);
796
+ function verifySwapData(swap: Swap, events: string[], amount: string) {
797
+ const swapPair = `swap ${swap.getAccountToDebit.currency.ticker} to ${swap.getAccountToCredit.currency.ticker}`;
810
798
 
811
- const receivedGetAmount = containsSubstringInEvent(`${swap.amountToReceive}`, events);
812
- expect(receivedGetAmount).toBeTruthy();
799
+ if (getSpeculosModel() !== DeviceModelId.nanoS) {
800
+ expectDeviceScreenContains(swapPair, events, "Swap pair not found on the device screen");
801
+ }
802
+ expectDeviceScreenContains(amount, events, `Amount ${amount} not found on the device screen`);
813
803
  }
814
804
 
815
- function verifySwapFeesAmountScreen(swap: Swap, events: string[]) {
816
- const parsedFeesAmount = extractNumberFromString(swap.feesAmount);
817
- swap.feesAmount =
818
- parsedFeesAmount.length < 19 ? parsedFeesAmount : parsedFeesAmount.substring(0, 18);
819
-
820
- const receivedFeesAmount = containsSubstringInEvent(swap.feesAmount, events);
821
- expect(receivedFeesAmount).toBeTruthy();
805
+ function expectDeviceScreenContains(substring: string, events: string[], message: string) {
806
+ const found = containsSubstringInEvent(substring, events);
807
+ if (!found) {
808
+ throw new Error(
809
+ `${message}. Expected events to contain "${substring}". Got: ${JSON.stringify(events)}`,
810
+ );
811
+ }
822
812
  }
823
-
824
- const extractNumberFromString = (input: string | undefined): string => {
825
- const match = input?.match(/[\d.]+/);
826
- return match ? match[0] : "";
827
- };
@@ -177,9 +177,9 @@ const DEFAULT_SWAP_PROVIDERS: Record<string, ProviderConfig & Partial<Additional
177
177
  type: "CEX",
178
178
  name: "NEAR Intents",
179
179
  needsBearerToken: false,
180
- termsOfUseUrl: "https://docs.thorswap.finance/thorswap/resources/terms-of-service",
180
+ termsOfUseUrl: "https://swapkit.dev/terms-of-service/",
181
181
  supportUrl: "https://ledgerhelp.swapkit.dev/",
182
- mainUrl: "https://www.thorswap.finance/",
182
+ mainUrl: "https://www.near.org/intents",
183
183
  needsKYC: false,
184
184
  version: 2,
185
185
  publicKey: {
@@ -111,7 +111,7 @@ export const DEFAULT_FEATURES: Features = {
111
111
  ptxServiceCtaExchangeDrawer: DEFAULT_FEATURE,
112
112
  ptxServiceCtaScreens: DEFAULT_FEATURE,
113
113
  ptxSwapReceiveTRC20WithoutTrx: DEFAULT_FEATURE,
114
- ptxSwapconfirmSwapOnDevice: DEFAULT_FEATURE,
114
+ ptxSwapDetailedView: DEFAULT_FEATURE,
115
115
  disableNftLedgerMarket: DEFAULT_FEATURE,
116
116
  disableNftRaribleOpensea: DEFAULT_FEATURE,
117
117
  disableNftSend: DEFAULT_FEATURE,
@@ -670,6 +670,7 @@ export const DEFAULT_FEATURES: Features = {
670
670
  },
671
671
  supportDeviceApex: DEFAULT_FEATURE,
672
672
  llmSyncOnboardingIncr1: DEFAULT_FEATURE,
673
+ noah: DEFAULT_FEATURE,
673
674
  };
674
675
 
675
676
  // Firebase SDK treat JSON values as strings
@@ -0,0 +1,3 @@
1
+ type EntryPoint = "notifications" | "flow";
2
+
3
+ export { EntryPoint };
@@ -0,0 +1,102 @@
1
+ import { filterServiceStatusIncidents } from "./index";
2
+ import { LEDGER_COMPONENTS } from "./ledger-components";
3
+ import type { Incident } from "./types";
4
+
5
+ const makeIncident = (overrides: Partial<Incident> = {}): Incident => ({
6
+ created_at: "2025-01-01T00:00:00Z",
7
+ id: Math.random().toString(16).slice(2),
8
+ impact: "minor",
9
+ incident_updates: [],
10
+ monitoring_at: null,
11
+ name: "Test incident",
12
+ page_id: null,
13
+ resolved_at: null,
14
+ shortlink: null,
15
+ status: "investigating",
16
+ updated_at: null,
17
+ ...overrides,
18
+ });
19
+
20
+ describe("filterServiceStatusIncidents", () => {
21
+ it("returns empty when no tickers or no incidents", () => {
22
+ expect(filterServiceStatusIncidents([], ["BTC"]).length).toBe(0);
23
+ expect(filterServiceStatusIncidents([makeIncident()], []).length).toBe(0);
24
+ });
25
+
26
+ it("matches incidents with known Ledger components (case-insensitive)", () => {
27
+ const incidents: Incident[] = [
28
+ makeIncident({ components: [{ id: "1", name: "Ledger Application Store" }] }),
29
+ makeIncident({ components: [{ id: "2", name: "ledger appliCAtion store" }] }),
30
+ makeIncident({ components: [{ id: "3", name: "Unknown Component" }] }),
31
+ ];
32
+
33
+ const result = filterServiceStatusIncidents(incidents, ["BTC"], "notifications");
34
+ expect(result.map(i => i.components?.[0].id)).toEqual(["1", "2"]);
35
+ });
36
+
37
+ it("matches incidents if a component contains a tracked ticker as a whole word (case-insensitive)", () => {
38
+ const incidents: Incident[] = [
39
+ makeIncident({ components: [{ id: "1", name: "BTC Node" }] }),
40
+ makeIncident({ components: [{ id: "2", name: "eth Node" }] }),
41
+ makeIncident({ components: [{ id: "3", name: "TETHER Service" }] }),
42
+ makeIncident({ components: [{ id: "4", name: "NOTBTCService" }] }),
43
+ ];
44
+
45
+ const result = filterServiceStatusIncidents(incidents, ["BTC", "ETH"]);
46
+ expect(result.map(i => i.components?.[0].id)).toEqual(["1", "2"]);
47
+ });
48
+
49
+ it("includes incidents with no components array or empty array", () => {
50
+ const incidents: Incident[] = [
51
+ makeIncident({ components: undefined }),
52
+ makeIncident({ components: [] }),
53
+ ];
54
+
55
+ const result = filterServiceStatusIncidents(incidents, ["BTC"]);
56
+ expect(result.length).toBe(2);
57
+ });
58
+
59
+ it("escapes tickers so special regex characters do not break matching", () => {
60
+ const incidents: Incident[] = [
61
+ makeIncident({ components: [{ id: "1", name: "USDT Node" }] }),
62
+ makeIncident({ components: [{ id: "2", name: "US.DT Node" }] }),
63
+ makeIncident({ components: [{ id: "3", name: "(USDT) Node" }] }),
64
+ makeIncident({ components: [{ id: "4", name: "Ethereum Node" }] }),
65
+ makeIncident({ components: [{ id: "5", name: "Ethereum (ETH) swap issue" }] }),
66
+ ];
67
+
68
+ const result = filterServiceStatusIncidents(incidents, ["USDT", "ETH"]);
69
+ expect(result.map(i => i.components?.[0].id)).toEqual(["1", "3", "5"]);
70
+ expect(result.length).toBe(3);
71
+ });
72
+
73
+ it("matches incidents for all known Ledger components", () => {
74
+ const incidents: Incident[] = LEDGER_COMPONENTS.map((name, index) =>
75
+ makeIncident({ components: [{ id: `${index + 1}`, name }] }),
76
+ );
77
+
78
+ const result = filterServiceStatusIncidents(incidents, ["BTC"], "notifications");
79
+ expect(result.length).toBe(LEDGER_COMPONENTS.length);
80
+ });
81
+
82
+ it("filters correctly with common tickers", () => {
83
+ const tickers = ["XRP", "SOL", "ETH", "BTC", "ADA", "USDC", "USDT"];
84
+ const incidents: Incident[] = [
85
+ makeIncident({ components: [{ id: "1", name: "BTC Node" }] }),
86
+ makeIncident({ components: [{ id: "2", name: "eth Node" }] }),
87
+ makeIncident({ components: [{ id: "3", name: "ADA-Service" }] }),
88
+ makeIncident({ components: [{ id: "4", name: "usdc gateway" }] }),
89
+ makeIncident({ components: [{ id: "5", name: "NOTUSDTService" }] }),
90
+ makeIncident({ components: [{ id: "6", name: "USDT" }] }),
91
+ makeIncident({ components: [{ id: "7", name: "xrp relayer" }] }),
92
+ makeIncident({ components: [{ id: "8", name: "SOL Node" }] }),
93
+ makeIncident({ components: [{ id: "9", name: "SOLANA Node" }] }),
94
+ makeIncident({ components: [{ id: "10", name: "Random Service" }] }),
95
+ makeIncident({ components: [] }),
96
+ ];
97
+
98
+ const result = filterServiceStatusIncidents(incidents, tickers);
99
+ expect(result.map(i => i.components?.[0]?.id)).toEqual(["1", "2", "3", "4", "6", "7", "8"]);
100
+ expect(result.length).toBe(8);
101
+ });
102
+ });
@@ -4,6 +4,9 @@ import defaultNetworkApi from "./api";
4
4
  import { fromPromise } from "xstate";
5
5
  import { useMachine } from "@xstate/react";
6
6
  import { serviceStatusMachine } from "./machine";
7
+ import { LEDGER_COMPONENTS } from "./ledger-components";
8
+ import { EntryPoint } from "./entry-points";
9
+
7
10
  type Props = {
8
11
  children: React.ReactNode;
9
12
  autoUpdateDelay: number;
@@ -33,21 +36,61 @@ function escapeRegExp(string) {
33
36
  return string.replace(/[.*+?^${}()[\]\\]/g, "\\$&"); // $& means the whole matched string
34
37
  }
35
38
 
39
+ function sanitizeName(name: string): string {
40
+ return name.toLowerCase().trim().replace(/\s+/g, " "); // collapse multiple spaces
41
+ }
42
+
43
+ /**
44
+ * Filters service status incidents based on related tickers or Ledger components.
45
+ *
46
+ * ## Behavior:
47
+ * - If there are no `tickers` or no `incidents`, returns an empty list.
48
+ * - For each incident:
49
+ * 1. ✅ If the incident has no components → keep it (always relevant).
50
+ * 2. ✅ If at least one component name matches a known **Ledger component** → keep it.
51
+ * 3. ✅ If at least one component name contains a **currency ticker** → keep it.
52
+ * 4. ❌ Otherwise, the incident is discarded.
53
+ *
54
+ * @param {Incident[]} [incidents=[]] - List of incidents to filter.
55
+ * @param {string[]} [tickers=[]] - List of currency tickers to match against incident components.
56
+ * @param {EntryPoint} [entryPoint] - Entry point where the incidents are displayed (specifically for Ledger components)
57
+ * @returns {Incident[]} The list of incidents relevant to the provided tickers or Ledger components.
58
+ */
59
+
36
60
  export function filterServiceStatusIncidents(
37
61
  incidents: Incident[],
38
62
  tickers: string[] = [],
63
+ entryPoint?: EntryPoint,
39
64
  ): Incident[] {
40
- if (!tickers || tickers.length === 0 || !incidents || incidents.length === 0) {
41
- return [];
42
- }
43
-
44
- const tickersRegex = new RegExp(escapeRegExp(tickers.join("|")), "i");
45
- return incidents.filter(
46
- ({ components }) =>
47
- !components || // dont filter out if no components
48
- components.length === 0 ||
49
- components.some(({ name }) => tickersRegex.test(name)), // component name should hold currency ticker
50
- );
65
+ if (!incidents.length) return [];
66
+ if (!tickers.length) return [];
67
+
68
+ const tickerRegex = tickers.length
69
+ ? new RegExp(`\\b(${tickers.map(escapeRegExp).join("|")})\\b`, "i")
70
+ : null;
71
+
72
+ const ledgerComponentsSet = new Set(LEDGER_COMPONENTS.map(component => sanitizeName(component)));
73
+
74
+ return incidents.filter(({ components }) => {
75
+ // Keep global incidents with no components
76
+ if (!components?.length) return true;
77
+
78
+ return components.some(({ name }) => {
79
+ const sanitizedName = sanitizeName(name);
80
+
81
+ // Show Ledger components only for Notification Center
82
+ if (entryPoint === "notifications" && ledgerComponentsSet.has(sanitizedName)) {
83
+ return true;
84
+ }
85
+
86
+ // Show coin/ticker-specific components for flows
87
+ if (tickerRegex && tickerRegex.test(sanitizedName)) {
88
+ return true;
89
+ }
90
+
91
+ return false;
92
+ });
93
+ });
51
94
  }
52
95
 
53
96
  // filter out service status incidents by given currencies or fallback on context currencies
@@ -55,8 +98,12 @@ export function useFilteredServiceStatus(filters?: ServiceStatusUserSettings): S
55
98
  const stateData = useContext(ServiceStatusContext);
56
99
  const { incidents, context } = stateData;
57
100
  const filteredIncidents = useMemo(() => {
58
- return filterServiceStatusIncidents(incidents, filters?.tickers || context?.tickers);
59
- }, [incidents, context, filters?.tickers]);
101
+ return filterServiceStatusIncidents(
102
+ incidents,
103
+ filters?.tickers || context?.tickers,
104
+ filters?.entryPoint,
105
+ );
106
+ }, [incidents, filters?.tickers, context?.tickers, filters?.entryPoint]);
60
107
 
61
108
  return { ...stateData, incidents: filteredIncidents };
62
109
  }
@@ -69,6 +116,7 @@ export const ServiceStatusProvider = ({
69
116
  }: Props): ReactElement => {
70
117
  const fetchData = useCallback(async () => {
71
118
  const serviceStatusSummary = await networkApi.fetchStatusSummary();
119
+
72
120
  return {
73
121
  incidents: serviceStatusSummary.incidents,
74
122
  updateTime: Date.now(),
@@ -0,0 +1,13 @@
1
+ export const LEDGER_COMPONENTS = [
2
+ "Ledger Application Store",
3
+ "Ledger Live Security Services",
4
+ "Swap API",
5
+ "Buy",
6
+ "Earn Dashboard",
7
+ "Crypto Assets Service",
8
+ "Cloud Sync",
9
+ "Trustchain",
10
+ "Loyalty Program",
11
+ "Card Program",
12
+ "Countervalue API",
13
+ ] as const;