From ba0305569edf42b71bba2793bc7b74f3c65e089e Mon Sep 17 00:00:00 2001 From: Abdurrahman SASTIM Date: Mon, 7 Oct 2024 10:15:49 +0200 Subject: [PATCH 01/86] test: improve Speculos Detox support for remote capabilities --- apps/ledger-live-mobile/e2e/bridge/proxy.ts | 35 +++++++++------- apps/ledger-live-mobile/e2e/helpers.ts | 40 ++++++++++--------- .../e2e/page/common.page.ts | 17 +++++--- .../e2e/specs/speculos/addAccount.spec.ts | 6 +-- apps/ledger-live-mobile/package.json | 1 + .../src/SpeculosHttpTransport.ts | 2 +- pnpm-lock.yaml | 3 ++ 7 files changed, 61 insertions(+), 43 deletions(-) diff --git a/apps/ledger-live-mobile/e2e/bridge/proxy.ts b/apps/ledger-live-mobile/e2e/bridge/proxy.ts index 90d03c498b84..3651a078a066 100644 --- a/apps/ledger-live-mobile/e2e/bridge/proxy.ts +++ b/apps/ledger-live-mobile/e2e/bridge/proxy.ts @@ -13,27 +13,31 @@ import SpeculosHttpTransport, { } from "@ledgerhq/hw-transport-node-speculos-http"; import { retry } from "@ledgerhq/live-common/promise"; import { Buffer } from "buffer"; -import { findFreePort } from "./server"; import { getEnv } from "@ledgerhq/live-env"; import invariant from "invariant"; -const proxySubscriptions: [string, Subscription][] = []; +const proxySubscriptions: [number, Subscription][] = []; let transport: TransportModule; interface ProxyOptions { device: string; - port: string; + port: number; silent?: boolean; verbose?: boolean; - speculosApiPort: string; + speculosApiPort: number; + speculosUrl: string; } -export async function startProxy(proxyPort?: string, speculosApiPort?: string): Promise { - if (!proxyPort) proxyPort = (await findFreePort()).toString(); - if (!speculosApiPort) speculosApiPort = getEnv("SPECULOS_API_PORT").toString(); +export async function startProxy( + proxyPort: number, + speculosAddress?: string, + speculosApiPort?: number, +): Promise { + if (!speculosApiPort) speculosApiPort = getEnv("SPECULOS_API_PORT"); + if (!speculosAddress) speculosAddress = "localhost"; + invariant(speculosApiPort, "E2E Proxy : speculosApiPort is not defined"); - invariant(proxyPort, "E2E Proxy : proxyPort is not defined"); return new Promise((resolve, reject) => { const options: ProxyOptions = { @@ -41,6 +45,7 @@ export async function startProxy(proxyPort?: string, speculosApiPort?: string): port: proxyPort, silent: true, verbose: false, + speculosUrl: `http://${speculosAddress}`, speculosApiPort, }; @@ -68,29 +73,31 @@ export async function startProxy(proxyPort?: string, speculosApiPort?: string): }); } -export function closeProxy(proxyPort?: string) { +export function closeProxy(proxyPort?: number) { if (!proxyPort) { for (const [, subscription] of proxySubscriptions) { subscription.unsubscribe(); } return; } - const proxySubscription = proxySubscriptions.find(([string]) => string === proxyPort)?.[1]; + const proxySubscription = proxySubscriptions.find(([port]) => port === proxyPort)?.[1]; if (proxySubscription) { proxySubscription.unsubscribe(); proxySubscriptions.splice(proxySubscriptions.indexOf([proxyPort, proxySubscription])); } } -const job = ({ device, port, silent, verbose, speculosApiPort }: ProxyOptions) => +const job = ({ device, port, silent, verbose, speculosUrl, speculosApiPort }: ProxyOptions) => new Observable(observer => { const req: SpeculosHttpTransportOpts = { - apiPort: speculosApiPort, + apiPort: speculosApiPort.toString(), + baseURL: speculosUrl, }; transport = { id: `speculos-http-${speculosApiPort}`, - open: id => (id.includes(port) ? retry(() => SpeculosHttpTransport.open(req)) : null), + open: id => + id.includes(port.toString()) ? retry(() => SpeculosHttpTransport.open(req)) : null, disconnect: () => Promise.resolve(), }; registerTransportModule(transport); @@ -251,7 +258,7 @@ const job = ({ device, port, silent, verbose, speculosApiPort }: ProxyOptions) = const proxyUrls = ["localhost", ...ips].map(ip => `ws://${ip}:${port || "8435"}`); proxyUrls.forEach(url => log("proxy", `DEVICE_PROXY_URL=${url}`)); - server.listen(port || "8435", () => { + server.listen(port, () => { log("proxy", `\nNano S proxy started on ${ips[0]}\n`); observer.next(ips); }); diff --git a/apps/ledger-live-mobile/e2e/helpers.ts b/apps/ledger-live-mobile/e2e/helpers.ts index c429dd585b94..e1efccd2d6cd 100644 --- a/apps/ledger-live-mobile/e2e/helpers.ts +++ b/apps/ledger-live-mobile/e2e/helpers.ts @@ -3,7 +3,7 @@ import { Direction } from "detox/detox"; import { findFreePort, close as closeBridge, init as initBridge } from "./bridge/server"; import { startSpeculos, stopSpeculos, specs } from "@ledgerhq/live-common/e2e/speculos"; -import { SpeculosDevice } from "../../../libs/speculos-transport/lib"; +import { SpeculosDevice } from "@ledgerhq/speculos-transport"; import invariant from "invariant"; import { setEnv } from "@ledgerhq/live-env"; import { startProxy, closeProxy } from "./bridge/proxy"; @@ -24,7 +24,7 @@ export const accountIdParam = "?accountId="; const BASE_PORT = 30000; const MAX_PORT = 65535; let portCounter = BASE_PORT; // Counter for generating unique ports -const speculosDevices: [string, SpeculosDevice][] = []; +const speculosDevices: [number, SpeculosDevice][] = []; export async function waitForElementById(id: string | RegExp, timeout: number = DEFAULT_TIMEOUT) { return await waitFor(getElementById(id)).toBeVisible().withTimeout(timeout); @@ -164,16 +164,15 @@ export async function launchApp() { return port; } -export async function launchSpeculos(appName: string) { +export async function launchSpeculos(appName: string, proxyPort: number) { // Ensure the portCounter stays within the valid port range if (portCounter > MAX_PORT) { portCounter = BASE_PORT; } const speculosPort = portCounter++; - setEnv( - "SPECULOS_PID_OFFSET", - (speculosPort - BASE_PORT) * 1000 + parseInt(process.env.TEST_WORKER_INDEX || "0") * 100, - ); + const speculosPidOffset = + (speculosPort - BASE_PORT) * 1000 + parseInt(process.env.TEST_WORKER_INDEX || "0") * 100; + setEnv("SPECULOS_PID_OFFSET", speculosPidOffset); const testName = expect.getState().testPath || "unknown"; const speculosDevice = await startSpeculos(testName, specs[appName]); @@ -182,30 +181,33 @@ export async function launchSpeculos(appName: string) { const speculosApiPort = speculosDevice.ports.apiPort; invariant(speculosApiPort, "[E2E Setup] speculosApiPort not defined"); setEnv("SPECULOS_API_PORT", speculosApiPort); + speculosDevices.push([proxyPort, speculosDevice]); + console.warn(`Speculos started on ${proxyPort}`); + return speculosApiPort; +} - const proxyPort = await findFreePort(); +export async function launchProxy( + proxyPort: number, + speculosAddress?: string, + speculosPort?: number, +) { await device.reverseTcpPort(proxyPort); - await startProxy(proxyPort.toString()); - const proxyAddress = `localhost:${proxyPort}`; - speculosDevices.push([proxyAddress, speculosDevice]); - console.warn(`Speculos started on ${proxyAddress}`); - return proxyAddress; + await startProxy(proxyPort, speculosAddress, speculosPort); } -export async function deleteSpeculos(deviceAddress?: string) { - if (!deviceAddress) { +export async function deleteSpeculos(proxyPort?: number) { + if (!proxyPort) { for (const [address] of speculosDevices) { await deleteSpeculos(address); } return; } - const proxyPort = deviceAddress.match(/:(\d+)$/)?.[1]; if (proxyPort) closeProxy(proxyPort); - const speculosDevice = speculosDevices.find(([string]) => string === deviceAddress)?.[1]; + const speculosDevice = speculosDevices.find(([number]) => number === proxyPort)?.[1]; if (speculosDevice) { await stopSpeculos(speculosDevice); - speculosDevices.splice(speculosDevices.indexOf([deviceAddress, speculosDevice])); - console.warn(`Speculos stopped on ${deviceAddress}`); + speculosDevices.splice(speculosDevices.indexOf([proxyPort, speculosDevice])); + console.warn(`Speculos stopped on ${proxyPort}`); } setEnv("SPECULOS_API_PORT", 0); } diff --git a/apps/ledger-live-mobile/e2e/page/common.page.ts b/apps/ledger-live-mobile/e2e/page/common.page.ts index 8b5332ac727e..f9593ac177e9 100644 --- a/apps/ledger-live-mobile/e2e/page/common.page.ts +++ b/apps/ledger-live-mobile/e2e/page/common.page.ts @@ -1,6 +1,7 @@ import { DeviceUSB, ModelId, getUSBDevice, knownDevices } from "../models/devices"; import { getElementById, + launchProxy, scrollToId, tapByElement, tapById, @@ -12,6 +13,7 @@ import DeviceAction from "../models/DeviceAction"; import * as bridge from "../bridge/server"; import { launchSpeculos, deleteSpeculos } from "../helpers"; +const proxyAddress = "localhost"; export default class CommonPage { searchBarId = "common-search-field"; @@ -73,13 +75,16 @@ export default class CommonPage { } async addSpeculos(nanoApp: string) { - const proxyAddress = await launchSpeculos(nanoApp); - await bridge.addKnownSpeculos(proxyAddress); - return proxyAddress; + const proxyPort = await bridge.findFreePort(); + const speculosAddress = "localhost"; + const speculosPort = await launchSpeculos(nanoApp, proxyPort); + await launchProxy(proxyPort, speculosAddress, speculosPort); + await bridge.addKnownSpeculos(`${proxyAddress}:${proxyPort}`); + return proxyPort; } - async removeSpeculos(proxyAddress: string) { - await deleteSpeculos(proxyAddress); - await bridge.removeKnownSpeculos(proxyAddress); + async removeSpeculos(proxyPort: number) { + await deleteSpeculos(proxyPort); + await bridge.removeKnownSpeculos(`${proxyAddress}:${proxyPort}`); } } diff --git a/apps/ledger-live-mobile/e2e/specs/speculos/addAccount.spec.ts b/apps/ledger-live-mobile/e2e/specs/speculos/addAccount.spec.ts index 19a9e38f3902..4465e86e6826 100644 --- a/apps/ledger-live-mobile/e2e/specs/speculos/addAccount.spec.ts +++ b/apps/ledger-live-mobile/e2e/specs/speculos/addAccount.spec.ts @@ -14,14 +14,14 @@ describe("Add accounts", () => { }); currencies.forEach(({ currency, nanoApp, tmsLink }) => { - let deviceName: string; + let deviceNumber: number; $TmsLink(tmsLink); it(`${currency}: add accounts`, async () => { await app.addAccount.openViaDeeplink(); await app.addAccount.selectCurrency(currency); - deviceName = await app.common.addSpeculos(nanoApp); + deviceNumber = await app.common.addSpeculos(nanoApp); await app.addAccount.startAccountsDiscovery(); await app.addAccount.expectAccountDiscovery(currency, 1); @@ -32,7 +32,7 @@ describe("Add accounts", () => { }); afterEach(async () => { - await app.common.removeSpeculos(deviceName); + await app.common.removeSpeculos(deviceNumber); }); }); }); diff --git a/apps/ledger-live-mobile/package.json b/apps/ledger-live-mobile/package.json index b6b2c06153cd..9387b80afa9c 100644 --- a/apps/ledger-live-mobile/package.json +++ b/apps/ledger-live-mobile/package.json @@ -238,6 +238,7 @@ "@babel/runtime": "^7.24.1", "@jest/reporters": "^29.7.0", "@ledgerhq/hw-transport-node-speculos-http": "workspace:^", + "@ledgerhq/speculos-transport": "workspace:^", "@ledgerhq/test-utils": "workspace:^", "@react-native-community/cli": "12.3.6", "@react-native-community/cli-platform-android": "12.3.6", diff --git a/libs/ledgerjs/packages/hw-transport-node-speculos-http/src/SpeculosHttpTransport.ts b/libs/ledgerjs/packages/hw-transport-node-speculos-http/src/SpeculosHttpTransport.ts index e45475d312b1..63ea7dfbd618 100644 --- a/libs/ledgerjs/packages/hw-transport-node-speculos-http/src/SpeculosHttpTransport.ts +++ b/libs/ledgerjs/packages/hw-transport-node-speculos-http/src/SpeculosHttpTransport.ts @@ -53,7 +53,7 @@ export default class SpeculosHttpTransport extends Transport { static open = (opts: SpeculosHttpTransportOpts): Promise => new Promise((resolve, reject) => { const instance = axios.create({ - baseURL: `http://localhost:${opts.apiPort || "5000"}`, + baseURL: `${opts.baseURL || "http://localhost"}:${opts.apiPort || "5000"}`, timeout: opts.timeout, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5245111bb38d..1228b5fd9c8f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1293,6 +1293,9 @@ importers: '@ledgerhq/hw-transport-node-speculos-http': specifier: workspace:^ version: link:../../libs/ledgerjs/packages/hw-transport-node-speculos-http + '@ledgerhq/speculos-transport': + specifier: workspace:^ + version: link:../../libs/speculos-transport '@ledgerhq/test-utils': specifier: workspace:^ version: link:../../libs/test-utils From 81cd7735a3c94628b6d4825b736ef12a0b74d3a3 Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Wed, 2 Oct 2024 17:14:01 +0200 Subject: [PATCH 02/86] feat(lld/llm): add support to new device language packs --- .changeset/big-apples-smash.md | 7 +++++++ .../src/config/languages.ts | 8 ++++---- .../static/i18n/en/app.json | 8 ++++++-- ...r-language-installation-complete-linux.png | Bin 15541 -> 14354 bytes apps/ledger-live-mobile/src/languages.ts | 4 ++++ .../src/locales/en/common.json | 10 +++++++--- .../packages/types-live/src/languages.ts | 17 ++++++++++++++++- 7 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 .changeset/big-apples-smash.md diff --git a/.changeset/big-apples-smash.md b/.changeset/big-apples-smash.md new file mode 100644 index 000000000000..5178f87228b9 --- /dev/null +++ b/.changeset/big-apples-smash.md @@ -0,0 +1,7 @@ +--- +"@ledgerhq/types-live": patch +"ledger-live-desktop": patch +"live-mobile": patch +--- + +Add support to new language packs: de, pt-br, ru, tr diff --git a/apps/ledger-live-desktop/src/config/languages.ts b/apps/ledger-live-desktop/src/config/languages.ts index 32927c2d5b8f..6771b860ed5b 100644 --- a/apps/ledger-live-desktop/src/config/languages.ts +++ b/apps/ledger-live-desktop/src/config/languages.ts @@ -92,7 +92,7 @@ export const Languages = { label: "Deutsch", locales: buildLocales(LanguageLocaleIds.de), - deviceSupport: undefined, + deviceSupport: { label: "german", id: languageIds.german }, }, ja: { id: "ja", @@ -113,21 +113,21 @@ export const Languages = { label: "Português (Brasil)", locales: buildLocales(LanguageLocaleIds.pt), - deviceSupport: undefined, + deviceSupport: { label: "brazilian", id: languageIds.brazilian }, }, ru: { id: "ru", label: "Русский", locales: buildLocales(LanguageLocaleIds.ru), - deviceSupport: undefined, + deviceSupport: { label: "russian", id: languageIds.russian }, }, tr: { id: "tr", label: "Türkçe", locales: buildLocales(LanguageLocaleIds.tr), - deviceSupport: undefined, + deviceSupport: { label: "turkish", id: languageIds.turkish }, }, zh: { id: "zh", diff --git a/apps/ledger-live-desktop/static/i18n/en/app.json b/apps/ledger-live-desktop/static/i18n/en/app.json index 33906a23c6a8..9d36c2d28a50 100644 --- a/apps/ledger-live-desktop/static/i18n/en/app.json +++ b/apps/ledger-live-desktop/static/i18n/en/app.json @@ -2409,8 +2409,12 @@ "installingLanguage": "Installing language", "languages": { "english": "English", - "french": "French", - "spanish": "Spanish" + "french": "Français", + "spanish": "Español", + "german": "Deutsch", + "brazilian": "Português (Br)", + "russian": "Русский", + "turkish": "Türkçe" } }, "distribution": { diff --git a/apps/ledger-live-desktop/tests/specs/manager/devicelocalization.spec.ts-snapshots/manager-language-installation-complete-linux.png b/apps/ledger-live-desktop/tests/specs/manager/devicelocalization.spec.ts-snapshots/manager-language-installation-complete-linux.png index c4f98e70e21e3b26a17dd8766301c68ae87aea7f..65ed29f137aa5167c70409950c6432eba5f0cf0d 100644 GIT binary patch literal 14354 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 literal 15541 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 z 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 Date: Tue, 8 Oct 2024 12:47:18 +0200 Subject: [PATCH 03/86] fix(lld/firmwareupdate): "new language available" missing buttons --- .../UpdateStepFooterWrapper.tsx | 21 ++++++++++ .../helpers/createFirmwareUpdateSteps.ts | 9 +++-- .../modals/UpdateFirmwareModal/index.tsx | 19 +-------- .../steps/00-step-reset-device.tsx | 13 ++++-- .../steps/02-step-restore.tsx | 10 +++++ .../steps/03-step-confirmation.tsx | 9 +++-- .../steps/restore/Language.tsx | 40 ++++++++++++++++++- 7 files changed, 92 insertions(+), 29 deletions(-) create mode 100644 apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/UpdateStepFooterWrapper.tsx diff --git a/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/UpdateStepFooterWrapper.tsx b/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/UpdateStepFooterWrapper.tsx new file mode 100644 index 000000000000..736226ebd827 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/UpdateStepFooterWrapper.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import { Flex, Divider } from "@ledgerhq/react-ui"; + +export const UpdateStepFooterWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => { + return ( + + + + {children} + + + ); +}; diff --git a/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/helpers/createFirmwareUpdateSteps.ts b/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/helpers/createFirmwareUpdateSteps.ts index 882af5c2fd44..273b0fc53ef0 100644 --- a/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/helpers/createFirmwareUpdateSteps.ts +++ b/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/helpers/createFirmwareUpdateSteps.ts @@ -1,11 +1,11 @@ import { FirmwareUpdateContext } from "@ledgerhq/types-live"; import { DeviceModelId } from "@ledgerhq/devices"; -import StepResetDevice, { StepResetFooter } from "../steps/00-step-reset-device"; +import StepResetDevice from "../steps/00-step-reset-device"; import StepPrepare from "../steps/01-step-prepare"; import StepFlashMcu from "../steps/02-step-flash-mcu"; import StepRestore from "../steps/02-step-restore"; import StepUpdating from "../steps/02-step-updating"; -import StepConfirmation, { StepConfirmFooter } from "../steps/03-step-confirmation"; +import StepConfirmation from "../steps/03-step-confirmation"; import { Step, StepId, STEPS } from "../types"; import { isDeviceLocalizationSupported } from "@ledgerhq/live-common/device/use-cases/isDeviceLocalizationSupported"; import { isCustomLockScreenSupported } from "@ledgerhq/live-common/device/use-cases/isCustomLockScreenSupported"; @@ -49,7 +49,7 @@ export const createFirmwareUpdateSteps = ({ id: STEPS.RESET_DEVICE, label: "manager.modal.steps.reset", component: StepResetDevice, - footer: StepResetFooter, + footer: StepResetDevice.Footer, }; const prepareStep: Step = { @@ -78,13 +78,14 @@ export const createFirmwareUpdateSteps = ({ id: STEPS.RESTORE, label: restoreStepLabel, component: StepRestore, + footer: StepRestore.Footer, }; const finalStep: Step = { id: STEPS.FINISH, label: hasRestoreStep ? restoreStepLabel : installUpdateLabel, component: StepConfirmation, - footer: StepConfirmFooter, + footer: StepConfirmation.Footer, }; const steps: Step[] = []; diff --git a/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/index.tsx b/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/index.tsx index 507f5729a559..e772007b1cf8 100644 --- a/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/index.tsx @@ -6,7 +6,7 @@ import { DeviceInfo, FirmwareUpdateContext } from "@ledgerhq/types-live"; import { withV3StyleProvider } from "~/renderer/styles/StyleProviderV3"; import { hasFinalFirmware } from "@ledgerhq/live-common/hw/hasFinalFirmware"; import logger from "~/renderer/logger"; -import { Divider, Flex, FlowStepper, Text } from "@ledgerhq/react-ui"; +import { Flex, FlowStepper, Text } from "@ledgerhq/react-ui"; import Disclaimer from "./Disclaimer"; import Cancel from "./errors/Cancel"; import DeviceCancel from "./errors/DeviceError"; @@ -199,22 +199,7 @@ const UpdateModal = ({ > - {step.footer ? ( - - - - - - - ) : null} + {step.footer ? : null} ))} diff --git a/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/steps/00-step-reset-device.tsx b/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/steps/00-step-reset-device.tsx index cdb508c8746b..4a78a1a55686 100644 --- a/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/steps/00-step-reset-device.tsx +++ b/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/steps/00-step-reset-device.tsx @@ -12,6 +12,7 @@ import TrackPage from "~/renderer/analytics/TrackPage"; import Box from "~/renderer/components/Box"; import Text from "~/renderer/components/Text"; import Button from "~/renderer/components/Button"; +import { UpdateStepFooterWrapper } from "../UpdateStepFooterWrapper"; import { StepProps } from "../types"; const Container = styled(Box).attrs(() => ({ @@ -102,12 +103,16 @@ const StepResetDevice = ({ deviceModelId }: StepProps) => { ); }; -export function StepResetFooter({ transitionTo }: StepProps) { +function Footer({ transitionTo }: StepProps) { return ( - + + + ); } +StepResetDevice.Footer = Footer; + export default StepResetDevice; diff --git a/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/steps/02-step-restore.tsx b/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/steps/02-step-restore.tsx index 58d7ae176938..8e2782a89409 100644 --- a/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/steps/02-step-restore.tsx +++ b/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/steps/02-step-restore.tsx @@ -87,4 +87,14 @@ const StepRestore = ({ ); }; +const Footer: React.FC = props => { + const { completedRestoreSteps } = props; + if (!completedRestoreSteps.includes("language")) { + return ; + } + return null; +}; + +StepRestore.Footer = Footer; + export default StepRestore; diff --git a/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/steps/03-step-confirmation.tsx b/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/steps/03-step-confirmation.tsx index 8a85f4e41d9f..c4633c84fedd 100644 --- a/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/steps/03-step-confirmation.tsx +++ b/apps/ledger-live-desktop/src/renderer/modals/UpdateFirmwareModal/steps/03-step-confirmation.tsx @@ -8,6 +8,7 @@ import { getDeviceModel } from "@ledgerhq/devices"; import Box from "~/renderer/components/Box"; import { StepProps } from "../types"; import { BoxedIcon, Button, IconsLegacy } from "@ledgerhq/react-ui"; +import { UpdateStepFooterWrapper } from "../UpdateStepFooterWrapper"; const Container = styled(Box).attrs(() => ({ alignItems: "center", @@ -64,7 +65,7 @@ const StepConfirmation = ({ deviceModelId, appsToBeReinstalled }: StepProps) => ); }; -export const StepConfirmFooter = ({ +const Footer = ({ onDrawerClose, appsToBeReinstalled, finalStepSuccessButtonLabel, @@ -73,7 +74,7 @@ export const StepConfirmFooter = ({ const { t } = useTranslation(); return ( - <> + + + + ); +}; + +Language.Footer = LanguageFooter; + export default Language; From f644636cf1f744e8aecff5b75d3aa1b7b4caf865 Mon Sep 17 00:00:00 2001 From: qperrot Date: Fri, 4 Oct 2024 16:02:10 +0200 Subject: [PATCH 04/86] fix: Update Metis explorer through Firebase to support CORs --- .changeset/early-seas-love.md | 5 +++++ libs/ledger-live-common/src/families/evm/config.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/early-seas-love.md diff --git a/.changeset/early-seas-love.md b/.changeset/early-seas-love.md new file mode 100644 index 000000000000..7e767aa2e03c --- /dev/null +++ b/.changeset/early-seas-love.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/live-common": patch +--- + +Update Metis explorer through Firebase to support CORs diff --git a/libs/ledger-live-common/src/families/evm/config.ts b/libs/ledger-live-common/src/families/evm/config.ts index f287d10fa976..51bedee004cc 100644 --- a/libs/ledger-live-common/src/families/evm/config.ts +++ b/libs/ledger-live-common/src/families/evm/config.ts @@ -455,7 +455,7 @@ const evmConfig: CurrencyLiveConfigDefinition = { }, explorer: { type: "blockscout", - uri: "https://andromeda-explorer.metis.io/api", + uri: "https://api.routescan.io/v2/network/mainnet/evm/1088/etherscan/api", }, }, }, From 378deaf14d24bb308e5936aa0d854017d92b0fe0 Mon Sep 17 00:00:00 2001 From: Angus Bayley Date: Wed, 2 Oct 2024 16:55:35 +0100 Subject: [PATCH 05/86] build: create new app for prerelease and direct prerelease builds and uploads to it --- .../fastlane/.env.ios.prerelease | 7 +- .../fastlane/.env.ios.release | 1 + .../fastlane/.env.ios.staging | 1 + apps/ledger-live-mobile/fastlane/Fastfile | 44 +++++-- .../project.pbxproj | 119 ++++++++++++++++++ 5 files changed, 159 insertions(+), 13 deletions(-) diff --git a/apps/ledger-live-mobile/fastlane/.env.ios.prerelease b/apps/ledger-live-mobile/fastlane/.env.ios.prerelease index 9b481a8de5cf..41635827c95a 100644 --- a/apps/ledger-live-mobile/fastlane/.env.ios.prerelease +++ b/apps/ledger-live-mobile/fastlane/.env.ios.prerelease @@ -1,6 +1,7 @@ ENVFILE=.env.ios.prerelease -APP_IDENTIFIER="com.ledger.live" -MY_APP_BUNDLE_ID="com.ledger.live" -APP_NAME="Ledger Live" +APP_IDENTIFIER="com.ledger.live.prerelease" +MY_APP_BUNDLE_ID="com.ledger.live.prerelease" +APP_NAME="LL PRERELEASE" +APP_CONFIGURATION="Prerelease" SENTRY_ENVIRONMENT=prerelease SENTRY_PROJECT=llm-ios-prerelease diff --git a/apps/ledger-live-mobile/fastlane/.env.ios.release b/apps/ledger-live-mobile/fastlane/.env.ios.release index 964797ff5f14..f6f44efecf34 100644 --- a/apps/ledger-live-mobile/fastlane/.env.ios.release +++ b/apps/ledger-live-mobile/fastlane/.env.ios.release @@ -2,5 +2,6 @@ ENVFILE=.env.ios.release APP_IDENTIFIER="com.ledger.live" MY_APP_BUNDLE_ID="com.ledger.live" APP_NAME="Ledger Live" +APP_CONFIGURATION="Release" SENTRY_ENVIRONMENT=release SENTRY_PROJECT=llm-ios diff --git a/apps/ledger-live-mobile/fastlane/.env.ios.staging b/apps/ledger-live-mobile/fastlane/.env.ios.staging index 433fc389a34c..c92d40c7debe 100644 --- a/apps/ledger-live-mobile/fastlane/.env.ios.staging +++ b/apps/ledger-live-mobile/fastlane/.env.ios.staging @@ -2,5 +2,6 @@ ENVFILE=.env.ios.staging APP_IDENTIFIER="com.ledger.live.dev" MY_APP_BUNDLE_ID="com.ledger.live.dev" APP_NAME="LL [STAGING]" +APP_CONFIGURATION="Release" SENTRY_ENVIRONMENT=staging SENTRY_PROJECT=llm-ios-staging diff --git a/apps/ledger-live-mobile/fastlane/Fastfile b/apps/ledger-live-mobile/fastlane/Fastfile index ff8650476a88..cac05fec9976 100644 --- a/apps/ledger-live-mobile/fastlane/Fastfile +++ b/apps/ledger-live-mobile/fastlane/Fastfile @@ -142,6 +142,18 @@ platform :ios do keychain_password: ENV["CI_KEYCHAIN_PASSWORD"], git_basic_authorization: Base64.strict_encode64("#{ENV["GIT_REPO_USER"]}:#{ENV["GH_TOKEN"]}"), ) + match( + type: "appstore", + app_identifier: ["com.ledger.live.prerelease"], + force: true, + generate_apple_certs: true, + git_url: ENV["GIT_REPO_URL"], + username: ENV["APPLE_ID"], + team_id: ENV["DEVELOPER_TEAM_ID"], + keychain_name: ENV["CI_KEYCHAIN_NAME"], + keychain_password: ENV["CI_KEYCHAIN_PASSWORD"], + git_basic_authorization: Base64.strict_encode64("#{ENV["GIT_REPO_USER"]}:#{ENV["GH_TOKEN"]}"), + ) end desc "buid for deployment (app-store or ad-hoc)" @@ -163,10 +175,9 @@ platform :ios do build_number = latest_testflight_build_number( version: trim_version_number(package["version"]), - # we are moving to using ENV["APP_IDENTIFIER"] but staging + prerelease still override their own - # app identifier to publish to com.ledger.live . This will be removed once we are are fully publishing - # to separate apps - app_identifier: options[:nightly] ? ENV["APP_IDENTIFIER"] : "com.ledger.live" + # we are moving to using ENV["APP_IDENTIFIER"] but staging still overrides its own app identifier to + # publish to com.ledger.live . This will be removed once we are are fully publishing to separate apps + app_identifier: !options[:adhoc] ? ENV["APP_IDENTIFIER"] : "com.ledger.live" ) increment_build_number({ @@ -260,7 +271,7 @@ platform :ios do workspace: XCODE_WORKSPACE, # This should come from env files - as we move each release type to its own app we will # move them away from "Release" and to their own .env-file defined configuration - configuration: options[:nightly] ? ENV["APP_CONFIGURATION"] : "Release", + configuration: !options[:adhoc] ? ENV["APP_CONFIGURATION"] : "Release", silent: true, xcargs: `#{settings_to_override} -UseNewBuildSystem=YES`, output_directory: OUTPUT_DIRECTORY, @@ -312,6 +323,19 @@ platform :ios do UI.important("Another build is already in external beta review. Skipping external beta review submission") end + elsif (options[:prerelease]) + pilot( + skip_submission: false, + app_identifier: ENV["APP_IDENTIFIER"], + skip_waiting_for_build_processing: true, + ipa: IPA_DIRECTORY, + changelog: "Prerelease Build v#{package['version']} (#{build_number})", + beta_app_review_info: { + contact_email: "team-live@ledger.fr", + contact_first_name: "Ledger Live", + notes: "test prerelease builds" + } + ) else pilot( skip_submission: true, @@ -338,16 +362,16 @@ platform :ios do lane :ci_testflight do |options| setup_ios_ci build(ci: true) - upload(ci: true) + upload( + prerelease: true, + ci: true + ) end desc "ci: create nightly version" lane :ci_nightly do |options| setup_ios_ci - build( - nightly: true, - ci: true - ) + build(ci: true) upload( nightly: true, ci: true diff --git a/apps/ledger-live-mobile/ios/ledgerlivemobile.xcodeproj/project.pbxproj b/apps/ledger-live-mobile/ios/ledgerlivemobile.xcodeproj/project.pbxproj index f03a5c5a6601..08ad426c053f 100644 --- a/apps/ledger-live-mobile/ios/ledgerlivemobile.xcodeproj/project.pbxproj +++ b/apps/ledger-live-mobile/ios/ledgerlivemobile.xcodeproj/project.pbxproj @@ -1132,6 +1132,123 @@ }; name = Nightly; }; + BB2FFDA12CAD600D00AB17D8 /* Prerelease */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + DEVELOPMENT_TEAM = 5HK2Q4J4X4; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION, + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_OTHER_PREPROCESSOR_FLAGS = "-traditional"; + INFOPLIST_PREPROCESS = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.4; + LIBRARY_SEARCH_PATHS = ( + "$(SDKROOT)/usr/lib/swift", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "$(inherited) "; + OTHER_CPLUSPLUSFLAGS = "$(inherited) "; + OTHER_LDFLAGS = "$(inherited)"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_VERSION = 5.0; + USE_HERMES = true; + VALIDATE_PRODUCT = YES; + }; + name = Prerelease; + }; + BB2FFDA22CAD600D00AB17D8 /* Prerelease */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F77638C1F4BC132FB97FEEAF /* Pods-ledgerlivemobile.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = ledgerlivemobile/ledgerlivemobile.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 381; + DEVELOPMENT_TEAM = X6LFS5BQKN; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(PROJECT_DIR)", + "$(PROJECT_DIR)/ledgerlivemobile", + "$(SRCROOT)", + "$(inherited)", + ); + HEADER_SEARCH_PATHS = "$(inherited)"; + INFOPLIST_FILE = ledgerlivemobile/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + /usr/lib/swift, + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(SDKROOT)/usr/lib/swift", + "$(inherited)", + ); + OTHER_CFLAGS = "$(inherited)"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; + PRODUCT_BUNDLE_IDENTIFIER = com.ledger.live.prerelease; + PRODUCT_NAME = ledgerlivemobile; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "ledgerlivemobile-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VALID_ARCHS = "$(ARCHS_STANDARD)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Prerelease; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1141,6 +1258,7 @@ 13B07F941A680F5B00A75B9A /* Debug */, 13B07F951A680F5B00A75B9A /* Release */, BB0B80022CAAB47600AF2B0C /* Nightly */, + BB2FFDA22CAD600D00AB17D8 /* Prerelease */, 76138D0A243CBC8E00264435 /* Staging */, ); defaultConfigurationIsVisible = 0; @@ -1152,6 +1270,7 @@ 83CBBA201A601CBA00E9B192 /* Debug */, 83CBBA211A601CBA00E9B192 /* Release */, BB0B80012CAAB47600AF2B0C /* Nightly */, + BB2FFDA12CAD600D00AB17D8 /* Prerelease */, 76138D09243CBC8E00264435 /* Staging */, ); defaultConfigurationIsVisible = 0; From ee2be7078051afeda3bebe7e50bc3d932291b169 Mon Sep 17 00:00:00 2001 From: Angus Bayley Date: Wed, 2 Oct 2024 18:05:21 +0100 Subject: [PATCH 06/86] build: new icon set for prerelease app --- .../project.pbxproj | 2 +- .../Contents.json | 116 ++++++++++++++++++ .../Icon-App-20x20@2x.png | Bin 0 -> 1008 bytes .../Icon-App-20x20@3x.png | Bin 0 -> 1566 bytes .../Icon-App-29x29@2x.png | Bin 0 -> 1510 bytes .../Icon-App-29x29@3x.png | Bin 0 -> 2433 bytes .../Icon-App-40x40@2x.png | Bin 0 -> 2207 bytes .../Icon-App-40x40@3x.png | Bin 0 -> 3132 bytes .../Icon-App-57x57@2x 1.png | Bin 0 -> 1488 bytes .../Icon-App-60x60@2x 1.png | Bin 0 -> 3132 bytes .../Icon-App-60x60@3x.png | Bin 0 -> 4965 bytes .../Icon-App-76x76@1x.png | Bin 0 -> 1964 bytes .../Icon-App-76x76@2x.png | Bin 0 -> 4368 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 4838 bytes .../ItunesArtwork@2x.png | Bin 0 -> 12684 bytes 15 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Contents.json create mode 100644 apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-57x57@2x 1.png create mode 100644 apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-60x60@2x 1.png create mode 100644 apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/ItunesArtwork@2x.png diff --git a/apps/ledger-live-mobile/ios/ledgerlivemobile.xcodeproj/project.pbxproj b/apps/ledger-live-mobile/ios/ledgerlivemobile.xcodeproj/project.pbxproj index 08ad426c053f..9f36eb0580c3 100644 --- a/apps/ledger-live-mobile/ios/ledgerlivemobile.xcodeproj/project.pbxproj +++ b/apps/ledger-live-mobile/ios/ledgerlivemobile.xcodeproj/project.pbxproj @@ -1204,7 +1204,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = F77638C1F4BC132FB97FEEAF /* Pods-ledgerlivemobile.release.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = PrereleaseAppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = ledgerlivemobile/ledgerlivemobile.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; diff --git a/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Contents.json b/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Contents.json new file mode 100644 index 000000000000..3020a1fe1fcf --- /dev/null +++ b/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "filename" : "Icon-App-20x20@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-20x20@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-29x29@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-29x29@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-76x76@1x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "38x38" + }, + { + "filename" : "Icon-App-57x57@2x 1.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "38x38" + }, + { + "filename" : "Icon-App-40x40@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-40x40@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-60x60@2x 1.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "Icon-App-60x60@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "64x64" + }, + { + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "64x64" + }, + { + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "68x68" + }, + { + "filename" : "Icon-App-76x76@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "Icon-App-83.5x83.5@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "ItunesArtwork@2x.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-20x20@2x.png b/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c25f6eff573596d9790637b637cdbce81e5d939a GIT binary patch literal 1008 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 QB>(^b07*qoM6N<$f+NQS&;S4c literal 0 HcmV?d00001 diff --git a/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-29x29@2x.png b/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..cc3accffa0eee0bf9aa45316d6957cecffc78f66 GIT binary patch literal 1510 zcmV6G0h1=R5tR(odZS_o1*NSD6)Zv~1qJJ%9e9~IaHInV{srR5k);vo zOdNF54oo6WifE-76saJBXk#yiYG_(|F6$e7oI|RqZPN4R?c4T&L$deTm-Xzm*V=3C zL=h2T8$kWH1*jHwh;B9P5Z!9nA-dJDLv*WQhv-(r4$-ZKZ9}xijX^{N(=`9uM$$-@D3=R$oAmMOWLZOfZgFy*}Le3Km24!q)OhiNs!w|zT zL_}n4Y^-G4P$(qfa998t92{IRSEX>xMkA33Q&Ur@s#vtl>8dX)*6^{!p z5!H2_d_Iq&D2`z36|hu0dz^eekFM)(h`2FlWLcJoh)hjQNh+0c%5K22EJ>wOGBq`| zVy;S2wR6dgTGRc3su2|t*S29LsA`&K+p_&slX%r!prR=Fd_FWybJh0N zi>eVdO_NWbKJn_+D@@ZwQ55#?-_M~#hc=#>tOKr`qauQ3StT`PnkJg2@%Zs$o;-Qt z)EkCDPfrhb@7{IVl5&*u}n%Aot>S3w6$+6=H}*HBf6pE#TM$naNW6W&e4jtZt?cC zZL-{-7HseQyN#~ZuTpF}jTUchyIksfxu{5^nM{UkHcM@7Egc;lB?YvvD~kmzuFK_e z%+Jpgi^ZH>AtKIuJN~u%**Am5W0Zq+_ppf=hQYOK*BBWYp})VM-rin^prR-&FE3Ll z6jlghlUG%hTrLN|n>TN`fB!zJsxmV(zC$4RHtD2hTn9w(E@@a@|-qR}Y# z?%i`1<{u+!nkKEStsFgil==C2+S}V19UY~wuaA+D5tf#gh(sbxPEO+Y`-#P3B$G)J zi3B}8J#=?>Gd@0!h~V{lnV6WMudk06FJ5r@@@4ky*+Vj!WN~p3UDr8sQw+9J$l5`r%#DSqjYw5a^}n#8XFsV{`@(|j~}P2tBawb zAx@n-h2QUIdU~2jBtn0GKc7E;=Jo5>w6?ZVS65fIQ*tFTs`Py|QPJ^I?E3Apy}EV) zP<#bqw=d2{eh(h(y46y=;`&N1*EU_i@K0ge{QYK!=vKoH(XEF60S=i#_~^(W00000 M07*qoM6N<$f(*LlDgXcg literal 0 HcmV?d00001 diff --git a/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-29x29@3x.png b/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c83791617b2e963ef981ad3745d48f658a8c5d83 GIT binary patch literal 2433 zcmb7Gdpy(q7oTf$&BEMD6Su+d~MOmP?uXef$0Myk5Wmet(?L=k+@0^*Wz(&gXsJ=ipq=+RI6+OM^fl zIY$T7AHePLOTd!A8P&rI2Z5v<98uQiZuklWg&?XNrMT$SU@`FqSt$$>Ejh_BPI&Uu1m9b zZ4#^WrBQCExH!|w%1W5yp5)Cf&EaxIK0b$b=W@ARH*arQH>H$sVZO<$v+abm zG|{c??W(Qw2vQLRCGJVgFDOV4q!ZUGR|yz(OEdMHi!G6lJj{ebp>NZrl*xk?xFC-4 z@5w^2CsB)TtWy!Zd94rn@Cjl6$&JP2pW=)zW!s@6OFZh%zBJg)K%09yjAuTVhx$k` zu!3zpjkuK}s4b$LeXfWvM-l_8y%S+{V-x=svk27U_xG@d0zoXiGdEKR7rD z0=3BtzcSk|g&eABnHEu8TjSDt`un?^!p5xn1sklwZW_VcGtyD%MmdS4eZ5SitK zQm}>W*0e&py5?D$!N8(eut{(~l@i5GKJ6>7N(o;44st@epFjWgVYpS?k`g5Je)zIX zv1DWq9opU7mb59qzP_$GEmGxz0?WyDVIkUy5gv!Xl2z@G&GwH>-i&nVD4ywqlJ|33 zr`qb4mxoC95OsBYZ|`Y>0HFm=OCk&sg5X-T`6WZBYEHM$ z>Oe$9MAyn>OWVwxzT>@TSzy4Y2LFY7)mU7(nZ}3V;c0=tq-9M-g|(fXT`kY%Fc75f zvVnmCe|>$0+5d%1*59phav~d#fA?c&JE^VlwPlIbU}5{-q1kyhb|%`q{L|ox^2C~MKl-H=7W(zus0;fma@3K#Imx}EpH_sc(p`onVCWJ^YgJ-Y$_|Y$>wLKMLgjChSZq)5+QSE+a@Veq^S}s`332IvDeSmw49s{33K*uDQ9H1B9S& zU~osrkrL0-bg#a2_fPsS@+4e;>80tJOZ`K(vql z+^aVSS*8eTZ`?`^C4k6upw^4~MMWKzkdR35E-~^GjhJlCX%1(d0{rPVvd+lK$^Cte zd8qjJ+ZIPI)}icKDey>GCTy1TwU>#;vHU z%e&H^>@1jPkof!?r=6Tgxw-N$lQ!YD_V(vcsPxGnBzr>7)%w9~HItB)iKee}bd|~% zFJdPdbh@^!8$eyAI$Gr^Ddj1$ikrf3f`52C7xb$~06rGKuYVR#m6nkS9k26KQd4t0 zc<^9A{Y9V}xZ;u$18r?gIf=BmIQ%?zu2;FTqC%^Rc^agzrw`5&z(T4VIuZhgD=>`c)qPfyQ+ zf&vr(h>;H;PKAxWc-qilEG;Yh=kRlvS1X$4(Nu2252Xu|$2Ja3|K(Je_xN${$B(`W z5DN>7yrLqUPjvXC!4+sq0NwT?@{)?Ws zwkY)8hV0VPz1f-wbtaPumXfk|c9zSK-zQy2zdJ`S^k3t2-4t#e#e#7-902;aMJB;F zcr%DwTYM2eKR+^=jC696X=rH3D=p2|vdQq>-dHhG&mq^;bW?GPB4m|#*9%kq{bx_? zl~2EWw{@V^8?KK}b9wSRn_l>vk*aLZ`}ZAp#C8Fr3-0qKn?gD9!lzwx_RVcE(5;>C z%DkC2OouPfXx+=K#`<{PmGsO^?s&aF@a)^kmg`5aQo-Tlbt3Ln%IH`7^jsdJA|v71 zcG{0mrsN`$Hp-ftp--Pa?HL%L-Pd!;BN8Ps9jV#bvOp}$lP$a1Z1#y{;Ryg&AabG4 zxxc*1c!F^v5jrwDdcf3_iLl5j2R|RiefbitqpN#$gzN@N4I8U@T3IO#K!!Ha6ec26 zIUODzPHhYxnEK5(J~s9vka9>^SSMcjuu<5kVL@S`|I$ds!(*X)0E${#T2fF^$po;) z0ccGB(4n-U5NfG;jH`!7rfn9K4P%ymh zcJ_E}4ewgG^wZvJW@2^qi6_~>uq?U-Iji@1nxPixiMDSYFHb}C5d@da*um(FK5{}8 z=G93@R(wzv;|VLSIh61~jDN1R2F*kf@KOJje<#xat}FlSS8;Z2%bTa?1FIFt(e^B= J=9F*Re*mP!*en15 literal 0 HcmV?d00001 diff --git a/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-40x40@2x.png b/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2bce6ec8d8260395bba46ccdc17168b83e3c1171 GIT binary patch literal 2207 zcmbVOcR1T?8&0eyM#ZdEi5f*?6&)H|L=;8sO+|Gn4l#O85s4Y(P{gQJqcuv6L(Lp& zR*Y6#^9YIhRE<`xTIbj6`{VonyRLUU&-=&o-tTok_wzo9)>arkZV7G>2*igqGqDAB z>t7SX1+1w8-)lf1h#%I(&@McGE$YVh+xC7V?SQZ_hu??@Ke$@BwV(f6AIu11=Ro#rlHByfh)@LW4b1JpN;aIOCjW;+ zv~Oj@rX#Sn1H1Q{T-15DYUc%v5J^4*xI0=B{#rxRkY@q+-W5|h&C@wjOk-11Pyg87 zj`5~asw-Z<7GGIeK@d5RHk}1jDy=v9Og7TE>H`5rC@DFYuM?^~CW=OIFYF#bWr%i; zjzS=ruCMrOGDMh-FgtGW6Ta;aXPmvGn_H3?<#{h z=43sWSuEO0i0aR3w;dKs4!=GN?eKt?dmvL6*^){ihA>KICW*!Jt*m#-LYtcQrSGCK z7-JX_XL;p9K*pl>pE~%dht_udsY75oAUP7iaY4z2MRf-Ef^d{SMIS8R{nhwd)KF^5a z`!C6flRn+Sf3knsmx1-D3()+zu?+<=$Hri`wzh27?T9cr1qJ1T zT$OC?^F;^N})Nm&_ZiK3}*F+x~)l(d8^D~l_+Z!aq=yZ3YZf|i6O z?PT!N)YQ~ucPN=RvEr`~fF^P+Gv0>XCqILah@ z8!EGTa3nDJLbg7?NkRrw`X$8-Y4BLBGA?*50q=VASW+oD%}HHEQc^N&_}mlSrO8RB zN}a6OMYg+5E_KM{SFbb<0nnw-&L(pX5)fX&!NKFVm*Hz(&Y~_gS*=Ici(8^mqsA}} zVPRonNY`9I|6dPg2*j*f{itK|?CdP=LycAvR59!ypH~P~RaF-A0Tc=a!L+~wp8(@| zO^tg!dB1<=b2Az`VL=q!PEeDtrs!V8ydFw7tWksB`qmk73}tj2|hJFJsrrPfx|mId~vwqrY4a#{2D@0vO-2h?^i&F4bMa+C6m8>3zE`}6n3Z*IXXJ3b3%&2;qfzTrq0gd;RXGp zh58Fy`(tx+DT|9fZe5Sh^27B^&}hypsm2)0socE0;!mpraCq9iPcIj~e5guDOY47i ztAA`}COIySi=3M36A~h9Z*Sl7$HAp)hYRWH>0^_V2?Yhxc6N4Nc)aN8c=pW9%orJ~ z>rNbUM1+oxgr%h=fMK#dLeoM)NJ2tFT~{~d_Gm$Ic{x|DXII>~Kk;ub8X&2WLjAP% zc9auRG#n)@mST#EIz}xc7}+MMZ_0o7t2Pb}TovyVLsvh_$jfu~^zr#u0i-CKa-fpcS&};aw z(WA@8K|vbBewtjTPK{4Y5Z7j!d%V`U##l^MB8WaLbS{x1%FMnE&SOf$lkw|7YZt&*^eU6a6QK;8jY#dJuk}9vL z82|Y3S1{oA`;&Fx$6aujnqTZ&RR{t2LD9_*=SvzIgcLHM6*mg|+QK#%y9YLQc0@p9 zOlE(YqKXQs*T>M)6CN2E8ORzCXmL|zEid2l@&eU*bab!Jwb|I)b7sFK?d;(H6QYxk ziV!q?rh&{Ds4Ndo4rM$%>xxY%VWsi)d}c@695S71q}qpgO1EOCNQs^K+(U-zhYTui zs>mS1n}Y1hPKzGM=*i_TwV&9EnRw1-Tf%LYsEm-WjDGSz=Y-uoTki2t`VIqVJV02q KmB~vZFT%f&-~=}S literal 0 HcmV?d00001 diff --git a/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-40x40@3x.png b/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..5854574c33109631122e03938b8c516c9891012c GIT binary patch literal 3132 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 literal 0 HcmV?d00001 diff --git a/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-57x57@2x 1.png b/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-57x57@2x 1.png new file mode 100644 index 0000000000000000000000000000000000000000..5ab595b59bbd97c926d328a8eaef67b096a82eec GIT binary patch literal 1488 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 literal 0 HcmV?d00001 diff --git a/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-60x60@3x.png b/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..8416b5b01a51ea1c60a9c88facf5748aa06aab00 GIT binary patch literal 4965 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 NJ#8aI#dXK<{{h?DGA;lB literal 0 HcmV?d00001 diff --git a/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..334c4957b438139fe78fd9fafe252edefb70db56 GIT binary patch literal 4838 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 OL16!=Dd#;j{`enLc!IS6 literal 0 HcmV?d00001 diff --git a/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/ItunesArtwork@2x.png b/apps/ledger-live-mobile/ios/ledgerlivemobile/Images.xcassets/PrereleaseAppIcon.appiconset/ItunesArtwork@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..293e07919ef83c547b020742166ca8464377b42e GIT binary patch literal 12684 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 literal 0 HcmV?d00001 From 212b861a271c4d1baea4911bfab24c8f4a7b3013 Mon Sep 17 00:00:00 2001 From: Angus Bayley Date: Thu, 3 Oct 2024 10:27:08 +0100 Subject: [PATCH 07/86] build: remove unnecessary array syntax --- apps/ledger-live-mobile/fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ledger-live-mobile/fastlane/Fastfile b/apps/ledger-live-mobile/fastlane/Fastfile index cac05fec9976..f831aa36147d 100644 --- a/apps/ledger-live-mobile/fastlane/Fastfile +++ b/apps/ledger-live-mobile/fastlane/Fastfile @@ -144,7 +144,7 @@ platform :ios do ) match( type: "appstore", - app_identifier: ["com.ledger.live.prerelease"], + app_identifier: "com.ledger.live.prerelease", force: true, generate_apple_certs: true, git_url: ENV["GIT_REPO_URL"], From 90815816ed4d2dea6b99f4a0dc047c302facd3f1 Mon Sep 17 00:00:00 2001 From: Angus Bayley Date: Tue, 8 Oct 2024 15:44:22 +0100 Subject: [PATCH 08/86] build: use .env for app configuration --- apps/ledger-live-mobile/fastlane/Fastfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/ledger-live-mobile/fastlane/Fastfile b/apps/ledger-live-mobile/fastlane/Fastfile index f831aa36147d..a2a378bb36dc 100644 --- a/apps/ledger-live-mobile/fastlane/Fastfile +++ b/apps/ledger-live-mobile/fastlane/Fastfile @@ -269,9 +269,7 @@ platform :ios do gym( scheme: PROJECT_NAME, workspace: XCODE_WORKSPACE, - # This should come from env files - as we move each release type to its own app we will - # move them away from "Release" and to their own .env-file defined configuration - configuration: !options[:adhoc] ? ENV["APP_CONFIGURATION"] : "Release", + configuration: ENV["APP_CONFIGURATION"], silent: true, xcargs: `#{settings_to_override} -UseNewBuildSystem=YES`, output_directory: OUTPUT_DIRECTORY, From 48fd2f56518a73c07f700486f7dec6f1a80df078 Mon Sep 17 00:00:00 2001 From: Angus Bayley Date: Tue, 8 Oct 2024 16:09:49 +0100 Subject: [PATCH 09/86] fix: update that autogenerated mobile-env.md file --- apps/ledger-live-mobile/mobile-env.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ledger-live-mobile/mobile-env.md b/apps/ledger-live-mobile/mobile-env.md index 8903ec177531..89c1474172ca 100644 --- a/apps/ledger-live-mobile/mobile-env.md +++ b/apps/ledger-live-mobile/mobile-env.md @@ -23,7 +23,7 @@ In other cases, these env files will be used: | target | iOS | Android | |--|--|--| |release|com.ledger.live|com.ledger.live| -|prerelease|com.ledger.live|com.ledger.live| +|prerelease|com.ledger.live.prerelease|com.ledger.live| |nightly|com.ledger.live.nightly|com.ledger.live| |staging|com.ledger.live.dev|com.ledger.live.dev| # Sentry projects From 9c928544f1631917eb2c2f9e150d070537bb93b7 Mon Sep 17 00:00:00 2001 From: qperrot Date: Wed, 9 Oct 2024 14:46:10 +0200 Subject: [PATCH 10/86] fix: graph correctly display even if value is NaN --- .changeset/pink-apricots-yell.md | 5 +++++ libs/live-countervalues/src/portfolio.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/pink-apricots-yell.md diff --git a/.changeset/pink-apricots-yell.md b/.changeset/pink-apricots-yell.md new file mode 100644 index 000000000000..3f7b28b68653 --- /dev/null +++ b/.changeset/pink-apricots-yell.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/live-countervalues": patch +--- + +Portfolio all time display fix for Polkadot account diff --git a/libs/live-countervalues/src/portfolio.ts b/libs/live-countervalues/src/portfolio.ts index 1293da423cbf..e742d8e84056 100644 --- a/libs/live-countervalues/src/portfolio.ts +++ b/libs/live-countervalues/src/portfolio.ts @@ -143,7 +143,7 @@ export function getBalanceHistory( for (let i = 0; i < count - 1; i++) { history.unshift({ date: new Date(t - conf.increment * i), - value: balances[balances.length - 1 - i] ?? 0, + value: balances[balances.length - 1 - i] || 0, }); } From 9535221c3a611457b2de3debcad2e8be2d94c98e Mon Sep 17 00:00:00 2001 From: sarneijim <38540290+sarneijim@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:31:21 +0100 Subject: [PATCH 11/86] fix: fix sellNg and fundNg (#8027) * fix: fix sellNg * fix: extend fix to all ng types * refactor: set SELL literal * refactor: add swapNg --- libs/exchange-module/src/index.ts | 9 ++--- .../platform/transfer/completeExchange.ts | 33 ++++++++++--------- .../src/exchange/swap/completeExchange.ts | 2 +- .../src/exchange/swap/initSwap.ts | 2 +- .../packages/hw-app-exchange/src/Exchange.ts | 8 +++-- 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/libs/exchange-module/src/index.ts b/libs/exchange-module/src/index.ts index e13aadfeeed2..8ebe1a74c18f 100644 --- a/libs/exchange-module/src/index.ts +++ b/libs/exchange-module/src/index.ts @@ -158,8 +158,8 @@ export class ExchangeModule extends CustomModule { provider: string; fromAccountId: string; transaction: Transaction; - binaryPayload: Buffer; - signature: Buffer; + binaryPayload: string | Buffer; // Support Coinify Buffer legacy + signature: string | Buffer; // Support Coinify Buffer legacy feeStrategy: ExchangeCompleteParams["feeStrategy"]; }): Promise { const result = await this.request( @@ -169,8 +169,9 @@ export class ExchangeModule extends CustomModule { provider, fromAccountId, rawTransaction: serializeTransaction(transaction), - hexBinaryPayload: binaryPayload.toString("hex"), - hexSignature: signature.toString("hex"), + hexBinaryPayload: + typeof binaryPayload === "string" ? binaryPayload : binaryPayload.toString("hex"), + hexSignature: typeof signature === "string" ? signature : signature.toString("hex"), feeStrategy, }, ); diff --git a/libs/ledger-live-common/src/exchange/platform/transfer/completeExchange.ts b/libs/ledger-live-common/src/exchange/platform/transfer/completeExchange.ts index a4b8cc3c4ba3..71c131d3a233 100644 --- a/libs/ledger-live-common/src/exchange/platform/transfer/completeExchange.ts +++ b/libs/ledger-live-common/src/exchange/platform/transfer/completeExchange.ts @@ -3,7 +3,12 @@ import { firstValueFrom, from, Observable } from "rxjs"; import { TransportStatusError, WrongDeviceForAccount } from "@ledgerhq/errors"; import { delay } from "../../../promise"; -import { createExchange, ExchangeTypes } from "@ledgerhq/hw-app-exchange"; +import { + isExchangeTypeNg, + ExchangeTypes, + createExchange, + PayloadSignatureComputedFormat, +} from "@ledgerhq/hw-app-exchange"; import perFamily from "../../../generated/exchange"; import { getAccountCurrency, getMainAccount } from "../../../account"; import { getAccountBridge } from "../../../bridge"; @@ -86,11 +91,14 @@ const completeExchange = ( if (unsubscribed) return; currentStep = "PROCESS_TRANSACTION"; - await exchange.processTransaction(Buffer.from(binaryPayload, "hex"), estimatedFees); + const { payload, format }: { payload: Buffer; format: PayloadSignatureComputedFormat } = + isExchangeTypeNg(exchange.transactionType) + ? { payload: Buffer.from("." + binaryPayload), format: "jws" } + : { payload: Buffer.from(binaryPayload, "hex"), format: "raw" }; + await exchange.processTransaction(payload, estimatedFees, format); if (unsubscribed) return; - const bufferSignature = Buffer.from(signature, "hex"); - const goodSign = convertSignature(bufferSignature, exchangeType); + const goodSign = convertSignature(signature, exchange.transactionType); currentStep = "CHECK_TRANSACTION_SIGNATURE"; await exchange.checkTransactionSignature(goodSign); @@ -110,7 +118,7 @@ const completeExchange = ( estimatedFees: estimatedFees.toString(), }); currentStep = "CHECK_PAYOUT_ADDRESS"; - await exchange.checkPayoutAddress( + await exchange.validatePayoutOrAsset( payoutAddressConfig, payoutAddressConfigSignature, payoutAddressParameters.addressParameters, @@ -173,17 +181,10 @@ const completeExchange = ( * @param {ExchangeTypes} exchangeType * @return {Buffer} The correct format Buffer for AppExchange call. */ -function convertSignature(bufferSignature: Buffer, exchangeType: ExchangeTypes): Buffer { - const goodSign = - exchangeType === ExchangeTypes.Sell - ? bufferSignature - : Buffer.from(secp256k1.signatureExport(bufferSignature)); - - if (!goodSign) { - throw new Error("Could not check provider signature"); - } - - return goodSign; +function convertSignature(signature: string, exchangeType: ExchangeTypes): Buffer { + return isExchangeTypeNg(exchangeType) + ? Buffer.from(signature, "base64url") + : secp256k1.signatureExport(Buffer.from(signature, "hex")); } export default completeExchange; diff --git a/libs/ledger-live-common/src/exchange/swap/completeExchange.ts b/libs/ledger-live-common/src/exchange/swap/completeExchange.ts index bb382c795fef..54659f02b70e 100644 --- a/libs/ledger-live-common/src/exchange/swap/completeExchange.ts +++ b/libs/ledger-live-common/src/exchange/swap/completeExchange.ts @@ -115,7 +115,7 @@ const completeExchange = ( try { currentStep = "CHECK_PAYOUT_ADDRESS"; - await exchange.checkPayoutAddress( + await exchange.validatePayoutOrAsset( payoutAddressConfig, payoutAddressConfigSignature, payoutAddressParameters.addressParameters, diff --git a/libs/ledger-live-common/src/exchange/swap/initSwap.ts b/libs/ledger-live-common/src/exchange/swap/initSwap.ts index ff5f0060faa0..b1965fdc42c1 100644 --- a/libs/ledger-live-common/src/exchange/swap/initSwap.ts +++ b/libs/ledger-live-common/src/exchange/swap/initSwap.ts @@ -203,7 +203,7 @@ const initSwap = (input: InitSwapInput): Observable => { await getCurrencyExchangeConfig(payoutCurrency); try { - await swap.checkPayoutAddress( + await swap.validatePayoutOrAsset( payoutAddressConfig, payoutAddressConfigSignature, payoutAddressParameters.addressParameters, diff --git a/libs/ledgerjs/packages/hw-app-exchange/src/Exchange.ts b/libs/ledgerjs/packages/hw-app-exchange/src/Exchange.ts index e663b393de4a..2a2a84f44ffc 100644 --- a/libs/ledgerjs/packages/hw-app-exchange/src/Exchange.ts +++ b/libs/ledgerjs/packages/hw-app-exchange/src/Exchange.ts @@ -30,9 +30,9 @@ const CHECK_PARTNER_COMMAND = 0x05; const PROCESS_TRANSACTION_RESPONSE = 0x06; const CHECK_TRANSACTION_SIGNATURE = 0x07; const CHECK_PAYOUT_ADDRESS = 0x08; -const CHECK_ASSET_IN = 0x08; const CHECK_REFUND_ADDRESS = 0x09; const SIGN_COIN_TRANSACTION = 0x0a; +const CHECK_ASSET_IN_AND_DISPLAY = 0x0b; // Extension for PROCESS_TRANSACTION_RESPONSE APDU const P2_NONE = 0x00 << 4; @@ -260,7 +260,7 @@ export default class Exchange { maybeThrowProtocolError(result); } - async checkPayoutAddress( + async validatePayoutOrAsset( payoutCurrencyConfig: Buffer, currencyConfigSignature: Buffer, addressParameters: Buffer, @@ -280,7 +280,9 @@ export default class Exchange { ]); const result: Buffer = await this.transport.send( 0xe0, - this.transactionType === ExchangeTypes.Swap ? CHECK_PAYOUT_ADDRESS : CHECK_ASSET_IN, + this.transactionType === ExchangeTypes.Swap || this.transactionType === ExchangeTypes.SwapNg + ? CHECK_PAYOUT_ADDRESS + : CHECK_ASSET_IN_AND_DISPLAY, this.transactionRate, this.transactionType, bufferToSend, From fafecd19b71616e3460415ea6d637c6cfd532137 Mon Sep 17 00:00:00 2001 From: adammino-ledger Date: Wed, 9 Oct 2024 16:03:09 +0100 Subject: [PATCH 12/86] fix bug where flag stakePrograms was not checked into account screen, add check on flag into bitcoin header actions --- .../bitcoin/AccountHeaderManageActions.ts | 29 ++++++++++++------- .../screens/account/AccountHeaderActions.tsx | 26 +++++++++++------ 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/apps/ledger-live-desktop/src/renderer/families/bitcoin/AccountHeaderManageActions.ts b/apps/ledger-live-desktop/src/renderer/families/bitcoin/AccountHeaderManageActions.ts index bc5ffc14b5ee..9683a431ba1a 100644 --- a/apps/ledger-live-desktop/src/renderer/families/bitcoin/AccountHeaderManageActions.ts +++ b/apps/ledger-live-desktop/src/renderer/families/bitcoin/AccountHeaderManageActions.ts @@ -7,6 +7,7 @@ import { stakeDefaultTrack } from "~/renderer/screens/stake/constants"; import { BitcoinAccount } from "@ledgerhq/coin-bitcoin/lib/types"; import { TokenAccount } from "@ledgerhq/types-live"; import IconCoins from "~/renderer/icons/Coins"; +import useFeature from "@ledgerhq/live-common/featureFlags/useFeature"; type Props = { account: BitcoinAccount | TokenAccount; @@ -14,6 +15,10 @@ type Props = { }; const AccountHeaderActions = ({ account, parentAccount }: Props) => { + const stakeProgramsFeatureFlag = useFeature("stakePrograms"); + const listFlag = stakeProgramsFeatureFlag?.params?.list ?? []; + const stakeProgramsEnabled = stakeProgramsFeatureFlag?.enabled ?? false; + const availableOnStake = stakeProgramsEnabled && listFlag.includes("bitcoin"); const history = useHistory(); const { t } = useTranslation(); const mainAccount = getMainAccount(account, parentAccount); @@ -44,16 +49,20 @@ const AccountHeaderActions = ({ account, parentAccount }: Props) => { }; return [ - { - key: "Stake", - icon: IconCoins, - label: t("accounts.contextMenu.yield"), - event: "button_clicked2", - eventProperties: { - button: "stake", - }, - onClick: stakeOnClick, - }, + ...(availableOnStake + ? [ + { + key: "Stake", + icon: IconCoins, + label: t("accounts.contextMenu.yield"), + event: "button_clicked2", + eventProperties: { + button: "stake", + }, + onClick: stakeOnClick, + }, + ] + : []), ]; }; diff --git a/apps/ledger-live-desktop/src/renderer/screens/account/AccountHeaderActions.tsx b/apps/ledger-live-desktop/src/renderer/screens/account/AccountHeaderActions.tsx index 5b46b9608858..5474756fcd94 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/account/AccountHeaderActions.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/account/AccountHeaderActions.tsx @@ -40,6 +40,7 @@ import { getAvailableProviders } from "@ledgerhq/live-common/exchange/swap/index import { useFetchCurrencyAll } from "@ledgerhq/live-common/exchange/swap/hooks/index"; import { isWalletConnectSupported } from "@ledgerhq/live-common/walletConnect/index"; import { WC_ID } from "@ledgerhq/live-common/wallet-api/constants"; +import useFeature from "@ledgerhq/live-common/featureFlags/useFeature"; type RenderActionParams = { label: React.ReactNode; @@ -189,6 +190,10 @@ const AccountHeaderActions = ({ account, parentAccount, openModal }: Props) => { const swapDefaultTrack = useGetSwapTrackingProperties(); const specific = getLLDCoinFamily(mainAccount.currency.family); + const stakeProgramsFeatureFlag = useFeature("stakePrograms"); + const listFlag = stakeProgramsFeatureFlag?.params?.list ?? []; + const stakeProgramsEnabled = stakeProgramsFeatureFlag?.enabled ?? false; + const manage = specific?.accountHeaderManageActions; let manageList: ManageAction[] = []; if (manage) { @@ -204,6 +209,7 @@ const AccountHeaderActions = ({ account, parentAccount, openModal }: Props) => { const availableOnBuy = !!currency && isCurrencyAvailable(currency.id, "onRamp"); const availableOnSell = !!currency && isCurrencyAvailable(currency.id, "offRamp"); + const availableOnStake = stakeProgramsEnabled && listFlag.includes(currency.id || ""); // don't show buttons until we know whether or not we can show swap button, otherwise possible click jacking const showButtons = !!getAvailableProviders(); @@ -279,15 +285,17 @@ const AccountHeaderActions = ({ account, parentAccount, openModal }: Props) => { }, [openModal, parentAccount, account, buttonSharedTrackingFields]); const manageActions: RenderActionParams[] = [ - ...manageList.map(item => ({ - ...item, - contrastText, - currency, - eventProperties: { - ...buttonSharedTrackingFields, - ...item.eventProperties, - }, - })), + ...manageList + .filter(item => (availableOnStake && item.key === "Stake") || item.key !== "Stake") + .map(item => ({ + ...item, + contrastText, + currency, + eventProperties: { + ...buttonSharedTrackingFields, + ...item.eventProperties, + }, + })), ]; const buyHeader = onBuySell("buy")} />; From b961229545a2f759c6890685abd80f85334d291c Mon Sep 17 00:00:00 2001 From: Mounir Hamzaoui Date: Wed, 9 Oct 2024 17:21:08 +0200 Subject: [PATCH 13/86] feat: add memo tag information section in LLD recieve flow (#8055) --- .changeset/new-cougars-develop.md | 5 +++ .../MemoTag/__tests__/MemoTagInfo.test.tsx | 20 +++++++++ .../__tests__/MemoTagInfoBody.test.tsx | 30 +++++++++++++ .../MemoTag/components/MemoTagInfo.tsx | 42 +++++++++++++++++++ .../MemoTag/components/MemoTagInfoBody.tsx | 33 +++++++++++++++ .../src/newArch/features/MemoTag/constants.ts | 14 +++++++ .../modals/Receive/steps/StepReceiveFunds.tsx | 18 +++++++- .../static/i18n/en/app.json | 7 +++- 8 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 .changeset/new-cougars-develop.md create mode 100644 apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagInfo.test.tsx create mode 100644 apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagInfoBody.test.tsx create mode 100644 apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagInfo.tsx create mode 100644 apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagInfoBody.tsx create mode 100644 apps/ledger-live-desktop/src/newArch/features/MemoTag/constants.ts diff --git a/.changeset/new-cougars-develop.md b/.changeset/new-cougars-develop.md new file mode 100644 index 000000000000..834a7cd279b2 --- /dev/null +++ b/.changeset/new-cougars-develop.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": minor +--- + +Add Memo tag info section in LLD diff --git a/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagInfo.test.tsx b/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagInfo.test.tsx new file mode 100644 index 000000000000..aa708eadb79e --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagInfo.test.tsx @@ -0,0 +1,20 @@ +/** + * @jest-environment jsdom + */ +import React from "react"; +import { fireEvent, render, screen } from "tests/testUtils"; +import MemoTagInfo from "../components/MemoTagInfo"; + +describe("MemoTagInfo", () => { + it("should display MemoTagInfo", async () => { + render(); + const clickabelLabel = screen.getByText(/Need a Tag\/Memo?/); + expect(clickabelLabel).toBeVisible(); + fireEvent.click(clickabelLabel); + const memoTagInfoBody = await screen.findByTestId("memo-tag-info-body"); + // spec: when a user clicks on the label, it should display the memo tag info body + expect(memoTagInfoBody).toBeVisible(); + // spec: when a user clicks on the label, it should disappear + expect(clickabelLabel).not.toBeInTheDocument(); + }); +}); diff --git a/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagInfoBody.test.tsx b/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagInfoBody.test.tsx new file mode 100644 index 000000000000..3becffa6c68e --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagInfoBody.test.tsx @@ -0,0 +1,30 @@ +/** + * @jest-environment jsdom + */ +import React from "react"; +import { fireEvent, render, screen } from "tests/testUtils"; +import MemoTagInfoBody from "../components/MemoTagInfoBody"; +import { MEMO_TAG_LEARN_MORE_LINK } from "../constants"; +import * as LinkingHelpers from "~/renderer/linking"; + +jest.mock("~/renderer/linking", () => ({ + openURL: jest.fn(), +})); + +describe("MemoTagInfoBody", () => { + it("should display MemoTagInfoBody correctly", async () => { + render(); + + const memoTagInfoBody = screen.getByTestId("memo-tag-info-body"); + const learnMoreLink = screen.getByText(/Learn more about Tag\/Memo/); + + expect(memoTagInfoBody).toBeVisible(); + expect(learnMoreLink).toBeVisible(); + + fireEvent.click(learnMoreLink); + + jest.spyOn(LinkingHelpers, "openURL"); + // spec: when a user clicks on the learn more link, it should open the link + expect(LinkingHelpers.openURL).toHaveBeenCalledWith(MEMO_TAG_LEARN_MORE_LINK); + }); +}); diff --git a/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagInfo.tsx b/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagInfo.tsx new file mode 100644 index 000000000000..39b99bbbadb8 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagInfo.tsx @@ -0,0 +1,42 @@ +import React, { useState } from "react"; +import { Trans } from "react-i18next"; + +import { Alert } from "@ledgerhq/react-ui"; + +import { colors } from "~/renderer/styles/theme"; + +import Label from "~/renderer/components/Label"; + +import MemoTagInfoBody from "./MemoTagInfoBody"; + +const MemoTagInfo = () => { + const [isAlertDisplayed, toggleAlertDisplay] = useState(false); + + const handleOnToggleAlertDisplay = () => { + toggleAlertDisplay(!isAlertDisplayed); + }; + + return !isAlertDisplayed ? ( + + ) : ( + } + /> + ); +}; + +export default MemoTagInfo; diff --git a/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagInfoBody.tsx b/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagInfoBody.tsx new file mode 100644 index 000000000000..6354db4e01d1 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagInfoBody.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { Link, Text } from "@ledgerhq/react-ui"; +import { Trans } from "react-i18next"; +import { openURL } from "~/renderer/linking"; +import { MEMO_TAG_LEARN_MORE_LINK } from "../constants"; + +const MemoTagInfoBody = () => { + const handleOpenLMLink = () => openURL(MEMO_TAG_LEARN_MORE_LINK); + + return ( +
+ + + + + +
+ + + +
+ ); +}; + +export default MemoTagInfoBody; diff --git a/apps/ledger-live-desktop/src/newArch/features/MemoTag/constants.ts b/apps/ledger-live-desktop/src/newArch/features/MemoTag/constants.ts new file mode 100644 index 000000000000..43d38061a4ee --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/MemoTag/constants.ts @@ -0,0 +1,14 @@ +export const MEMO_TAG_LEARN_MORE_LINK = "https://support.ledger.com/article/4409603715217-zd"; +export const MEMO_TAG_COINS: string[] = [ + "ripple", + "stellar", + "cosmos", + "hedera", + "injective", + "crypto_org", + "crypto_org_croeseid", + "stacks", + "ton", + "eos", + "bsc", +]; diff --git a/apps/ledger-live-desktop/src/renderer/modals/Receive/steps/StepReceiveFunds.tsx b/apps/ledger-live-desktop/src/renderer/modals/Receive/steps/StepReceiveFunds.tsx index fc9fed00f4d4..55f74e5fde7d 100644 --- a/apps/ledger-live-desktop/src/renderer/modals/Receive/steps/StepReceiveFunds.tsx +++ b/apps/ledger-live-desktop/src/renderer/modals/Receive/steps/StepReceiveFunds.tsx @@ -29,7 +29,7 @@ import ModalBody from "~/renderer/components/Modal/ModalBody"; import QRCode from "~/renderer/components/QRCode"; import { getEnv } from "@ledgerhq/live-env"; import AccountTagDerivationMode from "~/renderer/components/AccountTagDerivationMode"; -import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; +import { FeatureToggle, useFeature } from "@ledgerhq/live-common/featureFlags/index"; import { LOCAL_STORAGE_KEY_PREFIX } from "./StepReceiveStakingFlow"; import { useDispatch } from "react-redux"; import { openModal } from "~/renderer/actions/modals"; @@ -41,6 +41,8 @@ import { getDefaultAccountName } from "@ledgerhq/live-wallet/accountName"; import { useMaybeAccountName } from "~/renderer/reducers/wallet"; import { UTXOAddressAlert } from "~/renderer/components/UTXOAddressAlert"; import { isUTXOCompliant } from "@ledgerhq/live-common/currencies/helpers"; +import MemoTagInfo from "~/newArch/features/MemoTag/components/MemoTagInfo"; +import { MEMO_TAG_COINS } from "~/newArch/features/MemoTag/constants"; const Separator = styled.div` border-top: 1px solid #99999933; @@ -68,7 +70,12 @@ const Receive1ShareAddress = ({ address: string; showQRCodeModal: () => void; }) => { - const isUTXOCompliantCurrency = isUTXOCompliant(account.currency.family); + const { currency } = account; + + const isUTXOCompliantCurrency = isUTXOCompliant(currency.family); + const shouldRenderMemoTagInfo = + currency.explorerId && MEMO_TAG_COINS.includes(currency.explorerId); + return ( <> @@ -103,6 +110,13 @@ const Receive1ShareAddress = ({ )} + + {shouldRenderMemoTagInfo && ( + + + + )} + ); }; diff --git a/apps/ledger-live-desktop/static/i18n/en/app.json b/apps/ledger-live-desktop/static/i18n/en/app.json index 9d36c2d28a50..fa027947bb6b 100644 --- a/apps/ledger-live-desktop/static/i18n/en/app.json +++ b/apps/ledger-live-desktop/static/i18n/en/app.json @@ -2253,7 +2253,12 @@ "description": "Stake your CELO with Ledger by Figment validator. It guarantees complete cover against slashing, backed by Ledger’s uncompromising security." } } - } + }, + "memoTag": { + "title": "Need a Tag/Memo?", + "description": "You might need a <0>Tag/Memo for receiving this asset from an exchange. You can use any combination of numbers like 1234.", + "learnMore": "Learn more about Tag/Memo" + } }, "send": { "title": "Send", From bc8559ec80f7036dc54174d7464d214d9e16e4b2 Mon Sep 17 00:00:00 2001 From: qperrot Date: Wed, 9 Oct 2024 17:30:59 +0200 Subject: [PATCH 14/86] chore: log NaN values --- libs/live-countervalues/src/portfolio.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/live-countervalues/src/portfolio.ts b/libs/live-countervalues/src/portfolio.ts index e742d8e84056..be14ae53f92f 100644 --- a/libs/live-countervalues/src/portfolio.ts +++ b/libs/live-countervalues/src/portfolio.ts @@ -19,6 +19,7 @@ import type { AssetsDistribution, } from "@ledgerhq/types-live"; import type { CryptoCurrency, Currency, TokenCurrency } from "@ledgerhq/types-cryptoassets"; +import { log } from "@ledgerhq/logs"; export const defaultAssetsDistribution = { minShowFirst: 1, @@ -141,9 +142,13 @@ export function getBalanceHistory( const t = new Date(conf.startOf(now).getTime() - 1).getTime(); // end of yesterday for (let i = 0; i < count - 1; i++) { + const value = balances[balances.length - 1 - i]; + if (Number.isNaN(value)) { + log("countervalues-error", `Value is NaN for ${account.id}`); + } history.unshift({ date: new Date(t - conf.increment * i), - value: balances[balances.length - 1 - i] || 0, + value: value || 0, }); } From c0e9ac0a256f8f77b51ad28fc84ecfe3df8bc053 Mon Sep 17 00:00:00 2001 From: Victor <162306106+VicAlbr@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:04:09 +0200 Subject: [PATCH 15/86] [QAA-261] Adding E2E delegation scenario for LLD (NEAR, SOLANA, COSMOS) (#8033) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: ✅ Adding delegate E2E test scenario * style: 🚚 Rename variable + answer PR coms * test: ♻️ Refacto modal and drawer components for e2e tests --- .../drawers/OperationDetails/index.tsx | 9 ++- .../drawer.component.ts} | 16 ----- .../ledger-live-desktop/tests/enum/Account.ts | 2 +- .../tests/enum/AppInfos.ts | 57 +++++++++++------ .../tests/enum/DeviceLabels.ts | 10 ++- .../tests/models/Delegate.ts | 9 +++ .../tests/page/drawer/delegate.drawer.ts | 50 +++++++++++++++ .../tests/page/drawer/ledger.sync.drawer.ts | 2 +- .../tests/page/drawer/send.drawer.ts | 21 +++++++ .../page/drawer/swap.confirmation.drawer.ts | 6 +- apps/ledger-live-desktop/tests/page/index.ts | 6 +- .../tests/page/modal/delegate.modal.ts | 17 +++++- .../tests/page/modal/send.modal.ts | 18 +++--- .../tests/page/speculos.page.ts | 61 ++++++++++++++++++- .../tests/specs/general/layout.spec.ts | 2 +- .../specs/manager/devicelocalization.spec.ts | 2 +- .../specs/services/confirmTransaction.spec.ts | 2 +- .../specs/services/cosmosStaking.spec.ts | 2 +- .../tests/specs/services/dapp.spec.ts | 2 +- .../specs/services/ethereumStaking.spec.ts | 2 +- .../tests/specs/services/liveapp-sdk.spec.ts | 2 +- .../tests/specs/services/swap.spec.ts | 2 +- .../tests/specs/services/wallet-api.spec.ts | 2 +- .../tests/specs/speculos/delegate.spec.ts | 40 ++++++++---- .../tests/specs/speculos/send.tx.spec.ts | 11 ++-- libs/ledger-live-common/src/e2e/speculos.ts | 5 ++ 26 files changed, 277 insertions(+), 81 deletions(-) rename apps/ledger-live-desktop/tests/{page/drawer/drawer.ts => component/drawer.component.ts} (75%) create mode 100644 apps/ledger-live-desktop/tests/models/Delegate.ts create mode 100644 apps/ledger-live-desktop/tests/page/drawer/delegate.drawer.ts create mode 100644 apps/ledger-live-desktop/tests/page/drawer/send.drawer.ts diff --git a/apps/ledger-live-desktop/src/renderer/drawers/OperationDetails/index.tsx b/apps/ledger-live-desktop/src/renderer/drawers/OperationDetails/index.tsx index ea7c3f7f7a00..14861c153c1e 100644 --- a/apps/ledger-live-desktop/src/renderer/drawers/OperationDetails/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/drawers/OperationDetails/index.tsx @@ -325,6 +325,7 @@ const OperationD = (props: Props) => { color="palette.text.shade60" mt={0} mb={1} + data-testid="transaction-type" > @@ -492,7 +493,13 @@ const OperationD = (props: Props) => { /> ) : undefined} - + diff --git a/apps/ledger-live-desktop/tests/page/drawer/drawer.ts b/apps/ledger-live-desktop/tests/component/drawer.component.ts similarity index 75% rename from apps/ledger-live-desktop/tests/page/drawer/drawer.ts rename to apps/ledger-live-desktop/tests/component/drawer.component.ts index 2bd2f3c35901..4a60dd551309 100644 --- a/apps/ledger-live-desktop/tests/page/drawer/drawer.ts +++ b/apps/ledger-live-desktop/tests/component/drawer.component.ts @@ -1,6 +1,4 @@ import { Component } from "tests/page/abstractClasses"; -import { expect } from "@playwright/test"; -import { Transaction } from "tests/models/Transaction"; import { step } from "tests/misc/reporters/step"; export class Drawer extends Component { @@ -10,9 +8,6 @@ export class Drawer extends Component { private closeButton = this.page.getByTestId("drawer-close-button"); private currencyButton = (currency: string) => this.page.getByTestId(`currency-row-${currency.toLowerCase()}`).first(); - private addressValue = (address: string) => - this.page.locator('[data-testid="drawer-content"]').locator(`text=${address}`); - private amountValue = this.page.getByTestId("amountReceived-drawer"); readonly selectAssetTitle = this.page.getByTestId("select-asset-drawer-title").first(); readonly selectAccountTitle = this.page.getByTestId("select-account-drawer-title").first(); readonly swapAmountFrom = this.page.getByTestId("swap-amount-from").first(); @@ -25,17 +20,6 @@ export class Drawer extends Component { await this.continueButton.click(); } - @step("Verify address is visible") - async addressValueIsVisible(address: string) { - await this.addressValue(address).waitFor({ state: "visible" }); - } - - @step("Verify that the information of the transaction is visible") - async expectReceiverInfos(tx: Transaction) { - await expect(this.addressValue(tx.accountToCredit.address)).toBeVisible(); - await expect(this.amountValue).toBeVisible(); - } - async waitForDrawerToBeVisible() { await this.content.waitFor({ state: "visible" }); await this.closeButton.waitFor({ state: "visible" }); diff --git a/apps/ledger-live-desktop/tests/enum/Account.ts b/apps/ledger-live-desktop/tests/enum/Account.ts index 46f169e47570..2408d0a396e5 100644 --- a/apps/ledger-live-desktop/tests/enum/Account.ts +++ b/apps/ledger-live-desktop/tests/enum/Account.ts @@ -10,7 +10,7 @@ export class Account { static readonly BTC_NATIVE_SEGWIT_1 = new Account( Currency.BTC, "Bitcoin 1", - "bc1qx7f9plgr8msjatkv0dw2ne8gguwfjqr6xyjp50", + "bc1qm6tw2c0u842qjs7g2n2c7ulh76f6xn4sk0dsyt", ); static readonly BTC_NATIVE_SEGWIT_2 = new Account( diff --git a/apps/ledger-live-desktop/tests/enum/AppInfos.ts b/apps/ledger-live-desktop/tests/enum/AppInfos.ts index b3cbf78e879a..161ea689268a 100644 --- a/apps/ledger-live-desktop/tests/enum/AppInfos.ts +++ b/apps/ledger-live-desktop/tests/enum/AppInfos.ts @@ -5,12 +5,12 @@ export class AppInfos { public readonly name: string, public readonly sendPattern?: DeviceLabels[], public readonly receivePattern?: DeviceLabels[], - public readonly lsPattern?: DeviceLabels[], + public readonly delegatePattern?: DeviceLabels[], ) {} static readonly BITCOIN = new AppInfos( "Bitcoin", [ - DeviceLabels.AMOUT, + DeviceLabels.AMOUNT, DeviceLabels.ADDRESS, DeviceLabels.CONTINUE, DeviceLabels.REJECT, @@ -21,7 +21,7 @@ export class AppInfos { static readonly BITCOIN_TESTNET = new AppInfos( "Bitcoin Test", [ - DeviceLabels.AMOUT, + DeviceLabels.AMOUNT, DeviceLabels.ADDRESS, DeviceLabels.CONTINUE, DeviceLabels.REJECT, @@ -31,47 +31,48 @@ export class AppInfos { ); static readonly DOGECOIN = new AppInfos( "Dogecoin", - [DeviceLabels.AMOUT, DeviceLabels.ADDRESS, DeviceLabels.ACCEPT, DeviceLabels.REJECT], + [DeviceLabels.AMOUNT, DeviceLabels.ADDRESS, DeviceLabels.ACCEPT, DeviceLabels.REJECT], [DeviceLabels.ADDRESS, DeviceLabels.APPROVE, DeviceLabels.REJECT], ); static readonly ETHEREUM = new AppInfos( "Ethereum", - [DeviceLabels.AMOUT, DeviceLabels.TO, DeviceLabels.ACCEPT, DeviceLabels.REJECT], + [DeviceLabels.AMOUNT, DeviceLabels.TO, DeviceLabels.ACCEPT, DeviceLabels.REJECT], [DeviceLabels.ADDRESS, DeviceLabels.APPROVE, DeviceLabels.REJECT], ); static readonly ETHEREUM_HOLESKY = new AppInfos( "Ethereum Holesky", - [DeviceLabels.AMOUT, DeviceLabels.TO, DeviceLabels.ACCEPT, DeviceLabels.REJECT], + [DeviceLabels.AMOUNT, DeviceLabels.TO, DeviceLabels.ACCEPT, DeviceLabels.REJECT], [DeviceLabels.ADDRESS, DeviceLabels.APPROVE, DeviceLabels.REJECT], ); static readonly ETHEREUM_SEPOLIA = new AppInfos( "Ethereum Sepolia", - [DeviceLabels.AMOUT, DeviceLabels.TO, DeviceLabels.ACCEPT, DeviceLabels.REJECT], + [DeviceLabels.AMOUNT, DeviceLabels.TO, DeviceLabels.ACCEPT, DeviceLabels.REJECT], [DeviceLabels.ADDRESS, DeviceLabels.APPROVE, DeviceLabels.REJECT], ); static readonly ETHEREUM_CLASSIC = new AppInfos( "Ethereum Classic", - [DeviceLabels.AMOUT, DeviceLabels.TO, DeviceLabels.ACCEPT, DeviceLabels.REJECT], + [DeviceLabels.AMOUNT, DeviceLabels.TO, DeviceLabels.ACCEPT, DeviceLabels.REJECT], [DeviceLabels.ADDRESS, DeviceLabels.APPROVE, DeviceLabels.REJECT], ); static readonly SOLANA = new AppInfos( "Solana", [DeviceLabels.TRANSFER, DeviceLabels.RECIPIENT, DeviceLabels.APPROVE, DeviceLabels.REJECT], [DeviceLabels.PUBKEY, DeviceLabels.APPROVE, DeviceLabels.REJECT], + [DeviceLabels.DELEGATE_FROM, DeviceLabels.DEPOSIT, DeviceLabels.APPROVE, DeviceLabels.REJECT], ); static readonly POLKADOT = new AppInfos( "Polkadot", - [DeviceLabels.DEST, DeviceLabels.AMOUT, DeviceLabels.CAPS_APPROVE, DeviceLabels.CAPS_REJECT], + [DeviceLabels.DEST, DeviceLabels.AMOUNT, DeviceLabels.CAPS_APPROVE, DeviceLabels.CAPS_REJECT], [DeviceLabels.ADDRESS, DeviceLabels.CAPS_APPROVE, DeviceLabels.CAPS_REJECT], ); static readonly TRON = new AppInfos( "Tron", - [DeviceLabels.AMOUT, DeviceLabels.TO, DeviceLabels.SIGN, DeviceLabels.CANCEL], + [DeviceLabels.AMOUNT, DeviceLabels.TO, DeviceLabels.SIGN, DeviceLabels.CANCEL], [DeviceLabels.ADDRESS, DeviceLabels.APPROVE, DeviceLabels.CANCEL], ); static readonly RIPPLE = new AppInfos( "Ripple", - [DeviceLabels.AMOUT, DeviceLabels.DESTINATION, DeviceLabels.SIGN, DeviceLabels.REJECT], + [DeviceLabels.AMOUNT, DeviceLabels.DESTINATION, DeviceLabels.SIGN, DeviceLabels.REJECT], [DeviceLabels.ADDRESS, DeviceLabels.APPROVE, DeviceLabels.REJECT], ); static readonly CARDANO = new AppInfos( @@ -86,13 +87,13 @@ export class AppInfos { ); static readonly BITCOIN_CASH = new AppInfos( "Bitcoin Cash", - [DeviceLabels.AMOUT, DeviceLabels.ADDRESS, DeviceLabels.ACCEPT, DeviceLabels.REJECT], + [DeviceLabels.AMOUNT, DeviceLabels.ADDRESS, DeviceLabels.ACCEPT, DeviceLabels.REJECT], [DeviceLabels.ADDRESS, DeviceLabels.APPROVE, DeviceLabels.REJECT], ); static readonly ALGORAND = new AppInfos( "Algorand", [ - DeviceLabels.AMOUT, + DeviceLabels.AMOUNT, DeviceLabels.RECEIVER, DeviceLabels.CAPS_APPROVE, DeviceLabels.CAPS_REJECT, @@ -101,35 +102,51 @@ export class AppInfos { ); static readonly COSMOS = new AppInfos( "Cosmos", - [DeviceLabels.AMOUT, DeviceLabels.TO, DeviceLabels.CAPS_APPROVE, DeviceLabels.CAPS_REJECT], + [DeviceLabels.AMOUNT, DeviceLabels.TO, DeviceLabels.CAPS_APPROVE, DeviceLabels.CAPS_REJECT], [DeviceLabels.ADDRESS, DeviceLabels.CAPS_APPROVE, DeviceLabels.CAPS_REJECT], + [ + DeviceLabels.PLEASE_REVIEW, + DeviceLabels.AMOUNT, + DeviceLabels.CAPS_APPROVE, + DeviceLabels.CAPS_REJECT, + ], ); static readonly TEZOS = new AppInfos( "Tezos", - [DeviceLabels.AMOUT, DeviceLabels.DESTINATION, DeviceLabels.ACCEPT, DeviceLabels.REJECT], + [DeviceLabels.AMOUNT, DeviceLabels.DESTINATION, DeviceLabels.ACCEPT, DeviceLabels.REJECT], [DeviceLabels.ADDRESS, DeviceLabels.APPROVE, DeviceLabels.REJECT], ); static readonly POLYGON = new AppInfos( "Polygon", - [DeviceLabels.AMOUT, DeviceLabels.ADDRESS, DeviceLabels.ACCEPT, DeviceLabels.REJECT], + [DeviceLabels.AMOUNT, DeviceLabels.ADDRESS, DeviceLabels.ACCEPT, DeviceLabels.REJECT], [DeviceLabels.ADDRESS, DeviceLabels.APPROVE, DeviceLabels.REJECT], ); static readonly BINANCE_SMART_CHAIN = new AppInfos( "Binance Smart Chain", - [DeviceLabels.AMOUT, DeviceLabels.ADDRESS, DeviceLabels.ACCEPT, DeviceLabels.REJECT], + [DeviceLabels.AMOUNT, DeviceLabels.ADDRESS, DeviceLabels.ACCEPT, DeviceLabels.REJECT], [DeviceLabels.ADDRESS, DeviceLabels.APPROVE, DeviceLabels.REJECT], ); static readonly TON = new AppInfos( "Ton", - [DeviceLabels.AMOUT, DeviceLabels.TO, DeviceLabels.APPROVE, DeviceLabels.REJECT], + [DeviceLabels.AMOUNT, DeviceLabels.TO, DeviceLabels.APPROVE, DeviceLabels.REJECT], [DeviceLabels.ADDRESS, DeviceLabels.APPROVE, DeviceLabels.REJECT], ); static readonly NEAR = new AppInfos( "Near", - [DeviceLabels.AMOUT, DeviceLabels.DESTINATION, DeviceLabels.ACCEPT, DeviceLabels.REJECT], + [DeviceLabels.AMOUNT, DeviceLabels.DESTINATION, DeviceLabels.ACCEPT, DeviceLabels.REJECT], [DeviceLabels.WALLET_ID, DeviceLabels.APPROVE, DeviceLabels.REJECT], + [ + DeviceLabels.VIEW_HEADER, + DeviceLabels.RECEIVER, + DeviceLabels.CONTINUE_TO_ACTION, + DeviceLabels.VIEW_ACTION, + DeviceLabels.METHOD_NAME, + DeviceLabels.DEPOSIT, + DeviceLabels.REJECT, + DeviceLabels.SIGN, + ], ); - static readonly LS = new AppInfos("LedgerSync", [DeviceLabels.LOGIN_LEDGER_SYNC]); + static readonly LS = new AppInfos("LedgerSync"); static readonly EXCHANGE = new AppInfos("Exchange", [ DeviceLabels.SEND, DeviceLabels.GET, diff --git a/apps/ledger-live-desktop/tests/enum/DeviceLabels.ts b/apps/ledger-live-desktop/tests/enum/DeviceLabels.ts index c008d9e1db97..40d3767992e4 100644 --- a/apps/ledger-live-desktop/tests/enum/DeviceLabels.ts +++ b/apps/ledger-live-desktop/tests/enum/DeviceLabels.ts @@ -1,6 +1,6 @@ export enum DeviceLabels { CONTINUE = "Continue", - AMOUT = "Amount", + AMOUNT = "Amount", ADDRESS = "Address", REJECT = "Reject", SIGN = "Sign", @@ -23,9 +23,15 @@ export enum DeviceLabels { TRANSACTION_FEE = "Transaction fee", FINALIZE = "Finalize", RECEIVER = "Receiver", - LOGIN_LEDGER_SYNC = "Log in to Ledger Sync", GET = "Get", FEES = "Fees", ACCEPT_AND_SEND = "Accept and send", WALLET_ID = "Wallet ID", + VIEW_HEADER = "View Header", + CONTINUE_TO_ACTION = "Continue to action", + VIEW_ACTION = "View action", + METHOD_NAME = "Method name", + DEPOSIT = "Deposit", + DELEGATE_FROM = "Delegate from", + PLEASE_REVIEW = "Please review", } diff --git a/apps/ledger-live-desktop/tests/models/Delegate.ts b/apps/ledger-live-desktop/tests/models/Delegate.ts new file mode 100644 index 000000000000..f4142fb9b65e --- /dev/null +++ b/apps/ledger-live-desktop/tests/models/Delegate.ts @@ -0,0 +1,9 @@ +import { Account } from "../enum/Account"; + +export class Delegate { + constructor( + public account: Account, + public amount: string, + public provider: string, + ) {} +} diff --git a/apps/ledger-live-desktop/tests/page/drawer/delegate.drawer.ts b/apps/ledger-live-desktop/tests/page/drawer/delegate.drawer.ts new file mode 100644 index 000000000000..bec74710af40 --- /dev/null +++ b/apps/ledger-live-desktop/tests/page/drawer/delegate.drawer.ts @@ -0,0 +1,50 @@ +import { step } from "tests/misc/reporters/step"; +import { Drawer } from "tests/component/drawer.component"; +import { Currency } from "tests/enum/Currency"; +import { Delegate } from "tests/models/Delegate"; +import { expect } from "@playwright/test"; +import { Transaction } from "tests/models/Transaction"; + +export class DelegateDrawer extends Drawer { + private provider = (provider: string) => + this.page.getByTestId("drawer-content").locator(`text=${provider}`); + private amountValue = this.page.getByTestId("amountReceived-drawer"); + private transactionType = this.page.getByTestId("transaction-type"); + private addressValue = (address: string) => + this.page.locator('[data-testid="drawer-content"]').locator(`text=${address}`); + + @step("Verify address $0 is visible") + async addressValueIsVisible(address: string) { + await expect(this.addressValue(address)).toBeVisible(); + } + + @step("Verify provider is visible") + async providerIsVisible(account: Delegate) { + if (account.account.currency === Currency.ATOM) { + await expect(this.provider(account.provider)).toBeVisible(); + } + } + + @step("Verify amount is visible") + async amountValueIsVisible() { + await expect(this.amountValue).toBeVisible(); + } + + @step("Verify transaction type is correct") + async transactionTypeIsVisible() { + await expect(this.transactionType).toBeVisible(); + } + + @step("Verify that the information of the transaction is visible") + async expectReceiverInfos(tx: Transaction) { + await expect(this.addressValue(tx.accountToCredit.address)).toBeVisible(); + await expect(this.amountValue).toBeVisible(); + } + + @step("Verify that the information of the delegation is visible") + async expectDelegationInfos(delegationInfo: Delegate) { + await this.providerIsVisible(delegationInfo); + await this.amountValueIsVisible(); + await this.transactionTypeIsVisible(); + } +} diff --git a/apps/ledger-live-desktop/tests/page/drawer/ledger.sync.drawer.ts b/apps/ledger-live-desktop/tests/page/drawer/ledger.sync.drawer.ts index 09e6e346544f..bf9b0c40b3c4 100644 --- a/apps/ledger-live-desktop/tests/page/drawer/ledger.sync.drawer.ts +++ b/apps/ledger-live-desktop/tests/page/drawer/ledger.sync.drawer.ts @@ -1,6 +1,6 @@ import { step } from "tests/misc/reporters/step"; import { expect } from "@playwright/test"; -import { Drawer } from "./drawer"; +import { Drawer } from "../../component/drawer.component"; import { extractNumberFromText } from "tests/utils/textParserUtils"; export class LedgerSyncDrawer extends Drawer { diff --git a/apps/ledger-live-desktop/tests/page/drawer/send.drawer.ts b/apps/ledger-live-desktop/tests/page/drawer/send.drawer.ts new file mode 100644 index 000000000000..7d94ac086d32 --- /dev/null +++ b/apps/ledger-live-desktop/tests/page/drawer/send.drawer.ts @@ -0,0 +1,21 @@ +import { step } from "tests/misc/reporters/step"; +import { Drawer } from "tests/component/drawer.component"; +import { expect } from "@playwright/test"; +import { Transaction } from "tests/models/Transaction"; + +export class SendDrawer extends Drawer { + private addressValue = (address: string) => + this.page.locator('[data-testid="drawer-content"]').locator(`text=${address}`); + private amountValue = this.page.getByTestId("amountReceived-drawer"); + + @step("Verify address is visible") + async addressValueIsVisible(address: string) { + await expect(this.addressValue(address)).toBeVisible(); + } + + @step("Verify that the information of the transaction is visible") + async expectReceiverInfos(tx: Transaction) { + await expect(this.addressValue(tx.accountToCredit.address)).toBeVisible(); + await expect(this.amountValue).toBeVisible(); + } +} diff --git a/apps/ledger-live-desktop/tests/page/drawer/swap.confirmation.drawer.ts b/apps/ledger-live-desktop/tests/page/drawer/swap.confirmation.drawer.ts index f4a9a49836df..8220e5179ff2 100644 --- a/apps/ledger-live-desktop/tests/page/drawer/swap.confirmation.drawer.ts +++ b/apps/ledger-live-desktop/tests/page/drawer/swap.confirmation.drawer.ts @@ -1,12 +1,12 @@ import { expect } from "@playwright/test"; import { step } from "tests/misc/reporters/step"; -import { Drawer } from "tests/page/drawer/drawer"; +import { Drawer } from "tests/component/drawer.component"; import { capitalizeFirstLetter } from "tests/utils/textParserUtils"; export class SwapConfirmationDrawer extends Drawer { private amountSent = this.page.getByTestId("amountSent"); private amountReceived = this.page.getByTestId("amountReceived"); - private provider = this.page.getByTestId("provider"); + private swapProvider = this.page.getByTestId("provider"); private fees = this.page.getByTestId("fees"); private sourceAccount = this.page.getByTestId("sourceAccount"); private targetAccount = this.page.getByTestId("targetAccount"); @@ -24,7 +24,7 @@ export class SwapConfirmationDrawer extends Drawer { @step("Verify provider: $0") async verifyProvider(provider: string) { - await expect(this.provider).toHaveText(capitalizeFirstLetter(provider)); + await expect(this.swapProvider).toHaveText(capitalizeFirstLetter(provider)); } @step("Verify source currency: $0") diff --git a/apps/ledger-live-desktop/tests/page/index.ts b/apps/ledger-live-desktop/tests/page/index.ts index b26c41b8f2d2..fc3e9b512d4c 100644 --- a/apps/ledger-live-desktop/tests/page/index.ts +++ b/apps/ledger-live-desktop/tests/page/index.ts @@ -8,12 +8,14 @@ import { Modal } from "../component/modal.component"; import { ReceiveModal } from "../page/modal/receive.modal"; import { SpeculosPage } from "tests/page/speculos.page"; import { SendModal } from "tests/page/modal/send.modal"; -import { Drawer } from "tests/page/drawer/drawer"; +import { Drawer } from "tests/component/drawer.component"; import { SettingsPage } from "tests/page/settings.page"; import { LedgerSyncDrawer } from "./drawer/ledger.sync.drawer"; import { SwapPage } from "tests/page/swap.page"; import { SwapConfirmationDrawer } from "tests/page/drawer/swap.confirmation.drawer"; import { delegateModal } from "tests/page/modal/delegate.modal"; +import { DelegateDrawer } from "./drawer/delegate.drawer"; +import { SendDrawer } from "./drawer/send.drawer"; export class Application extends PageHolder { public account = new AccountPage(this.page); @@ -31,4 +33,6 @@ export class Application extends PageHolder { public ledgerSync = new LedgerSyncDrawer(this.page); public swap = new SwapPage(this.page); public swapDrawer = new SwapConfirmationDrawer(this.page); + public delegateDrawer = new DelegateDrawer(this.page); + public sendDrawer = new SendDrawer(this.page); } diff --git a/apps/ledger-live-desktop/tests/page/modal/delegate.modal.ts b/apps/ledger-live-desktop/tests/page/modal/delegate.modal.ts index 1df2c2d52bb5..e9eaf9e1ebe9 100644 --- a/apps/ledger-live-desktop/tests/page/modal/delegate.modal.ts +++ b/apps/ledger-live-desktop/tests/page/modal/delegate.modal.ts @@ -4,13 +4,14 @@ import { step } from "tests/misc/reporters/step"; export class delegateModal extends Modal { private titleProvider = this.page.getByTestId("modal-provider-title"); - private delegateContinueButton = this.page.locator("id=delegate-continue-button"); + private delegateContinueButton = this.page.getByText("Continue"); private rowProvider = this.page.getByTestId("modal-provider-row"); private searchOpenButton = this.page.getByText("Show all"); private searchCloseButton = this.page.getByText("Show less"); private inputSearchField = this.page.getByPlaceholder("Search by name or address..."); private stakeProviderContainer = (stakeProviderID: string) => this.page.getByTestId(`stake-provider-container-${stakeProviderID}`); + private detailsButton = this.page.getByRole("button", { name: "View details" }); @step("Get title provider") async getTitleProvider() { @@ -51,4 +52,18 @@ export class delegateModal extends Modal { async chooseStakeProvider(stakeProvider: string) { await this.stakeProviderContainer(stakeProvider).click(); } + + @step("Click on view details button") + async clickViewDetailsButton() { + await this.detailsButton.click(); + } + + @step("Fill amount $0") + async fillAmount(amount: string) { + if (amount == "send max") { + await this.toggleMaxAmount(); + } else { + await this.cryptoAmountField.fill(amount); + } + } } diff --git a/apps/ledger-live-desktop/tests/page/modal/send.modal.ts b/apps/ledger-live-desktop/tests/page/modal/send.modal.ts index 7d829d3aadb3..acd0af18873a 100644 --- a/apps/ledger-live-desktop/tests/page/modal/send.modal.ts +++ b/apps/ledger-live-desktop/tests/page/modal/send.modal.ts @@ -76,15 +76,6 @@ export class SendModal extends Modal { await expect(this.continueButton).toBeEnabled(); } - @step("Fill amount") - async fillAmount(amount: string) { - if (amount == "send max") { - await this.toggleMaxAmount(); - } else { - await this.cryptoAmountField.fill(amount); - } - } - @step("Click `Continue` button") async clickContinue() { await this.continueButton.click(); @@ -94,4 +85,13 @@ export class SendModal extends Modal { async checkContinueButtonDisabled() { await expect(this.continueButton).toBeDisabled(); } + + @step("Fill amount $0") + async fillAmount(amount: string) { + if (amount == "send max") { + await this.toggleMaxAmount(); + } else { + await this.cryptoAmountField.fill(amount); + } + } } diff --git a/apps/ledger-live-desktop/tests/page/speculos.page.ts b/apps/ledger-live-desktop/tests/page/speculos.page.ts index 1e4a1993e239..d982e9795325 100644 --- a/apps/ledger-live-desktop/tests/page/speculos.page.ts +++ b/apps/ledger-live-desktop/tests/page/speculos.page.ts @@ -1,17 +1,18 @@ import { AppPage } from "tests/page/abstractClasses"; import { step } from "tests/misc/reporters/step"; - import { pressBoth, pressRightUntil, verifyAddress as assertAddressesEquality, verifyAmount, verifySwapFeesAmount, + verifyProvider, waitFor, } from "@ledgerhq/live-common/e2e/speculos"; import { Account } from "../enum/Account"; import { expect } from "@playwright/test"; import { Transaction } from "tests/models/Transaction"; +import { Delegate } from "tests/models/Delegate"; import { DeviceLabels } from "tests/enum/DeviceLabels"; import { Currency } from "tests/enum/Currency"; import { Swap } from "tests/models/Swap"; @@ -86,4 +87,62 @@ export class SpeculosPage extends AppPage { await pressRightUntil(DeviceLabels.REJECT); await pressBoth(); } + + @step("Delegate Method - Solana") + async delegateSolana(delegatingAccount: Delegate) { + const { delegatePattern } = delegatingAccount.account.currency.speculosApp || {}; + if (!delegatePattern) { + return; + } + await waitFor(delegatePattern[0]); + await pressRightUntil(delegatePattern[2]); + await pressBoth(); + } + + @step("Delegate Method - Near") + async delegateNear(delegatingAccount: Delegate) { + const { delegatePattern } = delegatingAccount.account.currency.speculosApp || {}; + if (!delegatePattern) { + return; + } + await waitFor(delegatePattern[0]); + const provider = await pressRightUntil(delegatePattern[1]); + expect(verifyProvider(delegatingAccount.provider, provider)).toBe(true); + await pressRightUntil(delegatePattern[2]); + await pressBoth(); + await waitFor(delegatePattern[3]); + await pressRightUntil(delegatePattern[7]); + await pressBoth(); + } + + @step("Delegate Method - Cosmos") + async delegateCosmos(delegatingAccount: Delegate) { + const { delegatePattern } = delegatingAccount.account.currency.speculosApp || {}; + if (!delegatePattern) { + return; + } + await waitFor(delegatePattern[0]); + const amount = await pressRightUntil(delegatePattern[1]); + expect(verifyAmount(delegatingAccount.amount, amount)).toBe(true); + await pressRightUntil(delegatePattern[2]); + await pressBoth(); + } + + @step("Sign Delegation Transaction") + async signDelegationTransaction(delegatingAccount: Delegate) { + const currencyName = delegatingAccount.account.currency.name; + switch (currencyName) { + case Account.SOL_1.currency.name: + await this.delegateSolana(delegatingAccount); + break; + case Account.NEAR_1.currency.name: + await this.delegateNear(delegatingAccount); + break; + case Account.ATOM_1.currency.name: + await this.delegateCosmos(delegatingAccount); + break; + default: + throw new Error(`Unsupported currency: ${currencyName}`); + } + } } diff --git a/apps/ledger-live-desktop/tests/specs/general/layout.spec.ts b/apps/ledger-live-desktop/tests/specs/general/layout.spec.ts index d90afb201666..80c6f31f9506 100644 --- a/apps/ledger-live-desktop/tests/specs/general/layout.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/general/layout.spec.ts @@ -1,7 +1,7 @@ import test from "../../fixtures/common"; import { expect } from "@playwright/test"; import { Layout } from "../../component/layout.component"; -import { Drawer } from "../../page/drawer/drawer"; +import { Drawer } from "../../component/drawer.component"; import { SendModal } from "../../page/modal/send.modal"; import { ReceiveModal } from "../../page/modal/receive.modal"; import { SettingsPage } from "../../page/settings.page"; diff --git a/apps/ledger-live-desktop/tests/specs/manager/devicelocalization.spec.ts b/apps/ledger-live-desktop/tests/specs/manager/devicelocalization.spec.ts index 61057b8eba73..03702b43a09a 100644 --- a/apps/ledger-live-desktop/tests/specs/manager/devicelocalization.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/manager/devicelocalization.spec.ts @@ -4,7 +4,7 @@ import { ManagerPage } from "../../page/manager.page"; import { LanguageInstallation } from "../../page/drawer/language.installation.drawer"; import { DeviceAction } from "../../models/DeviceAction"; import { Layout } from "../../component/layout.component"; -import { Drawer } from "tests/page/drawer/drawer"; +import { Drawer } from "tests/component/drawer.component"; test.use({ userdata: "skip-onboarding" }); test.use({ env: { FORCE_PROVIDER: "12" } }); diff --git a/apps/ledger-live-desktop/tests/specs/services/confirmTransaction.spec.ts b/apps/ledger-live-desktop/tests/specs/services/confirmTransaction.spec.ts index 6a4774ec6dc1..3f0d31223bec 100644 --- a/apps/ledger-live-desktop/tests/specs/services/confirmTransaction.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/services/confirmTransaction.spec.ts @@ -3,7 +3,7 @@ import { expect } from "@playwright/test"; import { Modal } from "../../component/modal.component"; import { DiscoverPage } from "../../page/discover.page"; import { Layout } from "../../component/layout.component"; -import { Drawer } from "../../page/drawer/drawer"; +import { Drawer } from "../../component/drawer.component"; import { DeviceAction } from "../../models/DeviceAction"; import { randomUUID } from "crypto"; import { LiveAppWebview } from "../../models/LiveAppWebview"; diff --git a/apps/ledger-live-desktop/tests/specs/services/cosmosStaking.spec.ts b/apps/ledger-live-desktop/tests/specs/services/cosmosStaking.spec.ts index 6c68e4e7cfc0..52113cb6d44c 100644 --- a/apps/ledger-live-desktop/tests/specs/services/cosmosStaking.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/services/cosmosStaking.spec.ts @@ -1,6 +1,6 @@ import test from "../../fixtures/common"; import { expect } from "@playwright/test"; -import { Drawer } from "../../page/drawer/drawer"; +import { Drawer } from "../../component/drawer.component"; import { Modal } from "../../component/modal.component"; import { PortfolioPage } from "../../page/portfolio.page"; import { Layout } from "../../component/layout.component"; diff --git a/apps/ledger-live-desktop/tests/specs/services/dapp.spec.ts b/apps/ledger-live-desktop/tests/specs/services/dapp.spec.ts index 27280a6a4028..e737e6f24c5c 100644 --- a/apps/ledger-live-desktop/tests/specs/services/dapp.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/services/dapp.spec.ts @@ -4,7 +4,7 @@ import { DiscoverPage } from "../../page/discover.page"; import { Layout } from "../../component/layout.component"; import { WebviewLayout } from "../../component/webviewLayout.component"; -import { Drawer } from "../../page/drawer/drawer"; +import { Drawer } from "../../component/drawer.component"; import { Modal } from "../../component/modal.component"; import { DeviceAction } from "../../models/DeviceAction"; import dummyLiveApp from "./dapp.spec.ts-mocks/dummy-live-app"; diff --git a/apps/ledger-live-desktop/tests/specs/services/ethereumStaking.spec.ts b/apps/ledger-live-desktop/tests/specs/services/ethereumStaking.spec.ts index 1a18efd8a80a..ae23241b0aa5 100644 --- a/apps/ledger-live-desktop/tests/specs/services/ethereumStaking.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/services/ethereumStaking.spec.ts @@ -1,7 +1,7 @@ import test from "../../fixtures/common"; import { expect } from "@playwright/test"; import { Analytics } from "../../models/Analytics"; -import { Drawer } from "../../page/drawer/drawer"; +import { Drawer } from "../../component/drawer.component"; import { Modal } from "../../component/modal.component"; import { PortfolioPage } from "../../page/portfolio.page"; import { LiveAppWebview } from "../../models/LiveAppWebview"; diff --git a/apps/ledger-live-desktop/tests/specs/services/liveapp-sdk.spec.ts b/apps/ledger-live-desktop/tests/specs/services/liveapp-sdk.spec.ts index bc65ef58a175..17103341a189 100644 --- a/apps/ledger-live-desktop/tests/specs/services/liveapp-sdk.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/services/liveapp-sdk.spec.ts @@ -2,7 +2,7 @@ import test from "../../fixtures/common"; import { expect } from "@playwright/test"; import { DiscoverPage } from "../../page/discover.page"; import { Layout } from "../../component/layout.component"; -import { Drawer } from "../../page/drawer/drawer"; +import { Drawer } from "../../component/drawer.component"; import { Modal } from "../../component/modal.component"; import { DeviceAction } from "../../models/DeviceAction"; import { LiveAppWebview } from "../../models/LiveAppWebview"; diff --git a/apps/ledger-live-desktop/tests/specs/services/swap.spec.ts b/apps/ledger-live-desktop/tests/specs/services/swap.spec.ts index a392f5dfce1b..52ef6cad60a1 100644 --- a/apps/ledger-live-desktop/tests/specs/services/swap.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/services/swap.spec.ts @@ -6,7 +6,7 @@ import test from "../../fixtures/mockFixtures"; import { DeviceAction } from "../../models/DeviceAction"; import { AccountPage } from "../../page/account.page"; import { AccountsPage } from "../../page/accounts.page"; -import { Drawer } from "../../page/drawer/drawer"; +import { Drawer } from "../../component/drawer.component"; import { SwapPage } from "../../page/swap.page"; import { getBitcoinToDogecoinRatesMock, diff --git a/apps/ledger-live-desktop/tests/specs/services/wallet-api.spec.ts b/apps/ledger-live-desktop/tests/specs/services/wallet-api.spec.ts index f287adbb4842..78620026ecf8 100644 --- a/apps/ledger-live-desktop/tests/specs/services/wallet-api.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/services/wallet-api.spec.ts @@ -2,7 +2,7 @@ import test from "../../fixtures/common"; import { expect } from "@playwright/test"; import { DiscoverPage } from "../../page/discover.page"; import { Layout } from "../../component/layout.component"; -import { Drawer } from "../../page/drawer/drawer"; +import { Drawer } from "../../component/drawer.component"; import { Modal } from "../../component/modal.component"; import { DeviceAction } from "../../models/DeviceAction"; import { randomUUID } from "crypto"; diff --git a/apps/ledger-live-desktop/tests/specs/speculos/delegate.spec.ts b/apps/ledger-live-desktop/tests/specs/speculos/delegate.spec.ts index fdf0dbce837c..c315558cb9e9 100644 --- a/apps/ledger-live-desktop/tests/specs/speculos/delegate.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/speculos/delegate.spec.ts @@ -1,23 +1,21 @@ import { test } from "../../fixtures/common"; import { Account } from "../../enum/Account"; +import { Delegate } from "../../models/Delegate"; import { addTmsLink } from "tests/utils/allureUtils"; import { getDescription } from "../../utils/customJsonReporter"; const accounts = [ { - account: Account.ATOM_1, - xrayTicket: "B2CQA-2731", - provider: "Ledger", + delegate: new Delegate(Account.ATOM_1, "0.001", "Ledger"), + xrayTicket: "B2CQA-2731, B2CQA-2740", }, { - account: Account.SOL_1, - xrayTicket: "B2CQA-2730", - provider: "Ledger by Figment", + delegate: new Delegate(Account.SOL_1, "0.001", "Ledger by Figment"), + xrayTicket: "B2CQA-2730, B2CQA-2742", }, { - account: Account.NEAR_1, - xrayTicket: "B2CQA-2732", - provider: "ledgerbyfigment.poolv1.near", + delegate: new Delegate(Account.NEAR_1, "0.01", "ledgerbyfigment.poolv1.near"), + xrayTicket: "B2CQA-2732, B2CQA-2741", }, ]; @@ -25,10 +23,11 @@ for (const account of accounts) { test.describe("Delegate", () => { test.use({ userdata: "speculos-delegate", + speculosApp: account.delegate.account.currency.speculosApp, }); test( - `[${account.account.currency.name}] Delegate`, + `[${account.delegate.account.currency.name}] Delegate`, { annotation: { type: "TMS", @@ -38,10 +37,27 @@ for (const account of accounts) { async ({ app }) => { await addTmsLink(getDescription(test.info().annotations).split(", ")); await app.layout.goToAccounts(); - await app.accounts.navigateToAccountByName(account.account.accountName); + await app.accounts.navigateToAccountByName(account.delegate.account.accountName); await app.account.clickBannerCTA(); - await app.delegate.verifyProvider(account.provider); + await app.delegate.verifyProvider(account.delegate.provider); + + await app.delegate.continueDelegate(); + await app.delegate.fillAmount(account.delegate.amount); + await app.modal.countinueSendAmount(); + + await app.speculos.signDelegationTransaction(account.delegate); + await app.delegate.clickViewDetailsButton(); + + await app.drawer.waitForDrawerToBeVisible(); + await app.delegateDrawer.transactionTypeIsVisible(); + await app.delegateDrawer.providerIsVisible(account.delegate); + await app.delegateDrawer.amountValueIsVisible(); + await app.drawer.close(); + + await app.layout.syncAccounts(); + await app.account.clickOnLastOperation(); + await app.delegateDrawer.expectDelegationInfos(account.delegate); }, ); }); diff --git a/apps/ledger-live-desktop/tests/specs/speculos/send.tx.spec.ts b/apps/ledger-live-desktop/tests/specs/speculos/send.tx.spec.ts index c14dc91662ba..883f40ca9414 100644 --- a/apps/ledger-live-desktop/tests/specs/speculos/send.tx.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/speculos/send.tx.spec.ts @@ -185,7 +185,7 @@ for (const transaction of transactionE2E) { await app.speculos.expectValidTxInfo(transaction.transaction); await app.send.expectTxSent(); await app.account.navigateToViewDetails(); - await app.drawer.addressValueIsVisible(transaction.transaction.accountToCredit.address); + await app.sendDrawer.addressValueIsVisible(transaction.transaction.accountToCredit.address); await app.drawer.close(); await app.layout.goToAccounts(); @@ -194,7 +194,7 @@ for (const transaction of transactionE2E) { ); await app.layout.syncAccounts(); await app.account.clickOnLastOperation(); - await app.drawer.expectReceiverInfos(transaction.transaction); + await app.sendDrawer.expectReceiverInfos(transaction.transaction); }, ); }); @@ -366,6 +366,7 @@ test.describe("Verify send max user flow", () => { }, }, async ({ app }) => { + await addTmsLink(getDescription(test.info().annotations).split(", ")); await app.layout.goToAccounts(); await app.accounts.navigateToAccountByName(transactionInputValid.accountToDebit.accountName); @@ -386,7 +387,7 @@ for (const transaction of transactionAddressValid) { }); test( - `Check button enabled ${transaction.xrayTicket} - valid address input`, + `Check button enabled (from ${transaction.transaction.accountToDebit} to ${transaction.transaction.accountToCredit}) - valid address input ${transaction.xrayTicket}`, { annotation: { type: "TMS", @@ -394,6 +395,7 @@ for (const transaction of transactionAddressValid) { }, }, async ({ app }) => { + await addTmsLink(getDescription(test.info().annotations).split(", ")); await app.layout.goToAccounts(); await app.accounts.navigateToAccountByName( transaction.transaction.accountToDebit.accountName, @@ -415,7 +417,7 @@ for (const transaction of transactionsAddressInvalid) { }); test( - `Check "${transaction.expectedErrorMessage}" (${transaction.xrayTicket}) - invalid address input error`, + `Check "${transaction.expectedErrorMessage}" (from ${transaction.transaction.accountToDebit} to ${transaction.transaction.accountToCredit}) - invalid address input error${transaction.xrayTicket}`, { annotation: { type: "TMS", @@ -423,6 +425,7 @@ for (const transaction of transactionsAddressInvalid) { }, }, async ({ app }) => { + await addTmsLink(getDescription(test.info().annotations).split(", ")); await app.layout.goToAccounts(); await app.accounts.navigateToAccountByName( transaction.transaction.accountToDebit.accountName, diff --git a/libs/ledger-live-common/src/e2e/speculos.ts b/libs/ledger-live-common/src/e2e/speculos.ts index 64ca13d49fb2..a688762d58f5 100644 --- a/libs/ledger-live-common/src/e2e/speculos.ts +++ b/libs/ledger-live-common/src/e2e/speculos.ts @@ -375,6 +375,11 @@ export function verifyAmount(amount: string, text: string[]): boolean { return amountDevice.includes(amount); } +export function verifyProvider(provider: string, text: string[]): boolean { + const providerDevice = text.join(""); + return providerDevice.includes(provider); +} + export function verifySwapFeesAmount(amount: string, text: string[]): boolean { const amountDevice = text[3]; return amountDevice.includes(amount); From d036733429b2bdacf948a0555b557b61c6575267 Mon Sep 17 00:00:00 2001 From: Abdurrahman SASTIM Date: Wed, 2 Oct 2024 20:19:08 +0200 Subject: [PATCH 16/86] test: create CLI utils --- apps/cli/src/live-common-setup.ts | 19 ++++++++-- .../tests/fixtures/common.ts | 34 +++++++++-------- .../tests/utils/cliUtils.ts | 38 +++++++++++++++++++ 3 files changed, 72 insertions(+), 19 deletions(-) create mode 100644 apps/ledger-live-desktop/tests/utils/cliUtils.ts diff --git a/apps/cli/src/live-common-setup.ts b/apps/cli/src/live-common-setup.ts index 8cd83cadc60c..381739bdfa7d 100644 --- a/apps/cli/src/live-common-setup.ts +++ b/apps/cli/src/live-common-setup.ts @@ -15,6 +15,9 @@ import { checkLibs } from "@ledgerhq/live-common/sanityChecks"; import { closeAllSpeculosDevices } from "@ledgerhq/live-common/load/speculos"; import { LiveConfig } from "@ledgerhq/live-config/LiveConfig"; import { liveConfig } from "@ledgerhq/live-common/config/sharedConfig"; +import SpeculosHttpTransport, { + SpeculosHttpTransportOpts, +} from "../../../libs/ledgerjs/packages/hw-transport-node-speculos-http/lib/SpeculosHttpTransport"; checkLibs({ NotEnoughBalance, @@ -71,9 +74,19 @@ if (process.env.DEVICE_PROXY_URL) { }); } -const { SPECULOS_APDU_PORT, SPECULOS_BUTTON_PORT, SPECULOS_HOST } = process.env; +const { SPECULOS_API_PORT, SPECULOS_APDU_PORT, SPECULOS_BUTTON_PORT, SPECULOS_HOST } = process.env; -if (SPECULOS_APDU_PORT) { +if (SPECULOS_API_PORT) { + const req: Record = { + apiPort: parseInt(SPECULOS_API_PORT, 10), + }; + + registerTransportModule({ + id: "speculos-http", + open: () => retry(() => SpeculosHttpTransport.open(req as SpeculosHttpTransportOpts)), + disconnect: () => Promise.resolve(), + }); +} else if (SPECULOS_APDU_PORT) { const req: Record = { apduPort: parseInt(SPECULOS_APDU_PORT, 10), }; @@ -124,7 +137,7 @@ async function init() { LiveConfig.setConfig(liveConfig); -if (!process.env.CI) { +if (!process.env.CI && !SPECULOS_API_PORT) { init(); } diff --git a/apps/ledger-live-desktop/tests/fixtures/common.ts b/apps/ledger-live-desktop/tests/fixtures/common.ts index d6ef37942414..69487afed34a 100644 --- a/apps/ledger-live-desktop/tests/fixtures/common.ts +++ b/apps/ledger-live-desktop/tests/fixtures/common.ts @@ -88,24 +88,26 @@ export const test = base.extend({ let device: any | undefined; - if (IS_NOT_MOCK && speculosApp) { - // Ensure the portCounter stays within the valid port range - if (portCounter > MAX_PORT) { - portCounter = BASE_PORT; + try { + if (IS_NOT_MOCK && speculosApp) { + // Ensure the portCounter stays within the valid port range + if (portCounter > MAX_PORT) { + portCounter = BASE_PORT; + } + const speculosPort = portCounter++; + setEnv( + "SPECULOS_PID_OFFSET", + (speculosPort - BASE_PORT) * 1000 + parseInt(process.env.TEST_WORKER_INDEX || "0") * 100, + ); + device = await startSpeculos( + testInfo.title.replace(/ /g, "_"), + specs[speculosApp.name.replace(/ /g, "_")], + ); + setEnv("SPECULOS_API_PORT", device?.ports.apiPort?.toString()); + process.env.SPECULOS_API_PORT = device?.ports.apiPort; + process.env.MOCK = ""; } - const speculosPort = portCounter++; - setEnv( - "SPECULOS_PID_OFFSET", - (speculosPort - BASE_PORT) * 1000 + parseInt(process.env.TEST_WORKER_INDEX || "0") * 100, - ); - device = await startSpeculos( - testInfo.title.replace(/ /g, "_"), - specs[speculosApp.name.replace(/ /g, "_")], - ); - setEnv("SPECULOS_API_PORT", device?.ports.apiPort?.toString()); - } - try { // default environment variables env = Object.assign( { diff --git a/apps/ledger-live-desktop/tests/utils/cliUtils.ts b/apps/ledger-live-desktop/tests/utils/cliUtils.ts new file mode 100644 index 000000000000..0846b62a409d --- /dev/null +++ b/apps/ledger-live-desktop/tests/utils/cliUtils.ts @@ -0,0 +1,38 @@ +import { spawn } from "child_process"; + +const scriptPath = __dirname + "../../../../../apps/cli/bin/index.js"; + +/** + * Executes a command in the CLI with given arguments. + * @param {string} command - The command and its arguments as a single string. + * @returns {Promise} - Resolves with the output of the command or rejects on failure. + */ +export function runCliCommand(command: string): Promise { + return new Promise((resolve, reject) => { + const args = command.split(" "); + const child = spawn("node", [scriptPath, ...args], { stdio: "pipe" }); + + let output = ""; + let errorOutput = ""; + + child.stdout.on("data", data => { + output += data.toString(); + }); + + child.stderr.on("data", data => { + errorOutput += data.toString(); + }); + + child.on("exit", code => { + if (code === 0) { + resolve(output); + } else { + reject(new Error(`CLI command failed with exit code ${code}: ${errorOutput}`)); + } + }); + + child.on("error", error => { + reject(new Error(`Error executing CLI command: ${error.message}`)); + }); + }); +} From 032c9185aaaf1afcb574119431aa0daa83826c75 Mon Sep 17 00:00:00 2001 From: Abdurrahman SASTIM Date: Wed, 2 Oct 2024 20:39:16 +0200 Subject: [PATCH 17/86] ci: build CLI for Speculos tests --- .github/workflows/test-ui-e2e-only-desktop.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/test-ui-e2e-only-desktop.yml b/.github/workflows/test-ui-e2e-only-desktop.yml index 8cce945c1b09..62b58d6ddf1b 100644 --- a/.github/workflows/test-ui-e2e-only-desktop.yml +++ b/.github/workflows/test-ui-e2e-only-desktop.yml @@ -123,6 +123,15 @@ jobs: run: docker pull ${{ env.SPECULOS_IMAGE_TAG }} shell: bash + - name: Install CLI dependencies + env: + LANG: en_US.UTF-8 + run: pnpm i --filter="live-cli*..." + shell: bash + + - name: Build CLI + run: pnpm build:cli --api="http://127.0.0.1:${{ steps.caches.outputs.port }}" --token="${{ secrets.TURBOREPO_SERVER_TOKEN }}" --team="foo" + - name: Run playwright tests [Linux => xvfb-run] id: tests run: | From 5513ba5eec13b83e0482b1f18e3d5f2b3da439b9 Mon Sep 17 00:00:00 2001 From: Abdurrahman SASTIM Date: Thu, 3 Oct 2024 12:11:24 +0200 Subject: [PATCH 18/86] test: test a cli command --- apps/ledger-live-desktop/tests/fixtures/common.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/ledger-live-desktop/tests/fixtures/common.ts b/apps/ledger-live-desktop/tests/fixtures/common.ts index 69487afed34a..2e198d2a9af6 100644 --- a/apps/ledger-live-desktop/tests/fixtures/common.ts +++ b/apps/ledger-live-desktop/tests/fixtures/common.ts @@ -12,6 +12,7 @@ import { launchApp } from "tests/utils/electronUtils"; import { captureArtifacts } from "tests/utils/allureUtils"; import { randomUUID } from "crypto"; import { AppInfos } from "tests/enum/AppInfos"; +import { runCliCommand } from "../utils/cliUtils"; type TestFixtures = { lang: string; @@ -106,6 +107,10 @@ export const test = base.extend({ setEnv("SPECULOS_API_PORT", device?.ports.apiPort?.toString()); process.env.SPECULOS_API_PORT = device?.ports.apiPort; process.env.MOCK = ""; + + const command = "balanceHistory --format asciichart"; + const output = await runCliCommand(command); + console.log(output); } // default environment variables From 89eea6152d572a87534f14dc118f35be94a174dc Mon Sep 17 00:00:00 2001 From: Abdurrahman SASTIM Date: Thu, 3 Oct 2024 14:34:15 +0200 Subject: [PATCH 19/86] Revert "test: test a cli command" This reverts commit 5adec51300ce70015237b72fe205477da272da58. --- apps/ledger-live-desktop/tests/fixtures/common.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/ledger-live-desktop/tests/fixtures/common.ts b/apps/ledger-live-desktop/tests/fixtures/common.ts index 2e198d2a9af6..69487afed34a 100644 --- a/apps/ledger-live-desktop/tests/fixtures/common.ts +++ b/apps/ledger-live-desktop/tests/fixtures/common.ts @@ -12,7 +12,6 @@ import { launchApp } from "tests/utils/electronUtils"; import { captureArtifacts } from "tests/utils/allureUtils"; import { randomUUID } from "crypto"; import { AppInfos } from "tests/enum/AppInfos"; -import { runCliCommand } from "../utils/cliUtils"; type TestFixtures = { lang: string; @@ -107,10 +106,6 @@ export const test = base.extend({ setEnv("SPECULOS_API_PORT", device?.ports.apiPort?.toString()); process.env.SPECULOS_API_PORT = device?.ports.apiPort; process.env.MOCK = ""; - - const command = "balanceHistory --format asciichart"; - const output = await runCliCommand(command); - console.log(output); } // default environment variables From 27f764a524d76c5ad47a5704ddf66da29ad7e47d Mon Sep 17 00:00:00 2001 From: Abdurrahman Sastim <106583189+abdurrahman-ledger@users.noreply.github.com> Date: Thu, 3 Oct 2024 15:01:01 +0200 Subject: [PATCH 20/86] test: fix imports Co-authored-by: Kevin Le Seigle <31533861+KVNLS@users.noreply.github.com> --- apps/cli/package.json | 1 + apps/cli/src/live-common-setup.ts | 2 +- pnpm-lock.yaml | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 1bbc9fc15b9a..038609c7df3c 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -41,6 +41,7 @@ "@ledgerhq/hw-transport-mocker": "workspace:^", "@ledgerhq/hw-transport-node-hid": "workspace:^", "@ledgerhq/hw-transport-node-speculos": "workspace:^", + "@ledgerhq/hw-transport-node-speculos-http": "workspace:^", "@ledgerhq/live-common": "workspace:^", "@ledgerhq/live-config": "workspace:^", "@ledgerhq/live-countervalues": "workspace:^", diff --git a/apps/cli/src/live-common-setup.ts b/apps/cli/src/live-common-setup.ts index 381739bdfa7d..68a3bb91f00f 100644 --- a/apps/cli/src/live-common-setup.ts +++ b/apps/cli/src/live-common-setup.ts @@ -17,7 +17,7 @@ import { LiveConfig } from "@ledgerhq/live-config/LiveConfig"; import { liveConfig } from "@ledgerhq/live-common/config/sharedConfig"; import SpeculosHttpTransport, { SpeculosHttpTransportOpts, -} from "../../../libs/ledgerjs/packages/hw-transport-node-speculos-http/lib/SpeculosHttpTransport"; +} from "@ledgerhq/hw-transport-node-speculos-http"; checkLibs({ NotEnoughBalance, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed4772257fb0..417336eb4eb8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -165,6 +165,9 @@ importers: '@ledgerhq/hw-transport-node-speculos': specifier: workspace:^ version: link:../../libs/ledgerjs/packages/hw-transport-node-speculos + '@ledgerhq/hw-transport-node-speculos-http': + specifier: workspace:^ + version: link:../../libs/ledgerjs/packages/hw-transport-node-speculos-http '@ledgerhq/ledger-key-ring-protocol': specifier: workspace:^ version: link:../../libs/ledger-key-ring-protocol From 24f455607502d97824b76b328c7eafad36c0273a Mon Sep 17 00:00:00 2001 From: qperrot Date: Thu, 10 Oct 2024 10:45:45 +0200 Subject: [PATCH 21/86] chore: removed log --- libs/live-countervalues/src/portfolio.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/libs/live-countervalues/src/portfolio.ts b/libs/live-countervalues/src/portfolio.ts index be14ae53f92f..2847b32bd7b6 100644 --- a/libs/live-countervalues/src/portfolio.ts +++ b/libs/live-countervalues/src/portfolio.ts @@ -142,13 +142,9 @@ export function getBalanceHistory( const t = new Date(conf.startOf(now).getTime() - 1).getTime(); // end of yesterday for (let i = 0; i < count - 1; i++) { - const value = balances[balances.length - 1 - i]; - if (Number.isNaN(value)) { - log("countervalues-error", `Value is NaN for ${account.id}`); - } history.unshift({ date: new Date(t - conf.increment * i), - value: value || 0, + value: balances[balances.length - 1 - i] || 0, }); } From b8b7482c7ff20b73ba52ff7a9d9db12fb6d7a4ce Mon Sep 17 00:00:00 2001 From: qperrot Date: Thu, 10 Oct 2024 10:47:45 +0200 Subject: [PATCH 22/86] chore: remove logs import --- libs/live-countervalues/src/portfolio.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/live-countervalues/src/portfolio.ts b/libs/live-countervalues/src/portfolio.ts index 2847b32bd7b6..e742d8e84056 100644 --- a/libs/live-countervalues/src/portfolio.ts +++ b/libs/live-countervalues/src/portfolio.ts @@ -19,7 +19,6 @@ import type { AssetsDistribution, } from "@ledgerhq/types-live"; import type { CryptoCurrency, Currency, TokenCurrency } from "@ledgerhq/types-cryptoassets"; -import { log } from "@ledgerhq/logs"; export const defaultAssetsDistribution = { minShowFirst: 1, From 053111d83cf15a0c27e0a73d30440bdc1e8bc331 Mon Sep 17 00:00:00 2001 From: Lucas Werey <73439207+LucasWerey@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:55:50 +0200 Subject: [PATCH 23/86] :technologist:(lld-llm): remove inte and test folders of jest coverage (#8058) :technologist:(lld-llm): remove inte and test folder of jest coverage --- apps/ledger-live-desktop/jest.config.js | 8 +++++++- apps/ledger-live-mobile/jest.config.js | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/ledger-live-desktop/jest.config.js b/apps/ledger-live-desktop/jest.config.js index 4870e0403a02..71958c859afa 100644 --- a/apps/ledger-live-desktop/jest.config.js +++ b/apps/ledger-live-desktop/jest.config.js @@ -65,7 +65,13 @@ const commonConfig = { }; module.exports = { - collectCoverageFrom: ["src/**/*.{ts,tsx}", "!src/**/*.test.{ts,tsx}", "!src/**/*.spec.{ts,tsx}"], + collectCoverageFrom: [ + "src/**/*.{ts,tsx}", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + "!src/**/__integration__/**", + "!src/**/__tests__/**", + ], coverageReporters: ["json", "lcov", "json-summary"], silent: false, verbose: true, diff --git a/apps/ledger-live-mobile/jest.config.js b/apps/ledger-live-mobile/jest.config.js index afa2c7e78d6c..b0c87b51ea20 100644 --- a/apps/ledger-live-mobile/jest.config.js +++ b/apps/ledger-live-mobile/jest.config.js @@ -47,7 +47,13 @@ module.exports = { ], testPathIgnorePatterns: ["/node_modules/"], moduleDirectories: ["node_modules"], - collectCoverageFrom: ["src/**/*.{ts,tsx}", "!src/**/*.test.{ts,tsx}", "!src/**/*.spec.{ts,tsx}"], + collectCoverageFrom: [ + "src/**/*.{ts,tsx}", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + "!src/**/__integration__/**", + "!src/**/__tests__/**", + ], coverageReporters: ["json", "lcov", "json-summary"], moduleNameMapper: { ...pathsToModuleNameMapper(compilerOptions.paths), From d0eec9f6fc61a915119f0b9bf087ff270687ff30 Mon Sep 17 00:00:00 2001 From: Lucas Werey <73439207+LucasWerey@users.noreply.github.com> Date: Thu, 10 Oct 2024 15:03:33 +0200 Subject: [PATCH 24/86] :sparkles:(lld): rework coin control UI (#8047) --- .changeset/wet-socks-film.md | 6 + .../components/ExcludeAssetsSection.tsx | 32 +++ .../CoinControlModal/components/Footer.tsx | 36 ++++ .../components/Row/NoOrdinalsRow.tsx | 53 +++++ .../CoinControlModal/components/Row/index.tsx | 164 +++++++++++++++ .../components/Row/useOrdinalRowModel.ts | 77 +++++++ .../components/UtxoPickerHeaderSection.tsx | 18 ++ .../components/UtxoStrategyPicker.tsx | 49 +++++ .../CoinControlModal/components/index.ts | 7 + .../components/CoinControlModal/index.tsx | 136 ++++++++++++ .../useCoinControlModalModel.ts | 67 ++++++ .../Inscriptions/DiscoveryDrawer/index.tsx | 21 +- .../Ordinals/screens/Account/index.tsx | 7 +- .../screens/Account/useBitcoinAccountModel.ts | 8 + .../__integration__/coinControlModal.test.tsx | 198 ++++++++++++++++++ .../__integration__/mockedBTCAccount.ts | 58 ++++- .../Collectibles/__integration__/mockedTx.ts | 39 ++++ .../__integration__/mockedTxStatus.ts | 41 ++++ .../Collectibles/__integration__/shared.tsx | 4 + .../hooks/useCollectiblesAnalytics.ts | 23 ++ .../Collectibles/types/enum/Analytics.ts | 18 ++ .../src/renderer/components/Modal/index.tsx | 3 +- .../families/bitcoin/SendAmountFields.tsx | 36 +++- .../bitcoin/useBitcoinPickingStrategy.tsx | 2 +- .../static/i18n/en/app.json | 11 + apps/ledger-live-desktop/tests/jestSetup.js | 11 + libs/coin-modules/coin-bitcoin/src/logic.ts | 5 +- libs/live-nft-react/src/index.ts | 1 + 28 files changed, 1108 insertions(+), 23 deletions(-) create mode 100644 .changeset/wet-socks-film.md create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/ExcludeAssetsSection.tsx create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/Footer.tsx create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/Row/NoOrdinalsRow.tsx create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/Row/index.tsx create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/Row/useOrdinalRowModel.ts create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/UtxoPickerHeaderSection.tsx create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/UtxoStrategyPicker.tsx create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/index.ts create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/index.tsx create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/useCoinControlModalModel.ts create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/__integration__/coinControlModal.test.tsx create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/__integration__/mockedTx.ts create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/__integration__/mockedTxStatus.ts create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/hooks/useCollectiblesAnalytics.ts create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/types/enum/Analytics.ts diff --git a/.changeset/wet-socks-film.md b/.changeset/wet-socks-film.md new file mode 100644 index 000000000000..0c43fb0a3dd8 --- /dev/null +++ b/.changeset/wet-socks-film.md @@ -0,0 +1,6 @@ +--- +"@ledgerhq/coin-bitcoin": patch +"ledger-live-desktop": patch +--- + +rework of the coin control modal ui diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/ExcludeAssetsSection.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/ExcludeAssetsSection.tsx new file mode 100644 index 000000000000..47922a8a8d26 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/ExcludeAssetsSection.tsx @@ -0,0 +1,32 @@ +import React, { useCallback } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useTranslation } from "react-i18next"; +import { setHasProtectedOrdinalsAssets } from "~/renderer/actions/settings"; +import { hasProtectedOrdinalsAssetsSelector } from "~/renderer/reducers/settings"; +import { Flex, Text } from "@ledgerhq/react-ui"; +import Switch from "~/renderer/components/Switch"; + +export const ExcludeAssetsSection: React.FC = () => { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const hasProtectedOrdinals = useSelector(hasProtectedOrdinalsAssetsSelector); + + const onSwitchChange = useCallback(() => { + dispatch(setHasProtectedOrdinalsAssets(!hasProtectedOrdinals)); + }, [dispatch, hasProtectedOrdinals]); + + return ( + + + {t("ordinals.coinControl.excludeAssets")} + + + + + + ); +}; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/Footer.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/Footer.tsx new file mode 100644 index 000000000000..fad43f92a654 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/Footer.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { Flex, Icons, Button, Link } from "@ledgerhq/react-ui"; +import { BitcoinOutput } from "@ledgerhq/coin-bitcoin/lib/types"; +import { useTranslation } from "react-i18next"; + +type Props = { + onClickLink: () => void; + onClose: () => void; + returning?: BitcoinOutput; // will be used in the future +}; + +const Footer: React.FC = ({ onClickLink, onClose }) => { + const { t } = useTranslation(); + + return ( + + + + {t("ordinals.coinControl.learnMore")} + + + + + + ); +}; + +export default Footer; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/Row/NoOrdinalsRow.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/Row/NoOrdinalsRow.tsx new file mode 100644 index 000000000000..39d0f99cc17b --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/Row/NoOrdinalsRow.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import { Flex, Text } from "@ledgerhq/react-ui"; +import { SplitAddress, Cell } from "~/renderer/components/OperationsList/AddressCell"; +import { useTranslation } from "react-i18next"; + +type Props = { + outputIndex: number; + hash: string; + address: string; +}; + +const NoOrdinalsRow: React.FC = ({ outputIndex, hash, address }) => { + const { t } = useTranslation(); + return ( + + + + {t("ordinals.coinControl.address")} + + + + + + + + + + {t("ordinals.coinControl.transactionId")} + + + + #{outputIndex} {t("ordinals.coinControl.of")} + + + + + + + + ); +}; + +export default NoOrdinalsRow; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/Row/index.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/Row/index.tsx new file mode 100644 index 000000000000..29e3f699c5c0 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/Row/index.tsx @@ -0,0 +1,164 @@ +import React from "react"; +import { Trans } from "react-i18next"; +import styled from "styled-components"; +import { Box, Flex, Icons, Tooltip, Text } from "@ledgerhq/react-ui"; +import NoOrdinalsRow from "./NoOrdinalsRow"; +import Checkbox from "~/renderer/components/CheckBox"; +import FormattedVal from "~/renderer/components/FormattedVal"; +import { space } from "@ledgerhq/react-ui/styles/theme"; +import { useOrdinalRowModel, Props } from "./useOrdinalRowModel"; + +const Container = styled(Box)<{ disabled?: boolean }>` + display: flex; + flex-direction: column; + justify-content: flex-start; + border-radius: 8px; + align-items: center; + border: 1px solid ${({ theme }) => theme.colors.palette.neutral.c20}; + background-color: ${({ theme }) => theme.colors.palette.opacityDefault.c05}; + opacity: ${({ disabled }) => (disabled ? 0.5 : 1)}; + cursor: ${({ disabled }) => (disabled ? "default" : "pointer")}; + + &:hover { + border-color: ${({ theme, disabled }) => + disabled ? theme.colors.palette.text.shade20 : theme.colors.palette.primary.main}; + } +`; + +const UtxoContainer = styled(Flex)<{ isOpened?: boolean }>` + background-color: ${({ theme, isOpened }) => + isOpened ? theme.colors.palette.opacityDefault.c05 : undefined}; +`; + +const TooltipContainer = styled(Box)` + background-color: ${({ theme }) => theme.colors.palette.neutral.c100}; + padding: 8px; + border-radius: 16px; + display: flex; + gap: 2px; +`; + +type ViewProps = ReturnType; + +function View({ + utxo, + account, + unit, + isDetailsVisible, + disabled, + last, + unconfirmed, + input, + excluded, + toggleDetailsVisibility, + handleClick, +}: ViewProps) { + const renderLeftSelection = () => { + if (unconfirmed) + return ( + + + + + } + /> + ); + + if (last) + return ( + + + + + } + > + + + ); + + return ; + }; + return ( + + + + {renderLeftSelection()} + + + {utxo.blockHeight ? ( + + {account.blockHeight - utxo.blockHeight + " confirmations"} + + ) : ( + + + + )} + + + {input && !disabled ? ( + + + + ) : null} + + + + {isDetailsVisible ? : } + + + {isDetailsVisible && ( + + )} + + ); +} + +export const Row: React.FC = props => { + return ; +}; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/Row/useOrdinalRowModel.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/Row/useOrdinalRowModel.ts new file mode 100644 index 000000000000..3c32e7295861 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/Row/useOrdinalRowModel.ts @@ -0,0 +1,77 @@ +import { useState } from "react"; +import { + BitcoinAccount, + BitcoinOutput, + Transaction, + TransactionStatus, + UtxoStrategy, +} from "@ledgerhq/live-common/families/bitcoin/types"; +import { Unit } from "@ledgerhq/types-cryptoassets"; +import { AccountBridge } from "@ledgerhq/types-live"; +import { getUTXOStatus } from "@ledgerhq/live-common/families/bitcoin/logic"; + +export type Props = { + utxo: BitcoinOutput; + utxoStrategy: UtxoStrategy; + status: TransactionStatus; + account: BitcoinAccount; + totalExcludedUTXOS: number; + bridge: AccountBridge; + unit: Unit; + updateTransaction: (updater: (t: Transaction) => Transaction) => void; +}; + +export const useOrdinalRowModel = ({ + utxo, + utxoStrategy, + status, + account, + totalExcludedUTXOS, + bridge, + unit, + updateTransaction, +}: Props) => { + const [isDetailsVisible, setIsDetailsVisible] = useState(false); + const { excluded, reason } = getUTXOStatus(utxo, utxoStrategy); + const utxoStatus = excluded ? reason || "" : ""; + + const input = (status.txInputs || []).find( + input => input.previousOutputIndex === utxo.outputIndex && input.previousTxHash === utxo.hash, + ); + const unconfirmed = utxoStatus === "pickPendingUtxo"; + const last = !excluded && totalExcludedUTXOS + 1 === account.bitcoinResources?.utxos.length; + const disabled = unconfirmed || last; + + const handleClick = () => { + if (disabled) return; + const updatedStrategy = { + ...utxoStrategy, + excludeUTXOs: excluded + ? utxoStrategy.excludeUTXOs.filter( + e => e.hash !== utxo.hash || e.outputIndex !== utxo.outputIndex, + ) + : [...utxoStrategy.excludeUTXOs, { hash: utxo.hash, outputIndex: utxo.outputIndex }], + }; + + updateTransaction(t => bridge.updateTransaction(t, { utxoStrategy: updatedStrategy })); + }; + + const toggleDetailsVisibility = (e: React.MouseEvent) => { + e.stopPropagation(); + setIsDetailsVisible(!isDetailsVisible); + }; + + return { + utxo, + account, + unit, + isDetailsVisible, + disabled, + last, + unconfirmed, + input, + excluded, + toggleDetailsVisibility, + handleClick, + }; +}; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/UtxoPickerHeaderSection.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/UtxoPickerHeaderSection.tsx new file mode 100644 index 000000000000..00024c181f64 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/UtxoPickerHeaderSection.tsx @@ -0,0 +1,18 @@ +import React from "react"; +import { Text, Flex } from "@ledgerhq/react-ui"; +import { useTranslation } from "react-i18next"; + +export const UtxoPickerHeaderSection: React.FC = () => { + const { t } = useTranslation(); + + return ( + + + {t("ordinals.coinControl.utxos")} + + + {t("ordinals.coinControl.selectDesc")} + + + ); +}; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/UtxoStrategyPicker.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/UtxoStrategyPicker.tsx new file mode 100644 index 000000000000..7333ba124d7e --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/CoinControlModal/components/UtxoStrategyPicker.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { bitcoinPickingStrategy, Transaction } from "@ledgerhq/live-common/families/bitcoin/types"; +import { Account, AccountBridge, AccountRaw, TransactionStatusCommon } from "@ledgerhq/types-live"; +import useBitcoinPickingStrategy, { + Option, +} from "~/renderer/families/bitcoin/useBitcoinPickingStrategy"; +import { Text, Flex } from "@ledgerhq/react-ui"; +import Select from "~/renderer/components/Select"; + +type Props = { + transaction: Transaction; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + bridge: AccountBridge; + onChange: (updateFn: (t: Transaction, p: Partial) => void) => void; +}; + +export const UtxoStrategyPicker: React.FC = ({ transaction, bridge, onChange }) => { + const { t } = useTranslation(); + const { item, options } = useBitcoinPickingStrategy(transaction.utxoStrategy.strategy); + + const handleChange = (selectedItem: Option) => { + onChange( + bridge.updateTransaction(transaction, { + utxoStrategy: { + ...transaction.utxoStrategy, + strategy: selectedItem + ? bitcoinPickingStrategy[selectedItem.value as keyof typeof bitcoinPickingStrategy] + : 0, + }, + }), + ); + }; + + return ( + + + {t("bitcoin.strategy")} + +