diff --git a/.changeset/large-otters-refuse.md b/.changeset/large-otters-refuse.md new file mode 100644 index 000000000000..12e2b41386b8 --- /dev/null +++ b/.changeset/large-otters-refuse.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": minor +--- + +feat: display incompatibility for LNS swap diff --git a/apps/ledger-live-desktop/src/renderer/components/DeviceAction/index.tsx b/apps/ledger-live-desktop/src/renderer/components/DeviceAction/index.tsx index 90bac8a9f312..62a32ede3273 100644 --- a/apps/ledger-live-desktop/src/renderer/components/DeviceAction/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/DeviceAction/index.tsx @@ -1,25 +1,25 @@ -import React, { useEffect, Component } from "react"; +import React, { Component, useEffect } from "react"; import BigNumber from "bignumber.js"; import { Trans, useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; import { Action } from "@ledgerhq/live-common/hw/actions/types"; import { - OutdatedApp, - LatestFirmwareVersionRequired, - NoSuchAppOnProvider, EConnResetError, - LanguageInstallRefusedOnDevice, ImageDoesNotExistOnDevice, + LanguageInstallRefusedOnDevice, + LatestFirmwareVersionRequired, + NoSuchAppOnProvider, + OutdatedApp, } from "@ledgerhq/live-common/errors"; import { getCurrentDevice } from "~/renderer/reducers/devices"; import { - setPreferredDeviceModel, - setLastSeenDeviceInfo, addNewDeviceModel, + setLastSeenDeviceInfo, + setPreferredDeviceModel, } from "~/renderer/actions/settings"; import { - storeSelector as settingsSelector, preferredDeviceModelSelector, + storeSelector as settingsSelector, } from "~/renderer/reducers/settings"; import { DeviceModelId } from "@ledgerhq/devices"; import AutoRepair from "~/renderer/components/AutoRepair"; @@ -28,36 +28,35 @@ import SignMessageConfirm from "~/renderer/components/SignMessageConfirm"; import useTheme from "~/renderer/hooks/useTheme"; import { ManagerNotEnoughSpaceError, + TransportRaceCondition, + UnresponsiveDeviceError, UpdateYourApp, UserRefusedAddress, UserRefusedAllowManager, + UserRefusedDeviceNameChange, UserRefusedFirmwareUpdate, UserRefusedOnDevice, - UserRefusedDeviceNameChange, - UnresponsiveDeviceError, - TransportRaceCondition, } from "@ledgerhq/errors"; import { + DeviceNotOnboardedErrorComponent, InstallingApp, + renderAllowLanguageInstallation, renderAllowManager, renderAllowOpeningApp, + renderAllowRemoveCustomLockscreen, renderBootloaderStep, renderConnectYourDevice, - renderHardwareUpdate, renderError, + renderInstallingLanguage, renderInWrongAppForAccount, + renderListingApps, renderLoading, + renderLockedDeviceError, renderRequestQuitApp, renderRequiresAppInstallation, - renderListingApps, - renderWarningOutdated, - renderSwapDeviceConfirmation, renderSecureTransferDeviceConfirmation, - renderAllowLanguageInstallation, - renderInstallingLanguage, - renderAllowRemoveCustomLockscreen, - renderLockedDeviceError, - DeviceNotOnboardedErrorComponent, + renderSwapDeviceConfirmation, + renderWarningOutdated, } from "./rendering"; import { useGetSwapTrackingProperties } from "~/renderer/screens/exchange/Swap2/utils"; import { @@ -68,8 +67,8 @@ import { DeviceModelInfo, } from "@ledgerhq/types-live"; import { - ExchangeSwap, ExchangeRate, + ExchangeSwap, InitSwapResult, } from "@ledgerhq/live-common/exchange/swap/types"; import { Transaction, TransactionStatus } from "@ledgerhq/live-common/generated/types"; @@ -83,14 +82,6 @@ import { walletSelector } from "~/renderer/reducers/wallet"; type LedgerError = InstanceType>; -type SwapRequest = { - transaction: Transaction; - exchange: ExchangeSwap; - provider: string; - rate: number; - amountExpectedTo: number; -}; - type PartialNullable = { [P in keyof T]?: T[P] | null; }; @@ -340,10 +331,6 @@ export const DeviceActionDefaultRendering = ({ } } - if (device?.modelId === "nanoS" && (request as SwapRequest)?.provider === "thorswap") { - return renderHardwareUpdate(); - } - if (listingApps) { return renderListingApps(); } diff --git a/apps/ledger-live-desktop/src/renderer/components/DeviceAction/rendering.tsx b/apps/ledger-live-desktop/src/renderer/components/DeviceAction/rendering.tsx index 7f03feb54a6a..d36da1e7a3b1 100644 --- a/apps/ledger-live-desktop/src/renderer/components/DeviceAction/rendering.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/DeviceAction/rendering.tsx @@ -960,17 +960,23 @@ const OpenSwapBtn = () => { ); }; -export const renderHardwareUpdate = () => ( +export const HardwareUpdate = ({ + i18nKeyTitle, + i18nKeyDescription, +}: { + i18nKeyTitle: string; + i18nKeyDescription: string; +}) => (
NanoS
- <Trans i18nKey="swap.wrongDevice.title" /> + <Trans i18nKey={i18nKeyTitle} /> - + diff --git a/apps/ledger-live-desktop/src/renderer/components/LiveAppDrawer.tsx b/apps/ledger-live-desktop/src/renderer/components/LiveAppDrawer.tsx index eed9a68fed95..ac7e7f3c78ef 100644 --- a/apps/ledger-live-desktop/src/renderer/components/LiveAppDrawer.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/LiveAppDrawer.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback, useMemo } from "react"; +import React, { useCallback, useMemo, useState } from "react"; import styled from "styled-components"; import { useDispatch, useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; @@ -29,9 +29,11 @@ import CompleteExchange, { isCompleteExchangeData, } from "~/renderer/modals/Platform/Exchange/CompleteExchange/Body"; import { ExchangeType } from "@ledgerhq/live-common/wallet-api/Exchange/server"; -import { Exchange } from "@ledgerhq/live-common/exchange/types"; -import { renderLoading } from "./DeviceAction/rendering"; +import { Exchange, isExchangeSwap } from "@ledgerhq/live-common/exchange/types"; +import { HardwareUpdate, renderLoading } from "./DeviceAction/rendering"; import { createCustomErrorClass } from "@ledgerhq/errors"; +import { getCurrentDevice } from "~/renderer/reducers/devices"; +import { ExchangeSwap } from "@ledgerhq/live-common/exchange/swap/types"; const Divider = styled(Box)` border: 1px solid ${p => p.theme.colors.palette.divider}; @@ -63,10 +65,45 @@ export function isStartExchangeData(data: unknown): data is StartExchangeData { const DrawerClosedError = createCustomErrorClass("DrawerClosedError"); +type Keys = Record; + +const INCOMPATIBLE_NANO_S_TOKENS_KEYS: Keys = { + solana: { + title: "swap.incompatibility.spl_tokens_title", + description: "swap.incompatibility.spl_tokens_description", + }, +}; + +const INCOMPATIBLE_NANO_S_CURRENCY_KEYS: Keys = { + ton: { + title: "swap.incompatibility.ton_title", + description: "swap.incompatibility.ton_description", + }, + cardano: { + title: "swap.incompatibility.ada_title", + description: "swap.incompatibility.ada_description", + }, +}; +const getIncompatibleCurrencyKeys = (exchange: ExchangeSwap) => { + const parentFrom = exchange?.fromParentAccount?.currency?.id || ""; + const parentTo = exchange?.toParentAccount?.currency?.id || ""; + const from = + (exchange?.fromAccount.type === "Account" && exchange?.fromAccount?.currency?.id) || ""; + const to = (exchange?.toAccount.type === "Account" && exchange?.toAccount?.currency?.id) || ""; + + return ( + INCOMPATIBLE_NANO_S_TOKENS_KEYS[parentFrom] || + INCOMPATIBLE_NANO_S_TOKENS_KEYS[parentTo] || + INCOMPATIBLE_NANO_S_CURRENCY_KEYS[from] || + INCOMPATIBLE_NANO_S_CURRENCY_KEYS[to] + ); +}; + export const LiveAppDrawer = () => { const [dismissDisclaimerChecked, setDismissDisclaimerChecked] = useState(false); const { t } = useTranslation(); const dispatch = useDispatch(); + const device = useSelector(getCurrentDevice); // @ts-expect-error how to type payload? const { @@ -159,23 +196,43 @@ export const LiveAppDrawer = () => { ); - case "EXCHANGE_START": - return data && isStartExchangeData(data) ? ( - renderLoading()} - onResult={result => { - if ("startExchangeResult" in result) { - data.onResult(result.startExchangeResult); - } - if ("startExchangeError" in result) { - data.onCancel?.(result.startExchangeError); - dispatch(closePlatformAppDrawer()); - } - }} - /> - ) : null; + case "EXCHANGE_START": { + if (data && isStartExchangeData(data)) { + if (device?.modelId === "nanoS" && data.exchange && isExchangeSwap(data.exchange)) { + if (data.provider === "thorswap") { + return ( + + ); + } + const keys = getIncompatibleCurrencyKeys(data.exchange); + if (keys) { + return ( + + ); + } + } + return ( + renderLoading()} + onResult={result => { + if ("startExchangeResult" in result) { + data.onResult(result.startExchangeResult); + } + if ("startExchangeError" in result) { + data.onCancel?.(result.startExchangeError); + dispatch(closePlatformAppDrawer()); + } + }} + /> + ); + } + return null; + } case "EXCHANGE_COMPLETE": return data && isCompleteExchangeData(data) ? ( { default: return null; } - }, [payload, t, dismissDisclaimerChecked, onContinue, dispatch]); + }, [payload, t, dismissDisclaimerChecked, onContinue, device?.modelId, dispatch]); return (