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(live-12839): SWAP - setup demo3 flag and route #7573

Merged
merged 10 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions .changeset/nasty-deers-clap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@ledgerhq/types-live": patch
"ledger-live-desktop": patch
"@ledgerhq/live-common": patch
---

[swap] setup demo3 flag and routes
2 changes: 2 additions & 0 deletions apps/ledger-live-desktop/src/renderer/analytics/segment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const getPtxAttributes = () => {

const ptxSwapLiveAppDemoZero = analyticsFeatureFlagMethod("ptxSwapLiveAppDemoZero")?.enabled;
const ptxSwapLiveAppDemoOne = analyticsFeatureFlagMethod("ptxSwapLiveAppDemoOne")?.enabled;
const ptxSwapLiveAppDemoThree = analyticsFeatureFlagMethod("ptxSwapLiveAppDemoThree")?.enabled;
const ptxSwapThorswapProvider = analyticsFeatureFlagMethod("ptxSwapThorswapProvider")?.enabled;
const ptxSwapExodusProvider = analyticsFeatureFlagMethod("ptxSwapExodusProvider")?.enabled;

Expand All @@ -109,6 +110,7 @@ const getPtxAttributes = () => {
ptxSwapMoonpayProviderEnabled,
ptxSwapLiveAppDemoZero,
ptxSwapLiveAppDemoOne,
ptxSwapLiveAppDemoThree,
ptxSwapThorswapProvider,
ptxSwapExodusProvider,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useSwapLiveConfig } from "@ledgerhq/live-common/exchange/swap/hooks/index";
import { useRemoteLiveAppManifest } from "@ledgerhq/live-common/platform/providers/RemoteLiveAppProvider/index";
import { useLocalLiveAppManifest } from "@ledgerhq/live-common/wallet-api/LocalLiveAppProvider/index";
import React, { useState } from "react";
import styled from "styled-components";
import SwapWebView from "~/renderer/screens/exchange/Swap2/Form/SwapWebViewDemo3";

const Root = styled.div`
display: flex;
flex-direction: column;
justify-content: flex-start;
`;

// TODO: fix with proper error handling
const ErrorWrapper = styled.div`
width: auto;
display: inline-flex;
align-self: center;
align-items: center;
justify-self: center;
justify-content: center;
padding: 24px;
border-radius: 14px;
background-color: rgba(255, 0, 0, 0.3);
color: #fff;
font-weight: 500;
`;

export function SwapApp() {
const [unavailable, setUnavailable] = useState(false);
const swapLiveEnabledFlag = useSwapLiveConfig();
const swapLiveAppManifestID = swapLiveEnabledFlag?.params?.manifest_id;

const localManifest = useLocalLiveAppManifest(swapLiveAppManifestID || undefined);
const remoteManifest = useRemoteLiveAppManifest(swapLiveAppManifestID || undefined);
const manifest = localManifest || remoteManifest;

if (!manifest) {
// TODO: fix with proper error handling
return <ErrorWrapper>Unable to load application: missing manifest</ErrorWrapper>;
}

if (unavailable) {
// TODO: fix with proper error handling
return <ErrorWrapper>Unable to load application: Unavailable</ErrorWrapper>;
}

return (
<Root>
<SwapWebView manifest={manifest} liveAppUnavailable={() => setUnavailable(true)} />
</Root>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./App";
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { context } from "~/renderer/drawers/Provider";
import { useMaybeAccountName } from "~/renderer/reducers/wallet";
import { useGetSwapTrackingProperties } from "../../utils/index";
import TargetAccountDrawer from "../TargetAccountDrawer";
import { useIsSwapLiveFlagEnabled } from "../useIsSwapLiveFlagEnabled";
import { useIsSwapLiveFlagEnabled } from "../../hooks/useIsSwapLiveFlagEnabled";
import SectionInformative from "./SectionInformative";
import SummaryLabel from "./SummaryLabel";
import SummarySection from "./SummarySection";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SwapTransactionType } from "@ledgerhq/live-common/exchange/swap/types";
import React from "react";
import styled from "styled-components";
import { useIsSwapLiveFlagEnabled } from "../useIsSwapLiveFlagEnabled";
import { useIsSwapLiveFlagEnabled } from "../../hooks/useIsSwapLiveFlagEnabled";
import SectionFees from "./SectionFees";
import SectionTarget from "./SectionTarget";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import SwapFormRates from "../FormRates";
import SwapFormSummary from "../FormSummary";
import LoadingState from "../Rates/LoadingState";
import { SwapWebManifestIDs } from "../SwapWebView";
import { useIsSwapLiveFlagEnabled } from "../useIsSwapLiveFlagEnabled";
import { useIsSwapLiveFlagEnabled } from "../../hooks/useIsSwapLiveFlagEnabled";

const Button = styled(ButtonBase)`
width: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ import {
} from "@ledgerhq/live-common/exchange/swap/webApp/utils";
import { LiveAppManifest } from "@ledgerhq/live-common/platform/types";
import { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets";
import { useTranslation } from "react-i18next";
import { track } from "~/renderer/analytics/segment";
import { usePTXCustomHandlers } from "~/renderer/components/WebPTXPlayer/CustomHandlers";
import { NetworkStatus, useNetworkStatus } from "~/renderer/hooks/useNetworkStatus";
import { flattenAccountsSelector } from "~/renderer/reducers/accounts";
import { captureException } from "~/sentry/renderer";
import { CustomSwapQuotesState } from "../hooks/useSwapLiveAppQuoteState";
import FeesDrawerLiveApp from "./FeesDrawerLiveApp";
import { track } from "~/renderer/analytics/segment";
import { useTranslation } from "react-i18next";

export class UnableToLoadSwapLiveError extends Error {
constructor(message: string) {
Expand Down Expand Up @@ -86,18 +86,19 @@ export type SwapProps = {

export type SwapWebProps = {
manifest: LiveAppManifest;
liveAppUnavailable: () => void;
setQuoteState?: (next: CustomSwapQuotesState) => void;
swapState?: Partial<SwapProps>;
liveAppUnavailable(): void;
isMaxEnabled?: boolean;
sourceCurrency?: TokenCurrency | CryptoCurrency;
targetCurrency?: TokenCurrency | CryptoCurrency;
setQuoteState: (next: CustomSwapQuotesState) => void;
};
let lastGasOptions: GasOptions;

export const SwapWebManifestIDs = {
Demo0: "swap-live-app-demo-0",
Demo1: "swap-live-app-demo-1",
Demo3: "swap-live-app-demo-3",
};

const SwapWebAppWrapper = styled.div`
Expand Down Expand Up @@ -181,7 +182,7 @@ const SwapWebView = ({
const fromUnit = sourceCurrency?.units[0];

if (!quote.params) {
setQuoteState({
setQuoteState?.({
amountTo: undefined,
swapError: undefined,
counterValue: undefined,
Expand All @@ -192,7 +193,7 @@ const SwapWebView = ({
if (quote.params?.code && fromUnit) {
switch (quote.params.code) {
case "minAmountError":
setQuoteState({
setQuoteState?.({
amountTo: undefined,
counterValue: undefined,
swapError: new SwapExchangeRateAmountTooLow(undefined, {
Expand All @@ -209,7 +210,7 @@ const SwapWebView = ({
});
return Promise.resolve();
case "maxAmountError":
setQuoteState({
setQuoteState?.({
amountTo: undefined,
counterValue: undefined,
swapError: new SwapExchangeRateAmountTooLow(undefined, {
Expand All @@ -236,7 +237,7 @@ const SwapWebView = ({
)
: undefined;

setQuoteState({
setQuoteState?.({
amountTo,
counterValue: counterValue,
swapError: undefined,
Expand Down Expand Up @@ -487,7 +488,7 @@ const SwapWebView = ({

useEffect(() => {
// Determine the new quote state based on network status
setQuoteState({
setQuoteState?.({
amountTo: undefined,
counterValue: undefined,
...{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { SwapLiveError } from "@ledgerhq/live-common/exchange/swap/types";
import { LiveAppManifest } from "@ledgerhq/live-common/platform/types";
import { handlers as loggerHandlers } from "@ledgerhq/live-common/wallet-api/CustomLogger/server";
import { getEnv } from "@ledgerhq/live-env";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { useSelector } from "react-redux";
import styled from "styled-components";
import { Web3AppWebview } from "~/renderer/components/Web3AppWebview";
import { initialWebviewState } from "~/renderer/components/Web3AppWebview/helpers";
import { WebviewAPI, WebviewProps, WebviewState } from "~/renderer/components/Web3AppWebview/types";
import { TopBar } from "~/renderer/components/WebPlatformPlayer/TopBar";
import { usePTXCustomHandlers } from "~/renderer/components/WebPTXPlayer/CustomHandlers";
import { context } from "~/renderer/drawers/Provider";
import useTheme from "~/renderer/hooks/useTheme";
import logger from "~/renderer/logger";
import {
counterValueCurrencySelector,
enablePlatformDevToolsSelector,
languageSelector,
} from "~/renderer/reducers/settings";
import { captureException } from "~/sentry/renderer";
import WebviewErrorDrawer from "./WebviewErrorDrawer/index";

export class UnableToLoadSwapLiveError extends Error {
constructor(message: string) {
const name = "UnableToLoadSwapLiveError";
super(message || name);
this.name = name;
this.message = message;
}
}

export type SwapProps = {
provider: string;
fromAccountId: string;
fromParentAccountId?: string;
toAccountId: string;
fromAmount: string;
toAmount?: string;
quoteId: string;
rate: string;
feeStrategy: string;
customFeeConfig: string;
cacheKey: string;
loading: boolean;
error: boolean;
providerRedirectURL: string;
toNewTokenId: string;
swapApiBase: string;
estimatedFees: string;
estimatedFeesUnit: string;
};

export type SwapWebProps = {
manifest: LiveAppManifest;
liveAppUnavailable: () => void;
};

const SwapWebAppWrapper = styled.div`
width: 100%;
flex: 1;
`;

const SWAP_API_BASE = getEnv("SWAP_API_BASE");

const SwapWebView = ({ manifest, liveAppUnavailable }: SwapWebProps) => {
const {
colors: {
palette: { type: themeType },
},
} = useTheme();

const webviewAPIRef = useRef<WebviewAPI>(null);
const { setDrawer } = React.useContext(context);
const [webviewState, setWebviewState] = useState<WebviewState>(initialWebviewState);
const fiatCurrency = useSelector(counterValueCurrencySelector);
const locale = useSelector(languageSelector);
const enablePlatformDevTools = useSelector(enablePlatformDevToolsSelector);

const customPTXHandlers = usePTXCustomHandlers(manifest);
const customHandlers = useMemo(
() => ({
...loggerHandlers,
...customPTXHandlers,
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);

const hashString = useMemo(() => new URLSearchParams({}).toString(), []);

const onSwapWebviewError = (error?: SwapLiveError) => {
console.error("onSwapWebviewError", error);
logger.critical(error);
setDrawer(WebviewErrorDrawer, error);
};

const onStateChange: WebviewProps["onStateChange"] = state => {
setWebviewState(state);

if (!state.loading && state.isAppUnavailable) {
liveAppUnavailable();
captureException(
new UnableToLoadSwapLiveError(
'"Failed to load swap live app using WebPlatformPlayer in SwapWeb",',
),
);
}
};

useEffect(() => {
if (webviewState.url.includes("/unknown-error")) {
// the live app has re-directed to /unknown-error. Handle this in callback, probably wallet-api failure.
onSwapWebviewError();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [webviewState.url]);

return (
<>
{enablePlatformDevTools && (
<TopBar
manifest={{ ...manifest, url: `${manifest.url}#${hashString}` }}
webviewAPIRef={webviewAPIRef}
webviewState={webviewState}
/>
)}

<SwapWebAppWrapper>
<Web3AppWebview
manifest={{ ...manifest, url: `${manifest.url}#${hashString}` }}
inputs={{
theme: themeType,
lang: locale,
currencyTicker: fiatCurrency.ticker,
swapApiBase: SWAP_API_BASE,
}}
onStateChange={onStateChange}
ref={webviewAPIRef}
customHandlers={customHandlers as never}
hideLoader
/>
</SwapWebAppWrapper>
</>
);
};

export default SwapWebView;
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ import { useSwapLiveAppHook } from "~/renderer/hooks/swap-migrations/useSwapLive
import { flattenAccountsSelector, shallowAccountsSelector } from "~/renderer/reducers/accounts";
import { languageSelector } from "~/renderer/reducers/settings";
import { walletSelector } from "~/renderer/reducers/wallet";
import { useIsSwapLiveFlagEnabled } from "../hooks/useIsSwapLiveFlagEnabled";
import { useSwapLiveAppQuoteState } from "../hooks/useSwapLiveAppQuoteState";
import { trackSwapError, useGetSwapTrackingProperties } from "../utils/index";
import ExchangeDrawer from "./ExchangeDrawer/index";
import SwapFormSelectors from "./FormSelectors";
import { SwapMigrationUI } from "./Migrations/SwapMigrationUI";
import EmptyState from "./Rates/EmptyState";
import SwapWebView, { SwapWebProps } from "./SwapWebView";
import { useIsSwapLiveFlagEnabled } from "./useIsSwapLiveFlagEnabled";

const DAPP_PROVIDERS = ["paraswap", "oneinch", "moonpay"];

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useFeature } from "@ledgerhq/live-common/featureFlags/index";

// used to get the value of the Swap Live App flag
export const useIsSwapLiveFlagEnabled = (flag: string): boolean => {
const demoZero = useFeature("ptxSwapLiveAppDemoZero");
const demoOne = useFeature("ptxSwapLiveAppDemoOne");
const demoThree = useFeature("ptxSwapLiveAppDemoThree");

if (flag === "ptxSwapLiveAppDemoThree") {
return !!demoThree?.enabled;
}

if (flag === "ptxSwapLiveAppDemoOne") {
return !!demoOne?.enabled;
}

if (flag === "ptxSwapLiveAppDemoZero") {
return !!demoZero?.enabled;
}

throw new Error(`Unknown Swap Live App flag: ${flag}`);
};
Loading
Loading