Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: display incompatibility message for LNS swap, LIVE-14891, LIVE-14838, LIVE-14923 #8433

Merged
merged 14 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/large-otters-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ledger-live-desktop": minor
---

feat: display incompatibility for LNS swap
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 {
Expand All @@ -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";
Expand All @@ -83,14 +82,6 @@ import { walletSelector } from "~/renderer/reducers/wallet";

type LedgerError = InstanceType<LedgerErrorConstructor<{ [key: string]: unknown }>>;

type SwapRequest = {
transaction: Transaction;
exchange: ExchangeSwap;
provider: string;
rate: number;
amountExpectedTo: number;
};

type PartialNullable<T> = {
[P in keyof T]?: T[P] | null;
};
Expand Down Expand Up @@ -340,10 +331,6 @@ export const DeviceActionDefaultRendering = <R, H extends States, P>({
}
}

if (device?.modelId === "nanoS" && (request as SwapRequest)?.provider === "thorswap") {
return renderHardwareUpdate();
}

if (listingApps) {
return renderListingApps();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -960,17 +960,23 @@ const OpenSwapBtn = () => {
);
};

export const renderHardwareUpdate = () => (
export const HardwareUpdate = ({
i18nKeyTitle,
i18nKeyDescription,
}: {
i18nKeyTitle: string;
i18nKeyDescription: string;
}) => (
<Wrapper>
<Header>
<Image resource={Nano} alt="NanoS" mb="40px"></Image>
</Header>
<Flex alignItems="center" flexDirection="column" rowGap="16px" mr="40px" ml="40px">
<Title variant="body" color="palette.text.shade100">
<Trans i18nKey="swap.wrongDevice.title" />
<Trans i18nKey={i18nKeyTitle} />
</Title>
<Text variant="body" color="palette.text.shade60" textAlign="center">
<Trans i18nKey="swap.wrongDevice.description" />
<Trans i18nKey={i18nKeyDescription} />
</Text>
</Flex>
<ButtonFooter>
Expand Down
108 changes: 87 additions & 21 deletions apps/ledger-live-desktop/src/renderer/components/LiveAppDrawer.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -29,9 +29,12 @@ 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";
import { Transaction } from "@ledgerhq/live-common/generated/types";

const Divider = styled(Box)`
border: 1px solid ${p => p.theme.colors.palette.divider};
Expand All @@ -54,6 +57,14 @@ export type StartExchangeData = {
onResult: (startExchangeResult: StartExchangeSuccessResult) => void;
};

type SwapRequest = {
transaction: Transaction;
exchange: ExchangeSwap;
provider: string;
rate: number;
amountExpectedTo: number;
};

Justkant marked this conversation as resolved.
Show resolved Hide resolved
export function isStartExchangeData(data: unknown): data is StartExchangeData {
Justkant marked this conversation as resolved.
Show resolved Hide resolved
if (data === null || typeof data !== "object") {
return false;
Expand All @@ -63,10 +74,45 @@ export function isStartExchangeData(data: unknown): data is StartExchangeData {

const DrawerClosedError = createCustomErrorClass("DrawerClosedError");

type Keys = Record<string, { title: string; description: string }>;

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<boolean>(false);
const { t } = useTranslation();
const dispatch = useDispatch();
const device = useSelector(getCurrentDevice);

// @ts-expect-error how to type payload?
const {
Expand Down Expand Up @@ -159,23 +205,43 @@ export const LiveAppDrawer = () => {
</Box>
</>
);
case "EXCHANGE_START":
return data && isStartExchangeData(data) ? (
<DeviceAction
action={action}
request={data}
Result={() => 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 (
<HardwareUpdate
i18nKeyTitle="swap.wrongDevice.title"
i18nKeyDescription="swap.wrongDevice.description"
/>
);
}
const keys = getIncompatibleCurrencyKeys(data.exchange);
if (keys) {
return (
<HardwareUpdate i18nKeyTitle={keys.title} i18nKeyDescription={keys.description} />
);
}
}
return (
<DeviceAction
action={action}
request={data}
Result={() => 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) ? (
<CompleteExchange
Expand All @@ -188,7 +254,7 @@ export const LiveAppDrawer = () => {
default:
return null;
}
}, [payload, t, dismissDisclaimerChecked, onContinue, dispatch]);
}, [payload, t, dismissDisclaimerChecked, onContinue, device?.modelId, dispatch]);

return (
<SideDrawer
Expand Down
8 changes: 8 additions & 0 deletions apps/ledger-live-desktop/static/i18n/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,14 @@
"cta": "Explore compatible devices",
"changeProvider": "Swap with another provider"
},
"incompatibility": {
"ton_title": "Ledger Nano S™ does not support swapping Ton through Ledger Live",
"ton_description": "To swap Ton through Ledger Live, use any other compatible Ledger device, such as the Ledger Nano S Plus™, Ledger Nano X™, Ledger Flex™ or Ledger Stax™.",
"spl_tokens_title": "Ledger Nano S™ does not support swapping Solana tokens through Ledger Live",
"spl_tokens_description": "To swap Solana tokens through Ledger Live, use any other compatible Ledger device, such as the Ledger Nano S Plus™, Ledger Nano X™, Ledger Flex™ or Ledger Stax™.",
"ada_title": "Ledger Nano S™ does not support swapping Cardano through Ledger Live",
"ada_description": "To swap Cardano through Ledger Live, use any other compatible Ledger device, such as the Ledger Nano S Plus™, Ledger Nano X™, Ledger Flex™ or Ledger Stax™."
},
"providers": {
"title": "Choose a provider to swap crypto",
"learnMore": "What is Swap?",
Expand Down
Loading