From 0c80144b8c16fc3729baa6503875d21af87b2752 Mon Sep 17 00:00:00 2001 From: Liviu Ciulinaru Date: Tue, 13 Aug 2024 09:37:14 +0300 Subject: [PATCH] feat(live-12839): SWAP - setup demo3 flag and route (#7573) * 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 --- .changeset/nasty-deers-clap.md | 7 + .../src/renderer/analytics/segment.ts | 2 + .../screens/exchange/Swap2/App/App.tsx | 53 +++++++ .../screens/exchange/Swap2/App/index.ts | 1 + .../Swap2/Form/FormSummary/SectionTarget.tsx | 2 +- .../exchange/Swap2/Form/FormSummary/index.tsx | 2 +- .../Swap2/Form/Migrations/SwapMigrationUI.tsx | 2 +- .../exchange/Swap2/Form/SwapWebView.tsx | 19 +-- .../exchange/Swap2/Form/SwapWebViewDemo3.tsx | 148 ++++++++++++++++++ .../screens/exchange/Swap2/Form/index.tsx | 2 +- .../Swap2/Form/useIsSwapLiveFlagEnabled.ts | 18 --- .../Swap2/hooks/useIsSwapLiveFlagEnabled.ts | 22 +++ .../renderer/screens/exchange/Swap2/index.tsx | 11 +- .../useSwapLiveConfig.test.ts | 63 +++++--- .../live-app-migration/useSwapLiveConfig.ts | 7 +- .../src/featureFlags/defaultFeatures.ts | 7 + .../packages/types-live/src/feature.ts | 1 + 17 files changed, 312 insertions(+), 55 deletions(-) create mode 100644 .changeset/nasty-deers-clap.md create mode 100644 apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/App/App.tsx create mode 100644 apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/App/index.ts create mode 100644 apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/SwapWebViewDemo3.tsx delete mode 100644 apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/useIsSwapLiveFlagEnabled.ts create mode 100644 apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/hooks/useIsSwapLiveFlagEnabled.ts diff --git a/.changeset/nasty-deers-clap.md b/.changeset/nasty-deers-clap.md new file mode 100644 index 000000000000..c8506e809bf7 --- /dev/null +++ b/.changeset/nasty-deers-clap.md @@ -0,0 +1,7 @@ +--- +"@ledgerhq/types-live": patch +"ledger-live-desktop": patch +"@ledgerhq/live-common": patch +--- + +[swap] setup demo3 flag and routes diff --git a/apps/ledger-live-desktop/src/renderer/analytics/segment.ts b/apps/ledger-live-desktop/src/renderer/analytics/segment.ts index 0fa152742462..3264951f619f 100644 --- a/apps/ledger-live-desktop/src/renderer/analytics/segment.ts +++ b/apps/ledger-live-desktop/src/renderer/analytics/segment.ts @@ -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; @@ -109,6 +110,7 @@ const getPtxAttributes = () => { ptxSwapMoonpayProviderEnabled, ptxSwapLiveAppDemoZero, ptxSwapLiveAppDemoOne, + ptxSwapLiveAppDemoThree, ptxSwapThorswapProvider, ptxSwapExodusProvider, }; diff --git a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/App/App.tsx b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/App/App.tsx new file mode 100644 index 000000000000..ecbb5d8de74b --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/App/App.tsx @@ -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 Unable to load application: missing manifest; + } + + if (unavailable) { + // TODO: fix with proper error handling + return Unable to load application: Unavailable; + } + + return ( + + setUnavailable(true)} /> + + ); +} diff --git a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/App/index.ts b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/App/index.ts new file mode 100644 index 000000000000..c85430266518 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/App/index.ts @@ -0,0 +1 @@ +export * from "./App"; diff --git a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/FormSummary/SectionTarget.tsx b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/FormSummary/SectionTarget.tsx index 6c1f35cf3bca..29400edebe52 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/FormSummary/SectionTarget.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/FormSummary/SectionTarget.tsx @@ -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"; diff --git a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/FormSummary/index.tsx b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/FormSummary/index.tsx index ef586fd52986..ebc31449d6bd 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/FormSummary/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/FormSummary/index.tsx @@ -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"; diff --git a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/Migrations/SwapMigrationUI.tsx b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/Migrations/SwapMigrationUI.tsx index ffa34267d566..3d3a6d684b6b 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/Migrations/SwapMigrationUI.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/Migrations/SwapMigrationUI.tsx @@ -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%; diff --git a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/SwapWebView.tsx b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/SwapWebView.tsx index 79da6cb9c09e..aa176673840d 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/SwapWebView.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/SwapWebView.tsx @@ -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) { @@ -86,18 +86,19 @@ export type SwapProps = { export type SwapWebProps = { manifest: LiveAppManifest; + liveAppUnavailable: () => void; + setQuoteState?: (next: CustomSwapQuotesState) => void; swapState?: Partial; - 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` @@ -181,7 +182,7 @@ const SwapWebView = ({ const fromUnit = sourceCurrency?.units[0]; if (!quote.params) { - setQuoteState({ + setQuoteState?.({ amountTo: undefined, swapError: undefined, counterValue: undefined, @@ -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, { @@ -209,7 +210,7 @@ const SwapWebView = ({ }); return Promise.resolve(); case "maxAmountError": - setQuoteState({ + setQuoteState?.({ amountTo: undefined, counterValue: undefined, swapError: new SwapExchangeRateAmountTooLow(undefined, { @@ -236,7 +237,7 @@ const SwapWebView = ({ ) : undefined; - setQuoteState({ + setQuoteState?.({ amountTo, counterValue: counterValue, swapError: undefined, @@ -487,7 +488,7 @@ const SwapWebView = ({ useEffect(() => { // Determine the new quote state based on network status - setQuoteState({ + setQuoteState?.({ amountTo: undefined, counterValue: undefined, ...{ diff --git a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/SwapWebViewDemo3.tsx b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/SwapWebViewDemo3.tsx new file mode 100644 index 000000000000..1fa0a376809c --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/SwapWebViewDemo3.tsx @@ -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(null); + const { setDrawer } = React.useContext(context); + const [webviewState, setWebviewState] = useState(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 && ( + + )} + + + + + + ); +}; + +export default SwapWebView; diff --git a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/index.tsx b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/index.tsx index 1d8c12ff6644..60673be70446 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/index.tsx @@ -37,6 +37,7 @@ 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"; @@ -44,7 +45,6 @@ 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"]; diff --git a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/useIsSwapLiveFlagEnabled.ts b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/useIsSwapLiveFlagEnabled.ts deleted file mode 100644 index cb64a7fe6214..000000000000 --- a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/useIsSwapLiveFlagEnabled.ts +++ /dev/null @@ -1,18 +0,0 @@ -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"); - - if (demoZero?.enabled === demoOne?.enabled) return false; - - switch (flag) { - case "ptxSwapLiveAppDemoOne": - return Boolean(demoOne?.enabled); - case "ptxSwapLiveAppDemoZero": - return Boolean(demoZero?.enabled && !demoOne?.enabled); - default: - throw new Error(`Unknown Swap Live App flag: ${flag}`); - } -}; diff --git a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/hooks/useIsSwapLiveFlagEnabled.ts b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/hooks/useIsSwapLiveFlagEnabled.ts new file mode 100644 index 000000000000..9ae7f7d4f48b --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/hooks/useIsSwapLiveFlagEnabled.ts @@ -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}`); +}; diff --git a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/index.tsx b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/index.tsx index 03fdadc93d32..1e058217d94c 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/index.tsx @@ -6,6 +6,8 @@ import SwapForm from "./Form"; import SwapHistory from "./History"; import SwapNavbar from "./Navbar"; +import { SwapApp } from "./App"; +import { useIsSwapLiveFlagEnabled } from "./hooks/useIsSwapLiveFlagEnabled"; const Body = styled(Box)` flex: 1; `; @@ -45,17 +47,20 @@ const GlobalStyle = createGlobalStyle` } `; -function Swap2() { +const Swap2 = () => { + const isDemo3Enabled = useIsSwapLiveFlagEnabled("ptxSwapLiveAppDemoThree"); + const SwapPage = isDemo3Enabled ? SwapApp : SwapForm; + return (
- +
); -} +}; export default Swap2; diff --git a/libs/ledger-live-common/src/exchange/swap/hooks/live-app-migration/useSwapLiveConfig.test.ts b/libs/ledger-live-common/src/exchange/swap/hooks/live-app-migration/useSwapLiveConfig.test.ts index c904e9eb72de..b2357beb294c 100644 --- a/libs/ledger-live-common/src/exchange/swap/hooks/live-app-migration/useSwapLiveConfig.test.ts +++ b/libs/ledger-live-common/src/exchange/swap/hooks/live-app-migration/useSwapLiveConfig.test.ts @@ -13,44 +13,51 @@ const useMockFeature = useFeature as jest.Mock; describe("useSwapLiveConfig", () => { // Setup the mock for useFeatureFlags to return an object with getFeature - const setupFeatureFlagsMock = (demoZeroConfig, demoOneConfig) => { - useMockFeature.mockImplementation(flagName => { - switch (flagName) { - case "ptxSwapLiveAppDemoZero": - return demoZeroConfig; - case "ptxSwapLiveAppDemoOne": - return demoOneConfig; - default: - return null; - } - }); + const setupFeatureFlagsMock = ( + flags: Partial<{ enabled: boolean; params: { manifest_id: string } }>[], + ) => { + const flagsKeys = [ + "ptxSwapLiveAppDemoZero", + "ptxSwapLiveAppDemoOne", + "ptxSwapLiveAppDemoThree", + ]; + + useMockFeature.mockImplementation(flagName => flags[flagsKeys.indexOf(flagName)] ?? null); }; afterEach(() => { jest.clearAllMocks(); }); - it("should return null if both features have the same enabled state", () => { - setupFeatureFlagsMock({ enabled: true }, { enabled: true }); + it("should highest priority flag if all features have the same enabled state", () => { + setupFeatureFlagsMock([ + { enabled: true, params: { manifest_id: "demo_0" } }, + { enabled: true, params: { manifest_id: "demo_1" } }, + { enabled: true, params: { manifest_id: "demo_3" } }, + ]); const { result } = renderHook(() => useSwapLiveConfig()); - expect(result.current).toBeNull(); + expect(result.current).not.toBeNull(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect((result.current?.params as any)?.manifest_id).toEqual("demo_3"); }); it("should return null if both features are disabled", () => { - setupFeatureFlagsMock({ enabled: false }, { enabled: false }); + setupFeatureFlagsMock([{ enabled: false }, { enabled: false }, { enabled: false }]); const { result } = renderHook(() => useSwapLiveConfig()); expect(result.current).toBeNull(); }); it("should return demoZero if only demoZero is enabled", () => { - setupFeatureFlagsMock( + setupFeatureFlagsMock([ { enabled: true, params: { manifest_id: "swap-live-app-demo-0" }, }, { enabled: false }, - ); + { enabled: false }, + ]); const { result } = renderHook(() => useSwapLiveConfig()); expect(result.current).toEqual({ enabled: true, @@ -59,17 +66,35 @@ describe("useSwapLiveConfig", () => { }); it("should return demoOne if only demoOne is enabled", () => { - setupFeatureFlagsMock( + setupFeatureFlagsMock([ { enabled: false }, { enabled: true, params: { manifest_id: "swap-live-app-demo-1" }, }, - ); + { enabled: false }, + ]); const { result } = renderHook(() => useSwapLiveConfig()); expect(result.current).toEqual({ enabled: true, params: { manifest_id: "swap-live-app-demo-1" }, }); }); + + it("should return demoThree if only demoOne is enabled", () => { + setupFeatureFlagsMock([ + { enabled: false }, + { + enabled: true, + params: { manifest_id: "swap-live-app-demo-3" }, + }, + { enabled: false }, + ]); + + const { result } = renderHook(() => useSwapLiveConfig()); + expect(result.current).toEqual({ + enabled: true, + params: { manifest_id: "swap-live-app-demo-3" }, + }); + }); }); diff --git a/libs/ledger-live-common/src/exchange/swap/hooks/live-app-migration/useSwapLiveConfig.ts b/libs/ledger-live-common/src/exchange/swap/hooks/live-app-migration/useSwapLiveConfig.ts index b02089d96425..6cf9ec52c24f 100644 --- a/libs/ledger-live-common/src/exchange/swap/hooks/live-app-migration/useSwapLiveConfig.ts +++ b/libs/ledger-live-common/src/exchange/swap/hooks/live-app-migration/useSwapLiveConfig.ts @@ -4,8 +4,11 @@ import { useFeature } from "../../../../featureFlags"; export function useSwapLiveConfig() { const demoZero = useFeature("ptxSwapLiveAppDemoZero"); const demoOne = useFeature("ptxSwapLiveAppDemoOne"); + const demoThree = useFeature("ptxSwapLiveAppDemoThree"); - if (demoZero?.enabled === demoOne?.enabled) return null; + // Order is important in order to get the first enabled flag + const flags = [demoThree, demoOne, demoZero]; + const enabledFlag = flags.find(flag => flag?.enabled); - return demoZero?.enabled && !demoOne?.enabled ? demoZero : demoOne?.enabled ? demoOne : null; + return enabledFlag ?? null; } diff --git a/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts b/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts index 7f281c759260..4e5ebf851286 100644 --- a/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts +++ b/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts @@ -406,6 +406,13 @@ export const DEFAULT_FEATURES: Features = { }, }, + ptxSwapLiveAppDemoThree: { + enabled: false, + params: { + manifest_id: "swap-live-app-demo-3", + }, + }, + ptxSwapMoonpayProvider: DEFAULT_FEATURE, ptxSwapExodusProvider: DEFAULT_FEATURE, ptxSwapThorswapProvider: DEFAULT_FEATURE, diff --git a/libs/ledgerjs/packages/types-live/src/feature.ts b/libs/ledgerjs/packages/types-live/src/feature.ts index cc5c69b93a67..b1ebcd158956 100644 --- a/libs/ledgerjs/packages/types-live/src/feature.ts +++ b/libs/ledgerjs/packages/types-live/src/feature.ts @@ -164,6 +164,7 @@ export type Features = CurrencyFeatures & { fetchAdditionalCoins: Feature_FetchAdditionalCoins; ptxSwapLiveAppDemoZero: Feature_PtxSwapLiveAppDemoZero; ptxSwapLiveAppDemoOne: Feature_PtxSwapLiveAppDemoZero; + ptxSwapLiveAppDemoThree: Feature_PtxSwapLiveAppDemoZero; ptxSwapMoonpayProvider: Feature_PtxSwapMoonpayProvider; ptxSwapExodusProvider: Feature_PtxSwapExodusProvider; ptxSwapThorswapProvider: Feature_PtxSwapThorswapProvider;