From d647e74df5e3b83bdb4b2981e965a1d376eef191 Mon Sep 17 00:00:00 2001 From: adammino-ledger Date: Wed, 14 Aug 2024 10:20:21 +0200 Subject: [PATCH 01/54] add param partnerTestBaseUrl to be passed to buy-sell app to allow testing from partners --- .../src/renderer/screens/exchange/index.tsx | 4 ++++ libs/ledger-live-common/src/platform/types.ts | 1 + 2 files changed, 5 insertions(+) diff --git a/apps/ledger-live-desktop/src/renderer/screens/exchange/index.tsx b/apps/ledger-live-desktop/src/renderer/screens/exchange/index.tsx index e2cd295eeb91..732b5d4c5fd8 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/exchange/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/exchange/index.tsx @@ -98,6 +98,10 @@ const LiveAppExchange = ({ appId }: { appId: string }) => { ...customUrlParams, lang: locale, devMode, + ...(manifest?.providerTestBaseUrl && { + providerTestBaseUrl: manifest?.providerTestBaseUrl, + }), + ...Object.fromEntries(searchParams.entries()), }} /> diff --git a/libs/ledger-live-common/src/platform/types.ts b/libs/ledger-live-common/src/platform/types.ts index a8f91b50c1f1..4991aa43084f 100644 --- a/libs/ledger-live-common/src/platform/types.ts +++ b/libs/ledger-live-common/src/platform/types.ts @@ -126,6 +126,7 @@ export type LiveAppManifest = { currencies: string[] | "*"; visibility: Visibility; highlight?: boolean; + providerTestBaseUrl?: string; content: { cta?: TranslatableString; subtitle?: TranslatableString; From f748f4f9eb703d3c5adc20c4c05da902684e5d5e Mon Sep 17 00:00:00 2001 From: adammino-ledger Date: Thu, 15 Aug 2024 15:09:54 +0200 Subject: [PATCH 02/54] accept new variable providerTestBaseUrl just from the local manifest --- .../src/renderer/screens/exchange/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ledger-live-desktop/src/renderer/screens/exchange/index.tsx b/apps/ledger-live-desktop/src/renderer/screens/exchange/index.tsx index 732b5d4c5fd8..1fd7f66e40a6 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/exchange/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/exchange/index.tsx @@ -98,8 +98,8 @@ const LiveAppExchange = ({ appId }: { appId: string }) => { ...customUrlParams, lang: locale, devMode, - ...(manifest?.providerTestBaseUrl && { - providerTestBaseUrl: manifest?.providerTestBaseUrl, + ...(localManifest?.providerTestBaseUrl && { + providerTestBaseUrl: localManifest?.providerTestBaseUrl, }), ...Object.fromEntries(searchParams.entries()), From bf95843c2360ae2ac43ce01813cee215fe30063e Mon Sep 17 00:00:00 2001 From: adammino-ledger Date: Fri, 16 Aug 2024 10:39:36 +0200 Subject: [PATCH 03/54] add support for providerTestBaseUrl for LLM --- apps/ledger-live-mobile/src/screens/PTX/BuyAndSell/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/ledger-live-mobile/src/screens/PTX/BuyAndSell/index.tsx b/apps/ledger-live-mobile/src/screens/PTX/BuyAndSell/index.tsx index 0c469163f066..03809e1d7545 100644 --- a/apps/ledger-live-mobile/src/screens/PTX/BuyAndSell/index.tsx +++ b/apps/ledger-live-mobile/src/screens/PTX/BuyAndSell/index.tsx @@ -97,6 +97,9 @@ export function BuyAndSellScreen({ route }: Props) { theme, lang: locale, devMode, + ...(localManifest?.providerTestBaseUrl && { + providerTestBaseUrl: localManifest?.providerTestBaseUrl, + }), ...customParams, ...Object.fromEntries(searchParams.entries()), }} From 54578c329baf4434f9c5d9accb8842da00e45630 Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Tue, 27 Aug 2024 11:51:45 +0200 Subject: [PATCH 04/54] fix(lld): removal of custom lock screen --- .changeset/twenty-bottles-leave.md | 6 + .../screens/customImage/Step1ChooseImage.tsx | 16 +- .../renderer/screens/customImage/index.tsx | 21 ++- .../CustomImageManagerButton.tsx | 11 +- .../RemoveCustomImage.tsx | 7 +- .../DeviceInformationSummary/index.tsx | 3 + .../screens/manager/DeviceDashboard/index.tsx | 6 +- .../src/hw/customLockScreenRemove.test.ts | 138 +++++++++++++++++- .../src/hw/customLockScreenRemove.ts | 17 ++- 9 files changed, 190 insertions(+), 35 deletions(-) create mode 100644 .changeset/twenty-bottles-leave.md diff --git a/.changeset/twenty-bottles-leave.md b/.changeset/twenty-bottles-leave.md new file mode 100644 index 000000000000..832b60c515fb --- /dev/null +++ b/.changeset/twenty-bottles-leave.md @@ -0,0 +1,6 @@ +--- +"ledger-live-desktop": patch +"@ledgerhq/live-common": patch +--- + +Fix removal of custom lock screen on LLD diff --git a/apps/ledger-live-desktop/src/renderer/screens/customImage/Step1ChooseImage.tsx b/apps/ledger-live-desktop/src/renderer/screens/customImage/Step1ChooseImage.tsx index 4a9352c75c39..924b1db1c5ef 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/customImage/Step1ChooseImage.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/customImage/Step1ChooseImage.tsx @@ -19,10 +19,6 @@ import useIsMounted from "@ledgerhq/live-common/hooks/useIsMounted"; import TrackPage from "~/renderer/analytics/TrackPage"; import { analyticsPageNames, analyticsFlowName } from "./shared"; import { useTrack } from "~/renderer/analytics/segment"; -import { setDrawer } from "~/renderer/drawers/Provider"; -import RemoveCustomImage from "../manager/DeviceDashboard/DeviceInformationSummary/RemoveCustomImage"; -import { useSelector } from "react-redux"; -import { lastSeenCustomImageSelector } from "~/renderer/reducers/settings"; type Props = StepProps & { onResult: (res: ImageBase64Data) => void; @@ -31,6 +27,7 @@ type Props = StepProps & { setIsShowingNftGallery: (_: boolean) => void; loading?: boolean; hasCustomLockScreen?: boolean; + onClickRemoveCustomImage: () => void; }; const defaultMediaTypes = ["original", "big", "preview"]; @@ -56,6 +53,7 @@ const StepChooseImage: React.FC = props => { isShowingNftGallery, setIsShowingNftGallery, hasCustomLockScreen, + onClickRemoveCustomImage, } = props; const isMounted = useIsMounted(); const { t } = useTranslation(); @@ -111,12 +109,6 @@ const StepChooseImage: React.FC = props => { [isMounted, onError, selectedNftId], ); - const lastSeenCustomImage = useSelector(lastSeenCustomImageSelector); - - const onRemove = useCallback(() => { - setDrawer(RemoveCustomImage, {}); - }, []); - return ( = props => { }); }} /> - {hasCustomLockScreen || lastSeenCustomImage?.size ? ( + {hasCustomLockScreen ? ( {t("removeCurrentPicture.cta")} diff --git a/apps/ledger-live-desktop/src/renderer/screens/customImage/index.tsx b/apps/ledger-live-desktop/src/renderer/screens/customImage/index.tsx index 6525372c7128..f2e798b82732 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/customImage/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/customImage/index.tsx @@ -36,6 +36,7 @@ import TrackPage, { setTrackingSource } from "~/renderer/analytics/TrackPage"; import { useTrack } from "~/renderer/analytics/segment"; import DeviceModelPicker from "~/renderer/components/CustomImage/DeviceModelPicker"; import { useCompleteActionCallback } from "~/renderer/components/PostOnboardingHub/logic/useCompleteAction"; +import RemoveCustomImage from "../manager/DeviceDashboard/DeviceInformationSummary/RemoveCustomImage"; type Props = { imageUri?: string; @@ -44,6 +45,7 @@ type Props = { reopenPreviousDrawer?: () => void; deviceModelId: DeviceModelId | null; hasCustomLockScreen?: boolean; + setHasCustomLockScreen?: (value: boolean) => void; }; const orderedSteps: Step[] = [ @@ -62,6 +64,7 @@ const CustomImage: React.FC = props => { reopenPreviousDrawer, isFromPostOnboardingEntryPoint, hasCustomLockScreen, + setHasCustomLockScreen, } = props; const { t } = useTranslation(); const track = useTrack(); @@ -159,8 +162,9 @@ const CustomImage: React.FC = props => { }, []); const handleStepTransferResult = useCallback(() => { + setHasCustomLockScreen && setHasCustomLockScreen(true); setTransferDone(true); - }, []); + }, [setHasCustomLockScreen]); const handleError = useCallback( (step: Step, error: Error) => { @@ -236,6 +240,20 @@ const CustomImage: React.FC = props => { ) : null; + const [isShowingRemoveCustomImage, setIsShowingRemoveCustomImage] = useState(false); + const onClickRemoveCustomImage = useCallback(() => { + setIsShowingRemoveCustomImage(true); + }, []); + + if (isShowingRemoveCustomImage) { + return ( + setDrawer()} + onRemoved={setHasCustomLockScreen ? () => setHasCustomLockScreen(false) : undefined} + /> + ); + } + return ( = props => { isShowingNftGallery={isShowingNftGallery} setIsShowingNftGallery={setIsShowingNftGallery} hasCustomLockScreen={hasCustomLockScreen} + onClickRemoveCustomImage={onClickRemoveCustomImage} /> void; }; const CustomImageManagerButton = (props: Props) => { const { t } = useTranslation(); - const { disabled, deviceModelId, hasCustomLockScreen } = props; + const { disabled, deviceModelId, hasCustomLockScreen, setHasCustomLockScreen } = props; const onAdd = useCallback(() => { - setDrawer(CustomImage, { deviceModelId, hasCustomLockScreen }, { forceDisableFocusTrap: true }); - }, [deviceModelId, hasCustomLockScreen]); + setDrawer( + CustomImage, + { deviceModelId, hasCustomLockScreen, setHasCustomLockScreen }, + { forceDisableFocusTrap: true }, + ); + }, [deviceModelId, hasCustomLockScreen, setHasCustomLockScreen]); return ( diff --git a/apps/ledger-live-desktop/src/renderer/screens/manager/DeviceDashboard/DeviceInformationSummary/RemoveCustomImage.tsx b/apps/ledger-live-desktop/src/renderer/screens/manager/DeviceDashboard/DeviceInformationSummary/RemoveCustomImage.tsx index 89b44134c340..329226f223d5 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/manager/DeviceDashboard/DeviceInformationSummary/RemoveCustomImage.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/manager/DeviceDashboard/DeviceInformationSummary/RemoveCustomImage.tsx @@ -18,9 +18,9 @@ const TextEllipsis = styled.div` text-overflow: ellipsis; `; -type Props = { onClose?: () => void }; +type Props = { onClose: () => void; onRemoved?: () => void }; -const RemoveCustomImage: React.FC = ({ onClose }) => { +const RemoveCustomImage: React.FC = ({ onClose, onRemoved }) => { const request = useMemo(() => ({}), []); const { t } = useTranslation(); const dispatch = useDispatch(); @@ -40,7 +40,8 @@ const RemoveCustomImage: React.FC = ({ onClose }) => { setCompleted(true); setRunning(false); dispatch(clearLastSeenCustomImage()); - }, [dispatch]); + onRemoved && onRemoved(); + }, [dispatch, onRemoved]); const onError = useCallback( (error: Error) => { diff --git a/apps/ledger-live-desktop/src/renderer/screens/manager/DeviceDashboard/DeviceInformationSummary/index.tsx b/apps/ledger-live-desktop/src/renderer/screens/manager/DeviceDashboard/DeviceInformationSummary/index.tsx index 01be4ee6da71..343809b4eee2 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/manager/DeviceDashboard/DeviceInformationSummary/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/manager/DeviceDashboard/DeviceInformationSummary/index.tsx @@ -62,6 +62,7 @@ type Props = { installQueue: string[]; uninstallQueue: string[]; hasCustomLockScreen: boolean; + setHasCustomLockScreen: (value: boolean) => void; }; /** @@ -81,6 +82,7 @@ const DeviceInformationSummary = ({ installQueue, uninstallQueue, hasCustomLockScreen, + setHasCustomLockScreen, }: Props) => { const navigationLocked = useSelector(isNavigationLocked); @@ -167,6 +169,7 @@ const DeviceInformationSummary = ({ disabled={navigationLocked} deviceModelId={deviceModel.id} hasCustomLockScreen={hasCustomLockScreen} + setHasCustomLockScreen={setHasCustomLockScreen} /> ) : null} diff --git a/apps/ledger-live-desktop/src/renderer/screens/manager/DeviceDashboard/index.tsx b/apps/ledger-live-desktop/src/renderer/screens/manager/DeviceDashboard/index.tsx index 35b5e8eb8353..c43ed4eb79bd 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/manager/DeviceDashboard/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/manager/DeviceDashboard/index.tsx @@ -172,6 +172,9 @@ const DeviceDashboard = ({ }, [reduxDispatch, result.customImageBlocks]); const disableFirmwareUpdate = state.installQueue.length > 0 || state.uninstallQueue.length > 0; + + const [hasCustomLockScreen, setHasCustomLockScreen] = useState(result.customImageBlocks !== 0); + return ( <> {renderFirmwareUpdateBanner @@ -212,7 +215,8 @@ const DeviceDashboard = ({ device={device} deviceName={deviceName} isIncomplete={isIncomplete} - hasCustomLockScreen={result.customImageBlocks !== 0} + hasCustomLockScreen={hasCustomLockScreen} + setHasCustomLockScreen={setHasCustomLockScreen} /> from(job(transport))); +} + +jest.mock("./getDeviceInfo"); +const mockedGetDeviceInfo = jest.mocked(getDeviceInfo); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore next-line const mockTransportGenerator = out => ({ send: () => out }) as Transport; describe("customLockScreenRemove", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + test("should succeed if user approves", async () => { const mockedTransport: Transport = mockTransportGenerator( Buffer.from(StatusCodes.OK.toString(16), "hex"), ); - await expect(customLockScreenRemove(mockedTransport)).resolves.toBeUndefined(); + await expect(command(mockedTransport)).resolves.toBeUndefined(); }); test("should fail with correct error if user refuses", async () => { const mockedTransport: Transport = mockTransportGenerator( Buffer.from(StatusCodes.USER_REFUSED_ON_DEVICE.toString(16), "hex"), ); - await expect(customLockScreenRemove(mockedTransport)).rejects.toThrow(UserRefusedOnDevice); + await expect(command(mockedTransport)).rejects.toThrow(UserRefusedOnDevice); }); test("should throw if user refuses", async () => { const mockedTransport: Transport = mockTransportGenerator( Buffer.from(StatusCodes.USER_REFUSED_ON_DEVICE.toString(16), "hex"), ); - await expect(customLockScreenRemove(mockedTransport)).rejects.toThrow(Error); + await expect(command(mockedTransport)).rejects.toThrow(Error); }); test("missing image, should throw", async () => { const mockedTransport = mockTransportGenerator( Buffer.from(StatusCodes.CUSTOM_IMAGE_EMPTY.toString(16), "hex"), ); - await expect(customLockScreenRemove(mockedTransport)).rejects.toThrow( - ImageDoesNotExistOnDevice, - ); + await expect(command(mockedTransport)).rejects.toThrow(ImageDoesNotExistOnDevice); }); test("unexpected bootloader or any other code, should throw", async () => { const mockedTransport = mockTransportGenerator( Buffer.from(StatusCodes.DEVICE_IN_RECOVERY_MODE.toString(16), "hex"), ); - await expect(customLockScreenRemove(mockedTransport)).rejects.toThrow(UnexpectedBootloader); + await expect(command(mockedTransport)).rejects.toThrow(UnexpectedBootloader); + }); +}); + +describe("removeImage deviceAction", () => { + beforeEach(() => { + jest.clearAllMocks(); + mockWithDevice(new Transport()); + }); + + it('should emit an "unresponsiveDevice" event if getDeviceInfo takes too long', done => { + mockedGetDeviceInfo.mockImplementation(() => new Promise(() => {})); + + removeImage({ deviceId: "deviceId", request: {} }).subscribe({ + next: event => { + if (!event) { + done(new Error("unexpected undefined event")); + } + const { type } = event; + if (type === "unresponsiveDevice") { + done(); + } else { + done(new Error("unexpected event")); + } + }, + error: err => { + done(err); // it should not error + }, + }); + }); + + it("should error if getDeviceInfo fails", done => { + mockedGetDeviceInfo.mockRejectedValue(new Error("failed")); + + removeImage({ deviceId: "deviceId", request: {} }).subscribe({ + next: () => { + done(new Error("unexpected event")); + }, + error: err => { + try { + expect(err).toMatchObject(new Error("failed")); + done(); + } catch (e) { + done(e); + } + }, + }); + }); + + it("should complete with the correct events in case of success", done => { + mockedGetDeviceInfo.mockResolvedValue({} as DeviceInfo); + mockWithDevice(mockTransportGenerator(Buffer.from(StatusCodes.OK.toString(16), "hex"))); + + const expectedEventTypes = ["removeImagePermissionRequested", "imageRemoved"]; + const observedEventTypes: string[] = []; + + removeImage({ deviceId: "deviceId", request: {} }).subscribe({ + next: event => { + if (!event) { + done(new Error("unexpected undefined event")); + } + const { type } = event; + observedEventTypes.push(type); + }, + complete: () => { + try { + expect(observedEventTypes).toEqual(expectedEventTypes); + done(); + } catch (e) { + done(e); + } + }, + error: err => { + done(err); // it should not error + }, + }); + }); + + it("should complete with the correct events in case of failure", done => { + mockedGetDeviceInfo.mockResolvedValue({} as DeviceInfo); + mockWithDevice( + mockTransportGenerator(Buffer.from(StatusCodes.USER_REFUSED_ON_DEVICE.toString(16), "hex")), + ); + + const expectedEventTypes = ["removeImagePermissionRequested"]; + const observedEventTypes: string[] = []; + + removeImage({ deviceId: "deviceId", request: {} }).subscribe({ + next: event => { + if (!event) { + done(new Error("unexpected undefined event")); + } + const { type } = event; + observedEventTypes.push(type); + }, + complete: () => { + done(new Error("unexpected completion")); + }, + error: err => { + try { + expect(observedEventTypes).toEqual(expectedEventTypes); + expect(err).toMatchObject(new UserRefusedOnDevice()); + done(); + } catch (e) { + done(e); // it should not error + } + }, + }); }); }); diff --git a/libs/ledger-live-common/src/hw/customLockScreenRemove.ts b/libs/ledger-live-common/src/hw/customLockScreenRemove.ts index c395b5f06582..784dae1736c8 100644 --- a/libs/ledger-live-common/src/hw/customLockScreenRemove.ts +++ b/libs/ledger-live-common/src/hw/customLockScreenRemove.ts @@ -5,9 +5,9 @@ import { UserRefusedOnDevice, } from "@ledgerhq/errors"; import Transport from "@ledgerhq/hw-transport"; -import { Observable, from, of } from "rxjs"; +import { Observable, from, of, throwError } from "rxjs"; import { withDevice } from "./deviceAccess"; -import { delay, mergeMap } from "rxjs/operators"; +import { catchError, delay, mergeMap } from "rxjs/operators"; import getDeviceInfo from "./getDeviceInfo"; import { ImageDoesNotExistOnDevice } from "../errors"; @@ -57,9 +57,9 @@ export const command = async (transport: Transport): Promise => { }; export default function removeImage({ deviceId }: Input): Observable { - const sub = withDevice(deviceId)( + return withDevice(deviceId)( transport => - new Observable(subscriber => { + new Observable(subscriber => { const timeoutSub = of({ type: "unresponsiveDevice", }) @@ -73,9 +73,14 @@ export default function removeImage({ deviceId }: Input): Observable { + subscriber.error(e); + return throwError(() => e); }), ) - .subscribe(subscriber); + .subscribe(); return () => { timeoutSub.unsubscribe(); @@ -83,6 +88,4 @@ export default function removeImage({ deviceId }: Input): Observable; } From 1d1bfd164847431c0f4afe7ed8ae6d5df535c9cf Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Wed, 28 Aug 2024 11:13:14 +0200 Subject: [PATCH 05/54] fix(listApps): fallback if listAppsApdu not supported in old firmware --- .changeset/thick-fans-give.md | 5 + .../src/apps/listApps.test.ts | 95 +++++++++++++++++-- libs/ledger-live-common/src/apps/listApps.ts | 47 ++++++--- .../src/hw/connectManager.ts | 2 +- 4 files changed, 131 insertions(+), 18 deletions(-) create mode 100644 .changeset/thick-fans-give.md diff --git a/.changeset/thick-fans-give.md b/.changeset/thick-fans-give.md new file mode 100644 index 000000000000..1998923e39f1 --- /dev/null +++ b/.changeset/thick-fans-give.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/live-common": patch +--- + +Fix listApps: fallback to HSM script runner if listApps APDU is not available diff --git a/libs/ledger-live-common/src/apps/listApps.test.ts b/libs/ledger-live-common/src/apps/listApps.test.ts index e7cba45ff85a..c64baf88eb30 100644 --- a/libs/ledger-live-common/src/apps/listApps.test.ts +++ b/libs/ledger-live-common/src/apps/listApps.test.ts @@ -1,8 +1,8 @@ import { from } from "rxjs"; -import { UnexpectedBootloader } from "@ledgerhq/errors"; +import { StatusCodes, TransportStatusError, UnexpectedBootloader } from "@ledgerhq/errors"; import { aTransportBuilder } from "@ledgerhq/hw-transport-mocker"; import { listApps } from "./listApps"; -import ManagerAPI from "../manager/api"; +import ManagerAPI, { ListInstalledAppsEvent } from "../manager/api"; import { aDeviceInfoBuilder } from "../mock/fixtures/aDeviceInfo"; import { ManagerApiRepository, @@ -25,10 +25,15 @@ const mockedGetDeviceName = jest.mocked(getDeviceName); const mockedListCryptoCurrencies = jest.mocked(listCryptoCurrencies); const mockedCurrenciesByMarketCap = jest.mocked(currenciesByMarketcap); +const mockedListInstalledAppEvent: ListInstalledAppsEvent = { + type: "result", + payload: [], +}; + describe("listApps", () => { let mockedManagerApiRepository: ManagerApiRepository; let listAppsCommandSpy: jest.SpyInstance; - let listInstalledAppsSpy: jest.SpyInstance; + let listAppsWithManagerApiSpy: jest.SpyInstance; beforeEach(() => { jest @@ -46,7 +51,9 @@ describe("listApps", () => { .spyOn(jest.requireActual("../hw/listApps"), "default") .mockReturnValue(Promise.resolve([])); - listInstalledAppsSpy = jest.spyOn(ManagerAPI, "listInstalledApps").mockReturnValue(from([])); + listAppsWithManagerApiSpy = jest + .spyOn(ManagerAPI, "listInstalledApps") + .mockReturnValue(from([mockedListInstalledAppEvent])); }); afterEach(() => { @@ -150,7 +157,83 @@ describe("listApps", () => { jest.advanceTimersByTime(1); expect(listAppsCommandSpy).toHaveBeenCalled(); - expect(listInstalledAppsSpy).not.toHaveBeenCalled(); + expect(listAppsWithManagerApiSpy).not.toHaveBeenCalled(); + }); + + [ + StatusCodes.CLA_NOT_SUPPORTED, + StatusCodes.INS_NOT_SUPPORTED, + StatusCodes.UNKNOWN_APDU, + 0x6e01, + 0x6d01, + ].forEach(statusCode => { + it(`should call ManagerAPI.listInstalledApps() if deviceInfo.managerAllowed is true but list apps APDU returns 0x${statusCode.toString(16)}`, done => { + const transport = aTransportBuilder(); + const deviceInfo = aDeviceInfoBuilder({ + isOSU: false, + isBootloader: false, + managerAllowed: true, + targetId: 0x33200000, + }); + + listAppsCommandSpy.mockRejectedValue(new TransportStatusError(statusCode)); + + listApps({ + managerDevModeEnabled: false, + transport, + deviceInfo, + managerApiRepository: mockedManagerApiRepository, + forceProvider: 1, + }).subscribe({ + complete: () => { + try { + expect(listAppsCommandSpy).toHaveBeenCalled(); + expect(listAppsWithManagerApiSpy).toHaveBeenCalled(); + done(); + } catch (e) { + done(e); + } + }, + error: e => { + done(e); + }, + }); + jest.advanceTimersByTime(1); + }); + }); + + it("should return an observable that errors if listApps() throws an error that is not a TransportStatusError", done => { + const transport = aTransportBuilder(); + const deviceInfo = aDeviceInfoBuilder({ + isOSU: false, + isBootloader: false, + managerAllowed: true, + targetId: 0x33200000, + }); + + listAppsCommandSpy.mockRejectedValue(new Error("listApps failed")); + + listApps({ + managerDevModeEnabled: false, + transport, + deviceInfo, + managerApiRepository: mockedManagerApiRepository, + forceProvider: 1, + }).subscribe({ + error: err => { + try { + expect(err).toEqual(new Error("listApps failed")); + done(); + } catch (e) { + done(e); + } + }, + complete: () => { + done("this observable should not complete"); + }, + }); + + jest.advanceTimersByTime(1); }); it("should call ManagerAPI.listInstalledApps() if deviceInfo.managerAllowed is false", () => { @@ -172,7 +255,7 @@ describe("listApps", () => { jest.advanceTimersByTime(1); expect(listAppsCommandSpy).not.toHaveBeenCalled(); - expect(listInstalledAppsSpy).toHaveBeenCalled(); + expect(listAppsWithManagerApiSpy).toHaveBeenCalled(); }); it("should return an observable that errors if getDeviceVersion() throws", done => { diff --git a/libs/ledger-live-common/src/apps/listApps.ts b/libs/ledger-live-common/src/apps/listApps.ts index a3b59a3299f0..33bfa2d2193c 100644 --- a/libs/ledger-live-common/src/apps/listApps.ts +++ b/libs/ledger-live-common/src/apps/listApps.ts @@ -1,6 +1,6 @@ import Transport from "@ledgerhq/hw-transport"; import { DeviceModelId, getDeviceModel, identifyTargetId } from "@ledgerhq/devices"; -import { UnexpectedBootloader } from "@ledgerhq/errors"; +import { StatusCodes, TransportStatusError, UnexpectedBootloader } from "@ledgerhq/errors"; import { Observable, throwError, Subscription } from "rxjs"; import { App, DeviceInfo, idsToLanguage, languageIds } from "@ledgerhq/types-live"; import { LocalTracer } from "@ledgerhq/logs"; @@ -72,17 +72,13 @@ export const listApps = ({ */ let listAppsResponsePromise: Promise; - if (deviceInfo.managerAllowed) { - // If the user has already allowed a secure channel during this session we can directly - // ask the device for the installed applications instead of going through a scriptrunner, - // this is a performance optimization, part of a larger rework with Manager API v2. - tracer.trace("Using direct apdu listapps"); - listAppsResponsePromise = hwListApps(transport); - } else { - // Fallback to original web-socket list apps - tracer.trace("Using scriptrunner listapps"); - listAppsResponsePromise = new Promise((resolve, reject) => { + function listAppsWithSingleCommand(): Promise { + return hwListApps(transport); + } + + function listAppsWithManagerApi(): Promise { + return new Promise((resolve, reject) => { // TODO: migrate this ManagerAPI call to ManagerApiRepository sub = ManagerAPI.listInstalledApps(transport, { targetId: deviceInfo.targetId, @@ -104,6 +100,35 @@ export const listApps = ({ }); } + if (deviceInfo.managerAllowed) { + // If the user has already allowed a secure channel during this session we can directly + // ask the device for the installed applications instead of going through a scriptrunner, + // this is a performance optimization, part of a larger rework with Manager API v2. + tracer.trace("Using direct apdu listapps"); + listAppsResponsePromise = listAppsWithSingleCommand().catch(e => { + // For some old versions of the firmware, the listapps command is not supported. + // In this case, we fallback to the scriptrunner listapps. + if ( + e instanceof TransportStatusError && + [ + StatusCodes.CLA_NOT_SUPPORTED, + StatusCodes.INS_NOT_SUPPORTED, + StatusCodes.UNKNOWN_APDU, + 0x6e01, // No StatusCodes definition + 0x6d01, // No StatusCodes definition + ].includes(e.statusCode) + ) { + tracer.trace("Fallback to scriptrunner listapps"); + return listAppsWithManagerApi(); + } + throw e; + }); + } else { + // Fallback to original web-socket list apps + tracer.trace("Using scriptrunner listapps"); + listAppsResponsePromise = listAppsWithManagerApi(); + } + const filteredListAppsPromise = listAppsResponsePromise.then(result => { // Empty HashData can come from apps that are not real apps (such as language packs) // or custom applications that have been sideloaded. diff --git a/libs/ledger-live-common/src/hw/connectManager.ts b/libs/ledger-live-common/src/hw/connectManager.ts index 9aef0203796e..9aa4406b8c5e 100644 --- a/libs/ledger-live-common/src/hw/connectManager.ts +++ b/libs/ledger-live-common/src/hw/connectManager.ts @@ -93,9 +93,9 @@ const cmd = ({ deviceId, request }: Input): Observable => [ StatusCodes.CLA_NOT_SUPPORTED, StatusCodes.INS_NOT_SUPPORTED, + StatusCodes.UNKNOWN_APDU, 0x6e01, // No StatusCodes definition 0x6d01, // No StatusCodes definition - 0x6d02, // No StatusCodes definition ].includes(e.statusCode)) ) { return from(getAppAndVersion(transport)).pipe( From 6f865fd52119a017ea20fd8f51b6e30ebf00fe68 Mon Sep 17 00:00:00 2001 From: Thomas Brillard Date: Wed, 28 Aug 2024 16:29:24 +0200 Subject: [PATCH 06/54] =?UTF-8?q?fix:=20uses=20correct=20status=20for=20fe?= =?UTF-8?q?e=20drawers=20and=20disable=20button=20if=20any=20er=E2=80=A6?= =?UTF-8?q?=20(#7694)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: uses correct status for fee drawers and disable button if any error is present --- .changeset/fifty-eels-breathe.md | 5 +++++ .../screens/exchange/Swap2/Form/FeesDrawerLiveApp/index.tsx | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/fifty-eels-breathe.md diff --git a/.changeset/fifty-eels-breathe.md b/.changeset/fifty-eels-breathe.md new file mode 100644 index 000000000000..14f7a6cdb002 --- /dev/null +++ b/.changeset/fifty-eels-breathe.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +fix: uses correct status for fee drawers and disable button if any error is present diff --git a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/FeesDrawerLiveApp/index.tsx b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/FeesDrawerLiveApp/index.tsx index 3e3d0bd5af0e..b941d98cfa73 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/FeesDrawerLiveApp/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/FeesDrawerLiveApp/index.tsx @@ -102,7 +102,7 @@ export default function FeesDrawerLiveApp({ ); diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/BottomBar.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/BottomBar.tsx index e09a05bf44cb..e952d4d68ef3 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/BottomBar.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/BottomBar.tsx @@ -104,8 +104,8 @@ export function BottomBar({ return ( - - + + {shouldDisplaySelectAccount ? ( - - ) : null} + + + + ) : ( + + )} diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/SelectAccountButton.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/SelectAccountButton.tsx index 9a2568fa4dfc..f545e04c3a8b 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/SelectAccountButton.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/SelectAccountButton.tsx @@ -1,6 +1,6 @@ import React from "react"; import { Trans } from "react-i18next"; -import { Flex, Text } from "@ledgerhq/native-ui"; +import { Text } from "@ledgerhq/native-ui"; import { AppManifest } from "@ledgerhq/live-common/wallet-api/types"; import { CurrentAccountHistDB } from "@ledgerhq/live-common/wallet-api/react"; import { useDappCurrentAccount } from "@ledgerhq/live-common/wallet-api/useDappLogic"; @@ -25,13 +25,9 @@ export default function SelectAccountButton({ const currentAccountName = useMaybeAccountName(currentAccount); return ( - ); From fb301bf4ab9f029bc8ed50c2dab873ef172f9cb9 Mon Sep 17 00:00:00 2001 From: Kant Date: Thu, 22 Aug 2024 19:47:52 +0200 Subject: [PATCH 12/54] perf: improve perfs by using the networks instead of listing currencies to match --- .../src/components/Web3AppWebview/helpers.ts | 14 ++++++++++---- .../WebPlatformPlayer/SelectAccountButton.tsx | 5 +---- .../components/Web3Player/SelectAccountButton.tsx | 5 +---- libs/ledger-live-common/src/wallet-api/react.ts | 10 ---------- 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/apps/ledger-live-mobile/src/components/Web3AppWebview/helpers.ts b/apps/ledger-live-mobile/src/components/Web3AppWebview/helpers.ts index f215b846313a..726ea9482b55 100644 --- a/apps/ledger-live-mobile/src/components/Web3AppWebview/helpers.ts +++ b/apps/ledger-live-mobile/src/components/Web3AppWebview/helpers.ts @@ -7,7 +7,6 @@ import { useConfig, useWalletAPIServer, CurrentAccountHistDB, - useManifestCurrencies, } from "@ledgerhq/live-common/wallet-api/react"; import { useDappCurrentAccount, useDappLogic } from "@ledgerhq/live-common/wallet-api/useDappLogic"; import { Operation, SignedOperation } from "@ledgerhq/types-live"; @@ -36,6 +35,7 @@ import { currentRouteNameRef } from "../../analytics/screenRefs"; import { walletSelector } from "~/reducers/wallet"; import { WebViewOpenWindowEvent } from "react-native-webview/lib/WebViewTypes"; import { Linking } from "react-native"; +import { getCryptoCurrencyById } from "@ledgerhq/live-common/currencies/index"; export function useWebView( { @@ -528,8 +528,14 @@ export function useSelectAccount({ manifest: AppManifest; currentAccountHistDb?: CurrentAccountHistDB; }) { - const currencies = useManifestCurrencies(manifest); - const { setCurrentAccountHist } = useDappCurrentAccount(currentAccountHistDb); + const currencies = useMemo(() => { + return ( + manifest.dapp?.networks.map(network => { + return getCryptoCurrencyById(network.currency); + }) ?? [] + ); + }, [manifest.dapp?.networks]); + const { setCurrentAccountHist, currentAccount } = useDappCurrentAccount(currentAccountHistDb); const navigation = useNavigation(); const onSelectAccount = useCallback(() => { @@ -558,5 +564,5 @@ export function useSelectAccount({ } }, [manifest.id, currencies, navigation, setCurrentAccountHist]); - return { onSelectAccount }; + return { onSelectAccount, currentAccount }; } diff --git a/apps/ledger-live-mobile/src/components/WebPlatformPlayer/SelectAccountButton.tsx b/apps/ledger-live-mobile/src/components/WebPlatformPlayer/SelectAccountButton.tsx index f545e04c3a8b..5244e936605d 100644 --- a/apps/ledger-live-mobile/src/components/WebPlatformPlayer/SelectAccountButton.tsx +++ b/apps/ledger-live-mobile/src/components/WebPlatformPlayer/SelectAccountButton.tsx @@ -3,7 +3,6 @@ import { Trans } from "react-i18next"; import { Text } from "@ledgerhq/native-ui"; import { AppManifest } from "@ledgerhq/live-common/wallet-api/types"; import { CurrentAccountHistDB } from "@ledgerhq/live-common/wallet-api/react"; -import { useDappCurrentAccount } from "@ledgerhq/live-common/wallet-api/useDappLogic"; import Button from "~/components/Button"; import CircleCurrencyIcon from "~/components/CircleCurrencyIcon"; import { useSelectAccount } from "~/components/Web3AppWebview/helpers"; @@ -18,9 +17,7 @@ export default function SelectAccountButton({ manifest, currentAccountHistDb, }: SelectAccountButtonProps) { - const { currentAccount } = useDappCurrentAccount(currentAccountHistDb); - - const { onSelectAccount } = useSelectAccount({ manifest, currentAccountHistDb }); + const { onSelectAccount, currentAccount } = useSelectAccount({ manifest, currentAccountHistDb }); const currentAccountName = useMaybeAccountName(currentAccount); diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/SelectAccountButton.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/SelectAccountButton.tsx index f545e04c3a8b..5244e936605d 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/SelectAccountButton.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/SelectAccountButton.tsx @@ -3,7 +3,6 @@ import { Trans } from "react-i18next"; import { Text } from "@ledgerhq/native-ui"; import { AppManifest } from "@ledgerhq/live-common/wallet-api/types"; import { CurrentAccountHistDB } from "@ledgerhq/live-common/wallet-api/react"; -import { useDappCurrentAccount } from "@ledgerhq/live-common/wallet-api/useDappLogic"; import Button from "~/components/Button"; import CircleCurrencyIcon from "~/components/CircleCurrencyIcon"; import { useSelectAccount } from "~/components/Web3AppWebview/helpers"; @@ -18,9 +17,7 @@ export default function SelectAccountButton({ manifest, currentAccountHistDb, }: SelectAccountButtonProps) { - const { currentAccount } = useDappCurrentAccount(currentAccountHistDb); - - const { onSelectAccount } = useSelectAccount({ manifest, currentAccountHistDb }); + const { onSelectAccount, currentAccount } = useSelectAccount({ manifest, currentAccountHistDb }); const currentAccountName = useMaybeAccountName(currentAccount); diff --git a/libs/ledger-live-common/src/wallet-api/react.ts b/libs/ledger-live-common/src/wallet-api/react.ts index 907a26c85a57..1374555b166b 100644 --- a/libs/ledger-live-common/src/wallet-api/react.ts +++ b/libs/ledger-live-common/src/wallet-api/react.ts @@ -77,16 +77,6 @@ export function useWalletAPICurrencies(): WalletAPICurrency[] { }, []); } -export function useManifestCurrencies(manifest: AppManifest) { - return useMemo(() => { - const allCurrenciesAndTokens = listCurrencies(true); - - return manifest.currencies === "*" - ? allCurrenciesAndTokens - : matchCurrencies(allCurrenciesAndTokens, manifest.currencies); - }, [manifest.currencies]); -} - export function useGetAccountIds( accounts$: Observable | undefined, ): Map | undefined { From d99ddcf029c81877cfe1b2c9d73eb3ade8c90494 Mon Sep 17 00:00:00 2001 From: Kant Date: Thu, 22 Aug 2024 19:49:11 +0200 Subject: [PATCH 13/54] fix: BottomBar animation height calculation to allow different height than the header --- .../Web3HubApp/components/Web3Player/BottomBar.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/BottomBar.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/BottomBar.tsx index e952d4d68ef3..f0acc4e4b855 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/BottomBar.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/BottomBar.tsx @@ -13,9 +13,9 @@ import { ArrowLeftMedium, ArrowRightMedium, ReverseMedium } from "@ledgerhq/nati import { safeGetRefValue, CurrentAccountHistDB } from "@ledgerhq/live-common/wallet-api/react"; import { WebviewAPI, WebviewState } from "~/components/Web3AppWebview/types"; import SelectAccountButton from "./SelectAccountButton"; +import { TOTAL_HEADER_HEIGHT } from "../Header"; -const BAR_HEIGHT = 60; -export const TOTAL_HEADER_HEIGHT = BAR_HEIGHT; +const BAR_HEIGHT = 64; const ANIMATION_HEIGHT = TOTAL_HEADER_HEIGHT; const LAYOUT_RANGE = [0, ANIMATION_HEIGHT]; @@ -82,7 +82,7 @@ export function BottomBar({ const headerHeight = interpolate( layoutY.value, LAYOUT_RANGE, - [TOTAL_HEADER_HEIGHT, TOTAL_HEADER_HEIGHT - ANIMATION_HEIGHT], + [BAR_HEIGHT, BAR_HEIGHT - ANIMATION_HEIGHT], Extrapolation.CLAMP, ); @@ -96,8 +96,8 @@ export function BottomBar({ if (!layoutY) return {}; return { - height: TOTAL_HEADER_HEIGHT, - opacity: interpolate(layoutY.value, [0, ANIMATION_HEIGHT], [1, 0], Extrapolation.CLAMP), + height: BAR_HEIGHT, + opacity: interpolate(layoutY.value, LAYOUT_RANGE, [1, 0], Extrapolation.CLAMP), }; }); @@ -122,7 +122,7 @@ export function BottomBar({ {shouldDisplaySelectAccount ? ( - + Date: Thu, 22 Aug 2024 20:11:50 +0200 Subject: [PATCH 14/54] fix: ManifestItem label placement and style --- .../ManifestItem/Label/index.tsx | 28 ++++++++----------- .../ManifestsList/ManifestItem/index.tsx | 18 ++++++------ 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/Label/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/Label/index.tsx index 785961381358..b54d4a09edf4 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/Label/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/Label/index.tsx @@ -1,38 +1,34 @@ -import React from "react"; +import React, { ComponentProps } from "react"; import { Text } from "@ledgerhq/native-ui"; -type ItemStyle = { - badgeColor: string; - borderColor: string; - backgroundColor: string; -}; - type Props = { text: string; - style: ItemStyle; + style: Pick< + ComponentProps, + "color" | "borderColor" | "backgroundColor" | "borderWidth" + >; }; const Label: React.FC = ({ text, style }) => { - const { badgeColor, borderColor, backgroundColor } = style; + const { color, borderColor, backgroundColor, borderWidth = 1 } = style; return ( {text} diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/index.tsx index 20d2bc24b505..479d7131fb97 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/index.tsx @@ -86,30 +86,28 @@ export default function ManifestItem({ - + - + {manifest.name} - + {manifest.branch !== "stable" && ( ); diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Error/Simple.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Error/Simple.tsx index 0e0820a280fb..aba928734e22 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Error/Simple.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Error/Simple.tsx @@ -3,7 +3,7 @@ import React from "react"; import styled, { useTheme } from "styled-components/native"; type Props = { title: string; - desc: string; + desc?: string; mainButton: { label: string; onPress: () => void; @@ -22,9 +22,11 @@ export function ErrorComponent({ title, desc, mainButton }: Props) { {title} - - {desc} - + {desc && ( + + {desc} + + )} - + {!ledgerSyncFF?.enabled && ( + + + + )} - {!ledgerSyncFF?.enabled && } + Date: Tue, 3 Sep 2024 15:01:47 +0200 Subject: [PATCH 54/54] [FIX]: Remove Trustchain from error and handle it in generic way (#7725) --- .changeset/empty-pumas-argue.md | 6 ++++++ apps/ledger-live-desktop/static/i18n/en/app.json | 7 +++++++ apps/ledger-live-mobile/src/locales/en/common.json | 9 +++++++-- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 .changeset/empty-pumas-argue.md diff --git a/.changeset/empty-pumas-argue.md b/.changeset/empty-pumas-argue.md new file mode 100644 index 000000000000..e6305e4373bc --- /dev/null +++ b/.changeset/empty-pumas-argue.md @@ -0,0 +1,6 @@ +--- +"ledger-live-desktop": patch +"live-mobile": patch +--- + +Update errors in trad diff --git a/apps/ledger-live-desktop/static/i18n/en/app.json b/apps/ledger-live-desktop/static/i18n/en/app.json index 02c9b7b4986e..1dceee90663b 100644 --- a/apps/ledger-live-desktop/static/i18n/en/app.json +++ b/apps/ledger-live-desktop/static/i18n/en/app.json @@ -5695,6 +5695,13 @@ "title": "Something went wrong. Please reconnect your device", "description": "{{message}}" }, + "TrustchainEjected": { + "title": "Not Synced anymore" + }, + "TrustchainNotAllowed": { + "title": "You are not in sync anymore", + "description": "Please try again or contact Ledger Support." + }, "TrustchainNotFound": { "title": "Something went wrong while fetching your data", "description": "Please try again or contact Ledger Support." diff --git a/apps/ledger-live-mobile/src/locales/en/common.json b/apps/ledger-live-mobile/src/locales/en/common.json index f6acbdcb4724..915c2710e410 100644 --- a/apps/ledger-live-mobile/src/locales/en/common.json +++ b/apps/ledger-live-mobile/src/locales/en/common.json @@ -946,6 +946,13 @@ "description": "The server could not handle your request. Please try again later or contact Ledger Support.", "errorCode": "Code: {{errorCode}}" }, + "TrustchainEjected": { + "title": "Not Synced anymore" + }, + "TrustchainNotAllowed": { + "title": "You are not in sync anymore", + "description": "Please try again or contact Ledger Support." + }, "TrustchainNotFound": { "title": "Something went wrong while fetching your data", "description": "Please try again or contact Ledger Support." @@ -6818,8 +6825,6 @@ }, "errors": { "fetching": "Something went wrong while fetching your synchronized instances.", - "trustchain": "Unexpected falsy trustchain", - "memberCredentials": "Unexpected falsy member credentials", "ledgerSyncUnavailable": "Ledger Sync is currently unavailable. This doesn’t have any impact on your assets." } },