Skip to content

Commit

Permalink
feat(live-12839): SWAP - setup demo3 flag and route (#7573)
Browse files Browse the repository at this point in the history
* feat(LIVE-12839): demo-3 feature flag

* feat: demo3 dedicated swap page

* fix: remove debug logs

* chore: comments

* fix: rebase

* chore: add changeset

* fix: lint

* fix: lint

* fix: import from renderer

* chore: add logger critical

---------

Co-authored-by: Alinus Dumitrana <[email protected]>
  • Loading branch information
liviuciulinaru and Alinus Dumitrana authored Aug 13, 2024
1 parent 9badf39 commit 0c80144
Show file tree
Hide file tree
Showing 17 changed files with 312 additions and 55 deletions.
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

0 comments on commit 0c80144

Please sign in to comment.