From ab2f5a04a3b17282784741a26482b965a0afe461 Mon Sep 17 00:00:00 2001 From: Jhonatan Gonzalez Date: Wed, 23 Oct 2024 08:22:03 +1100 Subject: [PATCH] [NO CHANGELOG][Checkout Widget] CM-1001 Add funds <> Other widgets integration (#2334) --- packages/checkout/sdk/src/index.ts | 1 + .../checkout/widgets-lib/src/lib/constants.ts | 5 + .../checkout => lib}/hooks/useAsyncMemo.ts | 0 .../src/lib/withDefaultWidgetConfig.test.ts | 11 ++ .../src/lib/withDefaultWidgetConfig.ts | 6 + .../src/views/top-up/TopUpView.tsx | 173 ++++++++++-------- .../src/widgets/sale/SaleWidget.tsx | 23 ++- .../src/widgets/wallet/WalletWidget.tsx | 22 ++- .../components/BalanceItem/BalanceItem.tsx | 140 +++++++++----- .../wallet/context/WalletContext.test.ts | 4 +- .../widgets/wallet/context/WalletContext.ts | 6 + .../src/components/ui/checkout/checkout.tsx | 27 +-- .../src/components/ui/wallet/wallet.tsx | 115 ++++++++++-- 13 files changed, 376 insertions(+), 157 deletions(-) rename packages/checkout/widgets-lib/src/{widgets/checkout => lib}/hooks/useAsyncMemo.ts (100%) diff --git a/packages/checkout/sdk/src/index.ts b/packages/checkout/sdk/src/index.ts index 9e1b13d43f..5c3677dbc8 100644 --- a/packages/checkout/sdk/src/index.ts +++ b/packages/checkout/sdk/src/index.ts @@ -40,6 +40,7 @@ export type { EIP1193Provider, EIP6963ProviderInfo, EIP6963ProviderDetail, + AddFundsConfig, } from './types'; export type { diff --git a/packages/checkout/widgets-lib/src/lib/constants.ts b/packages/checkout/widgets-lib/src/lib/constants.ts index f1bc8f27e7..2a762d16a9 100644 --- a/packages/checkout/widgets-lib/src/lib/constants.ts +++ b/packages/checkout/widgets-lib/src/lib/constants.ts @@ -76,6 +76,11 @@ export const DEFAULT_SWAP_ENABLED = true; */ export const DEFAULT_BRIDGE_ENABLED = true; +/** + * Checkout Widget default add funds enabled flag + */ +export const DEFAULT_ADD_FUNDS_ENABLED = true; + /** * Checkout Widget default refresh quote interval */ diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useAsyncMemo.ts b/packages/checkout/widgets-lib/src/lib/hooks/useAsyncMemo.ts similarity index 100% rename from packages/checkout/widgets-lib/src/widgets/checkout/hooks/useAsyncMemo.ts rename to packages/checkout/widgets-lib/src/lib/hooks/useAsyncMemo.ts diff --git a/packages/checkout/widgets-lib/src/lib/withDefaultWidgetConfig.test.ts b/packages/checkout/widgets-lib/src/lib/withDefaultWidgetConfig.test.ts index 34deed69a9..721d4d79d9 100644 --- a/packages/checkout/widgets-lib/src/lib/withDefaultWidgetConfig.test.ts +++ b/packages/checkout/widgets-lib/src/lib/withDefaultWidgetConfig.test.ts @@ -6,6 +6,7 @@ import { DEFAULT_ENV, DEFAULT_ON_RAMP_ENABLED, DEFAULT_SWAP_ENABLED, + DEFAULT_ADD_FUNDS_ENABLED, DEFAULT_THEME, } from './constants'; @@ -17,6 +18,7 @@ describe('withDefaultWidgetConfig', () => { isOnRampEnabled: DEFAULT_ON_RAMP_ENABLED, isSwapEnabled: DEFAULT_SWAP_ENABLED, isBridgeEnabled: DEFAULT_BRIDGE_ENABLED, + isAddFundsEnabled: DEFAULT_ADD_FUNDS_ENABLED, }); expect(withDefaultWidgetConfigs(undefined)).toEqual({ theme: DEFAULT_THEME, @@ -24,6 +26,7 @@ describe('withDefaultWidgetConfig', () => { isOnRampEnabled: DEFAULT_ON_RAMP_ENABLED, isSwapEnabled: DEFAULT_SWAP_ENABLED, isBridgeEnabled: DEFAULT_BRIDGE_ENABLED, + isAddFundsEnabled: DEFAULT_ADD_FUNDS_ENABLED, }); }); @@ -38,6 +41,7 @@ describe('withDefaultWidgetConfig', () => { isOnRampEnabled: DEFAULT_ON_RAMP_ENABLED, isSwapEnabled: DEFAULT_SWAP_ENABLED, isBridgeEnabled: DEFAULT_BRIDGE_ENABLED, + isAddFundsEnabled: DEFAULT_ADD_FUNDS_ENABLED, }); expect( withDefaultWidgetConfigs({ @@ -49,6 +53,7 @@ describe('withDefaultWidgetConfig', () => { isOnRampEnabled: false, isSwapEnabled: DEFAULT_SWAP_ENABLED, isBridgeEnabled: DEFAULT_BRIDGE_ENABLED, + isAddFundsEnabled: DEFAULT_ADD_FUNDS_ENABLED, }); }); @@ -63,6 +68,7 @@ describe('withDefaultWidgetConfig', () => { isOnRampEnabled: DEFAULT_ON_RAMP_ENABLED, isSwapEnabled: DEFAULT_SWAP_ENABLED, isBridgeEnabled: DEFAULT_BRIDGE_ENABLED, + isAddFundsEnabled: DEFAULT_ADD_FUNDS_ENABLED, }); expect( @@ -75,6 +81,7 @@ describe('withDefaultWidgetConfig', () => { isOnRampEnabled: DEFAULT_ON_RAMP_ENABLED, isSwapEnabled: DEFAULT_SWAP_ENABLED, isBridgeEnabled: DEFAULT_BRIDGE_ENABLED, + isAddFundsEnabled: DEFAULT_ADD_FUNDS_ENABLED, }); expect( @@ -87,6 +94,7 @@ describe('withDefaultWidgetConfig', () => { isOnRampEnabled: DEFAULT_ON_RAMP_ENABLED, isSwapEnabled: DEFAULT_SWAP_ENABLED, isBridgeEnabled: DEFAULT_BRIDGE_ENABLED, + isAddFundsEnabled: DEFAULT_ADD_FUNDS_ENABLED, }); }); @@ -101,6 +109,7 @@ describe('withDefaultWidgetConfig', () => { isOnRampEnabled: DEFAULT_ON_RAMP_ENABLED, isSwapEnabled: DEFAULT_SWAP_ENABLED, isBridgeEnabled: DEFAULT_BRIDGE_ENABLED, + isAddFundsEnabled: DEFAULT_ADD_FUNDS_ENABLED, }); expect( @@ -113,6 +122,7 @@ describe('withDefaultWidgetConfig', () => { isOnRampEnabled: DEFAULT_ON_RAMP_ENABLED, isSwapEnabled: DEFAULT_SWAP_ENABLED, isBridgeEnabled: DEFAULT_BRIDGE_ENABLED, + isAddFundsEnabled: DEFAULT_ADD_FUNDS_ENABLED, }); expect( @@ -125,6 +135,7 @@ describe('withDefaultWidgetConfig', () => { isOnRampEnabled: DEFAULT_ON_RAMP_ENABLED, isSwapEnabled: DEFAULT_SWAP_ENABLED, isBridgeEnabled: DEFAULT_BRIDGE_ENABLED, + isAddFundsEnabled: DEFAULT_ADD_FUNDS_ENABLED, }); }); }); diff --git a/packages/checkout/widgets-lib/src/lib/withDefaultWidgetConfig.ts b/packages/checkout/widgets-lib/src/lib/withDefaultWidgetConfig.ts index fbc8fa1322..75bab77411 100644 --- a/packages/checkout/widgets-lib/src/lib/withDefaultWidgetConfig.ts +++ b/packages/checkout/widgets-lib/src/lib/withDefaultWidgetConfig.ts @@ -1,6 +1,7 @@ import { Environment } from '@imtbl/config'; import { WidgetTheme } from '@imtbl/checkout-sdk'; import { + DEFAULT_ADD_FUNDS_ENABLED, DEFAULT_BRIDGE_ENABLED, DEFAULT_ENV, DEFAULT_ON_RAMP_ENABLED, @@ -14,6 +15,7 @@ export type StrongCheckoutWidgetsConfig = { isOnRampEnabled: boolean; isSwapEnabled: boolean; isBridgeEnabled: boolean; + isAddFundsEnabled: boolean; }; function getValidTheme(theme?: string): WidgetTheme { @@ -52,4 +54,8 @@ export const withDefaultWidgetConfigs = ( DEFAULT_BRIDGE_ENABLED, configs?.isBridgeEnabled?.toString(), ), + isAddFundsEnabled: getValidBoolean( + DEFAULT_ADD_FUNDS_ENABLED, + configs?.isAddFundsEnabled?.toString(), + ), }); diff --git a/packages/checkout/widgets-lib/src/views/top-up/TopUpView.tsx b/packages/checkout/widgets-lib/src/views/top-up/TopUpView.tsx index 38710b0f13..93c94373ee 100644 --- a/packages/checkout/widgets-lib/src/views/top-up/TopUpView.tsx +++ b/packages/checkout/widgets-lib/src/views/top-up/TopUpView.tsx @@ -1,6 +1,6 @@ import { Body, Box, Heading } from '@biom3/react'; import { - ReactNode, useContext, useEffect, useState, + ReactNode, useContext, useEffect, useMemo, useState, } from 'react'; import { StandardAnalyticsControlTypes } from '@imtbl/react-analytics'; import { @@ -38,6 +38,7 @@ import { import { OnRampWidgetViews } from '../../context/view-context/OnRampViewContextTypes'; import { EventTargetContext } from '../../context/event-target-context/EventTargetContext'; import { TopUpMenuItem, TopUpMenuItemProps } from './TopUpMenuItem'; +import { useAsyncMemo } from '../../lib/hooks/useAsyncMemo'; type $Dictionary = { [key: string]: T }; @@ -48,6 +49,7 @@ interface TopUpViewProps { showOnrampOption: boolean; showSwapOption: boolean; showBridgeOption: boolean; + showAddFundsOption?: boolean; tokenAddress?: string; amount?: string; analytics: { @@ -81,6 +83,7 @@ export function TopUpView({ showOnrampOption, showSwapOption, showBridgeOption, + showAddFundsOption, tokenAddress, amount, analytics, @@ -106,20 +109,39 @@ export function TopUpView({ const [onRampFeesPercentage, setOnRampFeesPercentage] = useState('-.--'); const swapFeesInFiat = '0.05'; const [, setBridgeFeesInFiat] = useState('-.--'); - const [isSwapAvailable, setIsSwapAvailable] = useState(true); const title = heading ? t(...heading) : t('views.TOP_UP_VIEW.header.title'); const description = subheading ? t(...subheading) : null; const { page, track } = useAnalytics(); + const isSwapAvailable = useAsyncMemo(async () => { + if (!checkout) return undefined; + try { + return checkout.isSwapAvailable(); + } catch (error) { + return false; + } + }, [checkout]); + useMount(() => { - page({ - userJourney, - screen: 'TopUp', - }); + page({ userJourney, screen: 'TopUp' }); }); + // Go to add funds widget if available + useMount( + () => { + if (showAddFundsOption) { + orchestrationEvents.sendRequestAddFundsEvent(eventTarget, widgetEvent, { + toTokenAddress: tokenAddress ?? '', + toAmount: amount ?? '', + }); + } + }, + () => showAddFundsOption !== undefined, + [showAddFundsOption], + ); + useEffect(() => { if (!cryptoFiatDispatch) return; cryptoFiatDispatch({ @@ -156,14 +178,6 @@ export function TopUpView({ })(); }, [checkout !== undefined]); - // Check if swap is available - useEffect(() => { - if (!checkout) return; - (async () => { - setIsSwapAvailable(await checkout.isSwapAvailable()); - })(); - }, [checkout !== undefined]); - const localTrack = ( control: string, extras: any, @@ -287,68 +301,75 @@ export function TopUpView({ ); - const topUpFeatures: TopUpFeatures[] = [ - { - testId: 'onramp', - icon: 'BankCard', - iconVariant: 'bold', - textConfigKey: 'views.TOP_UP_VIEW.topUpOptions.debit', - onClickEvent: onClickOnRamp, - fee: () => renderFees( - `${t( - 'views.TOP_UP_VIEW.topUpOptions.debit.subcaption', - )} ≈ ${onRampFeesPercentage}%`, - ), - isAvailable: true, - isEnabled: showOnrampOption, - }, - { - testId: 'onramp', - icon: 'BankCard', - textConfigKey: 'views.TOP_UP_VIEW.topUpOptions.credit', - onClickEvent: onClickOnRamp, - fee: () => renderFees( - `${t( - 'views.TOP_UP_VIEW.topUpOptions.credit.subcaption', - )} ≈ ${onRampFeesPercentage}%`, - ), - isAvailable: true, - isEnabled: showOnrampOption, - }, - { - testId: 'advanced', - icon: 'Minting', - iconVariant: 'bold', - intentIcon: 'JumpTo', - textConfigKey: 'views.TOP_UP_VIEW.topUpOptions.advanced', - onClickEvent: onClickAdvancedOptions, - fee: () => renderFees(''), - isAvailable: true, - isEnabled: true, - }, - { - testId: 'swap', - icon: 'Swap', - textConfigKey: 'views.TOP_UP_VIEW.topUpOptions.swap', - onClickEvent: onClickSwap, - fee: () => renderFees( - `${t( - 'views.TOP_UP_VIEW.topUpOptions.swap.subcaption', - )} ≈ $${swapFeesInFiat} ${fiatSymbol.toUpperCase()}`, - ), - isAvailable: isSwapAvailable, - isEnabled: showSwapOption, - }, - { - testId: 'bridge', - icon: 'ArrowForward', - textConfigKey: 'views.TOP_UP_VIEW.topUpOptions.bridge', - onClickEvent: onClickBridge, - fee: () => renderFees(''), - isAvailable: true, - isEnabled: showBridgeOption, - }, - ]; + const topUpFeatures: TopUpFeatures[] = useMemo( + () => [ + { + testId: 'onramp', + icon: 'BankCard', + iconVariant: 'bold', + textConfigKey: 'views.TOP_UP_VIEW.topUpOptions.debit', + onClickEvent: onClickOnRamp, + fee: () => renderFees( + `${t( + 'views.TOP_UP_VIEW.topUpOptions.debit.subcaption', + )} ≈ ${onRampFeesPercentage}%`, + ), + isAvailable: true, + isEnabled: showOnrampOption, + }, + { + testId: 'onramp', + icon: 'BankCard', + textConfigKey: 'views.TOP_UP_VIEW.topUpOptions.credit', + onClickEvent: onClickOnRamp, + fee: () => renderFees( + `${t( + 'views.TOP_UP_VIEW.topUpOptions.credit.subcaption', + )} ≈ ${onRampFeesPercentage}%`, + ), + isAvailable: true, + isEnabled: showOnrampOption, + }, + { + testId: 'advanced', + icon: 'Minting', + iconVariant: 'bold', + intentIcon: 'JumpTo', + textConfigKey: 'views.TOP_UP_VIEW.topUpOptions.advanced', + onClickEvent: onClickAdvancedOptions, + fee: () => renderFees(''), + isAvailable: true, + isEnabled: true, + }, + { + testId: 'swap', + icon: 'Swap', + textConfigKey: 'views.TOP_UP_VIEW.topUpOptions.swap', + onClickEvent: onClickSwap, + fee: () => renderFees( + `${t( + 'views.TOP_UP_VIEW.topUpOptions.swap.subcaption', + )} ≈ $${swapFeesInFiat} ${fiatSymbol.toUpperCase()}`, + ), + isAvailable: !!isSwapAvailable, + isEnabled: showSwapOption, + }, + { + testId: 'bridge', + icon: 'ArrowForward', + textConfigKey: 'views.TOP_UP_VIEW.topUpOptions.bridge', + onClickEvent: onClickBridge, + fee: () => renderFees(''), + isAvailable: true, + isEnabled: showBridgeOption, + }, + ], + [showAddFundsOption, showBridgeOption, showOnrampOption, showSwapOption], + ); + + // if swap is available, don't show top up view + // await for redirect to add funds widget + if (showAddFundsOption) return null; return ( ; +type OptionalWidgetParams = Pick< +SaleWidgetParams, +'excludePaymentTypes' | 'excludeFiatCurrencies' | 'customOrderData' +>; type RequiredWidgetParams = Required< Omit >; @@ -87,6 +92,21 @@ export default function SaleWidget(props: SaleWidgetProps) { const loadingText = viewState.view.data?.loadingText || t('views.LOADING_VIEW.text'); + const isAddFundsAvailable = useAsyncMemo(async () => { + if (!checkout) return false; + + try { + const isSwapAvailable = await checkout.isSwapAvailable(); + const addFundsConfig = (await checkout.config.remote.getConfig( + 'addfunds', + )) as AddFundsConfig; + + return addFundsConfig.enabled && isSwapAvailable; + } catch (error) { + return false; + } + }, [checkout]); + useEffect(() => { if (!checkout || !provider) return; @@ -173,6 +193,7 @@ export default function SaleWidget(props: SaleWidgetProps) { showOnrampOption={config.isOnRampEnabled} showSwapOption={false} showBridgeOption={config.isBridgeEnabled} + showAddFundsOption={isAddFundsAvailable} onCloseButtonClick={() => sendSaleWidgetCloseEvent(eventTarget)} onBackButtonClick={() => { viewDispatch({ diff --git a/packages/checkout/widgets-lib/src/widgets/wallet/WalletWidget.tsx b/packages/checkout/widgets-lib/src/widgets/wallet/WalletWidget.tsx index e7aaee4aba..f26f7cbe43 100644 --- a/packages/checkout/widgets-lib/src/widgets/wallet/WalletWidget.tsx +++ b/packages/checkout/widgets-lib/src/widgets/wallet/WalletWidget.tsx @@ -5,7 +5,7 @@ import { useReducer, } from 'react'; import { useTranslation } from 'react-i18next'; -import { IMTBLWidgetEvents, WalletWidgetParams } from '@imtbl/checkout-sdk'; +import { IMTBLWidgetEvents, WalletWidgetParams, AddFundsConfig } from '@imtbl/checkout-sdk'; import { UserJourney } from '../../context/analytics-provider/SegmentAnalyticsProvider'; import { initialWalletState, @@ -54,6 +54,7 @@ export default function WalletWidget(props: WalletWidgetInputs) { isOnRampEnabled, isSwapEnabled, isBridgeEnabled, + isAddFundsEnabled, theme, }, walletConfig: { @@ -84,6 +85,8 @@ export default function WalletWidget(props: WalletWidgetInputs) { [viewState, viewDispatch], ); + const { supportedTopUps } = walletState; + const { balancesLoading, refreshBalances } = useBalance({ checkout, provider, @@ -116,13 +119,16 @@ export default function WalletWidget(props: WalletWidgetInputs) { (async () => { if (!checkout) return; - let checkSwapAvailable; - + let checkSwapAvailable = false; try { checkSwapAvailable = await checkout.isSwapAvailable(); - } catch (err: any) { - checkSwapAvailable = false; - } + } catch { /* */ } + + let checkAddFundsEnabled = isAddFundsEnabled; + try { + const addFundsConfig = (await checkout.config.remote.getConfig('addfunds') as AddFundsConfig); + checkAddFundsEnabled = addFundsConfig.enabled && isAddFundsEnabled; + } catch { /* */ } walletDispatch({ payload: { @@ -132,6 +138,8 @@ export default function WalletWidget(props: WalletWidgetInputs) { isSwapEnabled, isOnRampEnabled, isSwapAvailable: checkSwapAvailable, + isAddFundsEnabled: checkAddFundsEnabled, + isAddFundsAvailable: checkSwapAvailable && checkAddFundsEnabled, }, }, }); @@ -227,10 +235,10 @@ export default function WalletWidget(props: WalletWidgetInputs) { analytics={{ userJourney: UserJourney.WALLET }} widgetEvent={IMTBLWidgetEvents.IMTBL_WALLET_WIDGET_EVENT} checkout={checkout} - provider={provider} showOnrampOption={isOnRampEnabled} showSwapOption={isSwapEnabled} showBridgeOption={isBridgeEnabled} + showAddFundsOption={supportedTopUps?.isAddFundsAvailable} onCloseButtonClick={() => sendWalletWidgetCloseEvent(eventTarget)} /> )} diff --git a/packages/checkout/widgets-lib/src/widgets/wallet/components/BalanceItem/BalanceItem.tsx b/packages/checkout/widgets-lib/src/widgets/wallet/components/BalanceItem/BalanceItem.tsx index 123749bcce..1554748eb0 100644 --- a/packages/checkout/widgets-lib/src/widgets/wallet/components/BalanceItem/BalanceItem.tsx +++ b/packages/checkout/widgets-lib/src/widgets/wallet/components/BalanceItem/BalanceItem.tsx @@ -3,7 +3,10 @@ import { useContext, useEffect, useMemo, useState, } from 'react'; import { - IMTBLWidgetEvents, TokenFilterTypes, TokenInfo, WidgetTheme, + IMTBLWidgetEvents, + TokenFilterTypes, + TokenInfo, + WidgetTheme, } from '@imtbl/checkout-sdk'; import { Environment } from '@imtbl/config'; import { TokenImage } from '../../../../components/TokenImage/TokenImage'; @@ -16,7 +19,10 @@ import { formatZeroAmount, tokenValueFormat } from '../../../../lib/utils'; import { ConnectLoaderContext } from '../../../../context/connect-loader-context/ConnectLoaderContext'; import { isPassportProvider } from '../../../../lib/provider'; import { EventTargetContext } from '../../../../context/event-target-context/EventTargetContext'; -import { UserJourney, useAnalytics } from '../../../../context/analytics-provider/SegmentAnalyticsProvider'; +import { + UserJourney, + useAnalytics, +} from '../../../../context/analytics-provider/SegmentAnalyticsProvider'; export interface BalanceItemProps { balanceInfo: BalanceInfo; @@ -38,55 +44,120 @@ export function BalanceItem({ const [isOnRampEnabled, setIsOnRampEnabled] = useState(); const [isBridgeEnabled, setIsBridgeEnabled] = useState(); const [isSwapEnabled, setIsSwapEnabled] = useState(); + const [isAddFundsEnabled, setIsAddFundsEnabled] = useState(); const { eventTargetState: { eventTarget }, } = useContext(EventTargetContext); const [onRampAllowedTokens, setOnRampAllowedTokens] = useState( [], ); + const [swapAllowedTokens, setSwapAllowedTokens] = useState([]); const isPassport = isPassportProvider(provider); useEffect(() => { - const getOnRampAllowedTokens = async () => { + (async () => { if (!checkout) return; - const onRampAllowedTokensResult = await checkout.getTokenAllowList({ + const onRampTokens = checkout.getTokenAllowList({ type: TokenFilterTypes.ONRAMP, chainId: getL2ChainId(checkout.config), }); + const swapTokens = checkout.getTokenAllowList({ + type: TokenFilterTypes.SWAP, + chainId: getL2ChainId(checkout.config), + }); + + const [onRampAllowedTokensResult, swapAllowedTokensResult] = await Promise.all([onRampTokens, swapTokens]); + setOnRampAllowedTokens(onRampAllowedTokensResult.tokens); - }; - getOnRampAllowedTokens(); + setSwapAllowedTokens(swapAllowedTokensResult.tokens); + })(); }, [checkout]); useEffect(() => { if (!network || !supportedTopUps || !checkout) return; - const enableAddCoin = network.chainId === getL2ChainId(checkout.config) + const enableAddCoin = (supportedTopUps?.isAddFundsEnabled ?? true) + && (supportedTopUps?.isSwapAvailable ?? true); + setIsAddFundsEnabled(enableAddCoin); + + const enableBuyCoin = !enableAddCoin + && network.chainId === getL2ChainId(checkout.config) && (supportedTopUps?.isOnRampEnabled ?? true); - setIsOnRampEnabled(enableAddCoin); + setIsOnRampEnabled(enableBuyCoin); - const enableMoveCoin = (network.chainId === getL1ChainId(checkout.config) - || network.chainId === getL2ChainId(checkout.config)) + const enableMoveCoin = !enableAddCoin + && (network.chainId === getL1ChainId(checkout.config) + || network.chainId === getL2ChainId(checkout.config)) && (supportedTopUps?.isBridgeEnabled ?? true); setIsBridgeEnabled(enableMoveCoin); - const enableSwapCoin = network.chainId === getL2ChainId(checkout.config) - && (supportedTopUps?.isSwapEnabled ?? true) - && (supportedTopUps?.isSwapAvailable ?? true); + const enableSwapCoin = !enableAddCoin + && network.chainId === getL2ChainId(checkout.config) + && (supportedTopUps?.isSwapEnabled ?? true) + && (supportedTopUps?.isSwapAvailable ?? true); setIsSwapEnabled(enableSwapCoin); }, [network, supportedTopUps, checkout, isPassport]); - const showAddMenuItem = useMemo( - () => Boolean( + const showAddTokenMenuItem = useMemo(() => { + const canBuy = Boolean( isOnRampEnabled - && onRampAllowedTokens.length > 0 - && onRampAllowedTokens.find( - (token) => token.address?.toLowerCase() === balanceInfo.address?.toLowerCase(), - ), - ), - [isOnRampEnabled, onRampAllowedTokens], - ); + && onRampAllowedTokens.length > 0 + && onRampAllowedTokens.find( + (token) => token.address?.toLowerCase() === balanceInfo.address?.toLowerCase(), + ), + ); + + const canAdd = Boolean( + isAddFundsEnabled + && swapAllowedTokens.length > 0 + && swapAllowedTokens.find( + (token) => token.address?.toLowerCase() === balanceInfo.address?.toLowerCase(), + ), + ); + + return canBuy || canAdd; + }, [ + isOnRampEnabled, + onRampAllowedTokens, + isAddFundsEnabled, + swapAllowedTokens, + ]); + + const handleAddTokenClick = () => { + track({ + userJourney: UserJourney.WALLET, + screen: 'WalletBalances', + control: 'AddTokens', + controlType: 'Button', + extras: { + tokenSymbol: balanceInfo.symbol, + tokenAddress: balanceInfo.address, + }, + }); + + if (isAddFundsEnabled) { + orchestrationEvents.sendRequestAddFundsEvent( + eventTarget, + IMTBLWidgetEvents.IMTBL_WALLET_WIDGET_EVENT, + { + toAmount: '', + toTokenAddress: balanceInfo.address ?? '', + }, + ); + + return; + } + + orchestrationEvents.sendRequestOnrampEvent( + eventTarget, + IMTBLWidgetEvents.IMTBL_WALLET_WIDGET_EVENT, + { + tokenAddress: balanceInfo.address ?? '', + amount: '', + }, + ); + }; return ( @@ -108,7 +179,7 @@ export function BalanceItem({ price={tokenValueFormat(balanceInfo.balance)} fiatAmount={fiatAmount} /> - {(isOnRampEnabled || isSwapEnabled || isBridgeEnabled) && ( + {(isOnRampEnabled || isSwapEnabled || isBridgeEnabled || isAddFundsEnabled) && ( { - track({ - userJourney: UserJourney.WALLET, - screen: 'WalletBalances', - control: 'AddTokens', - controlType: 'Button', - extras: { - tokenSymbol: balanceInfo.symbol, - tokenAddress: balanceInfo.address, - }, - }); - orchestrationEvents.sendRequestOnrampEvent( - eventTarget, - IMTBLWidgetEvents.IMTBL_WALLET_WIDGET_EVENT, - { - tokenAddress: balanceInfo.address ?? '', - amount: '', - }, - ); - }} + sx={ShowMenuItem(showAddTokenMenuItem)} + onClick={handleAddTokenClick} > {`Add ${balanceInfo.symbol}`} diff --git a/packages/checkout/widgets-lib/src/widgets/wallet/context/WalletContext.test.ts b/packages/checkout/widgets-lib/src/widgets/wallet/context/WalletContext.test.ts index 698f6e8c46..aef78904bb 100644 --- a/packages/checkout/widgets-lib/src/widgets/wallet/context/WalletContext.test.ts +++ b/packages/checkout/widgets-lib/src/widgets/wallet/context/WalletContext.test.ts @@ -96,9 +96,11 @@ describe('WalletContext', () => { }); expect(supportedTopUps).toEqual({ isSwapEnabled: true, + isSwapAvailable: true, isBridgeEnabled: false, isOnRampEnabled: false, - isSwapAvailable: true, + isAddFundsEnabled: true, + isAddFundsAvailable: true, }); }); diff --git a/packages/checkout/widgets-lib/src/widgets/wallet/context/WalletContext.ts b/packages/checkout/widgets-lib/src/widgets/wallet/context/WalletContext.ts index 35bd32ec24..b03422ed70 100644 --- a/packages/checkout/widgets-lib/src/widgets/wallet/context/WalletContext.ts +++ b/packages/checkout/widgets-lib/src/widgets/wallet/context/WalletContext.ts @@ -23,6 +23,8 @@ export interface TopUpFeature { isSwapEnabled?: boolean; isBridgeEnabled?: boolean; isSwapAvailable?: boolean; + isAddFundsEnabled?: boolean; + isAddFundsAvailable?: boolean; } export const initialWalletState: WalletState = { @@ -118,8 +120,12 @@ export const walletReducer: Reducer = ( action.payload.supportedTopUps.isOnRampEnabled ?? true, isBridgeEnabled: action.payload.supportedTopUps.isBridgeEnabled ?? true, + isAddFundsEnabled: + action.payload.supportedTopUps.isAddFundsEnabled ?? true, isSwapAvailable: action.payload.supportedTopUps.isSwapAvailable ?? true, + isAddFundsAvailable: + action.payload.supportedTopUps.isAddFundsAvailable ?? true, }, }; default: diff --git a/packages/checkout/widgets-sample-app/src/components/ui/checkout/checkout.tsx b/packages/checkout/widgets-sample-app/src/components/ui/checkout/checkout.tsx index 6eb2025fac..1cf6c5739b 100644 --- a/packages/checkout/widgets-sample-app/src/components/ui/checkout/checkout.tsx +++ b/packages/checkout/widgets-sample-app/src/components/ui/checkout/checkout.tsx @@ -38,7 +38,6 @@ import { useAsyncMemo, usePrevState } from "../../../hooks"; import { Message } from "./components/messages"; import { Legend } from "./components/legend"; import { itemsMock } from "./items.mock"; -import { ChainId } from "@imtbl/checkout-sdk"; // const ENVIRONMENT_DEV = "development" as Environment; @@ -155,18 +154,22 @@ function CheckoutUI() { const [checkoutAppURL, setCheckoutAppURL] = useState(""); + const configEnvironment = useMemo(() => { + const params = new URLSearchParams(window.location.search); + return (params.get("environment") as Environment) || Environment.SANDBOX; + }, []); + // setup passport client - const passportClient = useMemo( - () => getPassportClient(environment), - [environment] - ); + const passportClient = useMemo(() => { + return getPassportClient(configEnvironment); + }, []); // handle passport login usePassportLoginCallback(passportClient); // setup checkout sdk const checkoutSdk = useMemo( - () => getCheckoutSdk(passportClient, environment), - [passportClient, environment] + () => getCheckoutSdk(passportClient, configEnvironment), + [passportClient] ); // set a state to keep widget params and configs @@ -237,8 +240,6 @@ function CheckoutUI() { // [checkoutSdk] // ); - - // know connected wallet type const isMetamask = web3Provider?.provider?.isMetaMask; const isPassport = ( @@ -396,9 +397,11 @@ function CheckoutUI() { // unmount when environment changes useEffect(() => { - if (environment !== prevEnvironment) { - console.log("ENV", environment, prevEnvironment); - unmount(); + if (environment !== prevEnvironment && prevEnvironment !== undefined) { + const params = new URLSearchParams(window.location.search); + params.set("environment", environment); + window.location.href = `${window.location.href}?${params.toString()}`; + } }, [environment, prevEnvironment]); diff --git a/packages/checkout/widgets-sample-app/src/components/ui/wallet/wallet.tsx b/packages/checkout/widgets-sample-app/src/components/ui/wallet/wallet.tsx index 6d7e59a54e..2ce9627cf5 100644 --- a/packages/checkout/widgets-sample-app/src/components/ui/wallet/wallet.tsx +++ b/packages/checkout/widgets-sample-app/src/components/ui/wallet/wallet.tsx @@ -1,20 +1,103 @@ -import { useEffect, useMemo } from 'react'; -import { Checkout, WalletEventType, WidgetTheme, WidgetType } from '@imtbl/checkout-sdk'; -import { WidgetsFactory } from '@imtbl/checkout-widgets'; +import { useEffect, useMemo } from "react"; +import { + AddFundsEventType, + Checkout, + OnRampEventType, + OrchestrationEventType, + WalletEventType, + WalletProviderName, + WidgetTheme, + WidgetType, +} from "@imtbl/checkout-sdk"; +import { WidgetsFactory } from "@imtbl/checkout-widgets"; +import { Environment } from "@imtbl/config"; function WalletUI() { - const checkout = useMemo(() => new Checkout(), []) - const wallet = useMemo(() => new WidgetsFactory(checkout, {}).create(WidgetType.WALLET), [checkout]) + const checkout = useMemo( + () => + new Checkout({ + baseConfig: { environment: Environment.SANDBOX }, + }), + [] + ); + const factory = useMemo(() => new WidgetsFactory(checkout, {}), [checkout]); + const wallet = useMemo(() => factory.create(WidgetType.WALLET), [factory]); + const addFunds = useMemo( + () => factory.create(WidgetType.ADD_FUNDS), + [factory] + ); + const onRamp = useMemo(() => factory.create(WidgetType.ONRAMP), [factory]); + + // Use this to connect to a wallet and skip connect loader + // useEffect(() => { + // if (!checkout) return; + + // (async () => { + // const { provider } = await checkout.createProvider({ + // walletProviderName: WalletProviderName.METAMASK, + // }); + + // await checkout.connect({ provider, requestWalletPermissions: false }); - const unmount = () => {wallet.unmount()} - const mount = () => {wallet.mount('wallet')} - const update = (theme: WidgetTheme) => {wallet.update({config: {theme}})} - const updateLanguage = (language: any) => {wallet.update({config: {language}})} + // const { isConnected } = await checkout.checkIsWalletConnected({ + // provider, + // }); + + // if (isConnected) { + // factory.updateProvider(provider); + // } + // })(); + // }, [checkout]); + + const unmount = () => { + wallet.unmount(); + }; + const mount = () => { + wallet.mount("wallet"); + }; + const update = (theme: WidgetTheme) => { + wallet.update({ config: { theme } }); + addFunds.update({ config: { theme } }); + onRamp.update({ config: { theme } }); + }; + const updateLanguage = (language: any) => { + wallet.update({ config: { language } }); + addFunds.update({ config: { language } }); + onRamp.update({ config: { language } }); + }; useEffect(() => { - mount() - wallet.addListener(WalletEventType.NETWORK_SWITCH, (data) => {console.log('NETWORK_SWITCH', data)}) - wallet.addListener(WalletEventType.CLOSE_WIDGET, () => {unmount()}) + mount(); + wallet.addListener(WalletEventType.NETWORK_SWITCH, (data) => { + console.log("NETWORK_SWITCH", data); + }); + wallet.addListener(WalletEventType.CLOSE_WIDGET, () => { + unmount(); + }); + wallet.addListener(OrchestrationEventType.REQUEST_ADD_FUNDS, (data) => { + unmount(); + addFunds.mount("wallet", { ...data, showBackButton: true }); + }); + wallet.addListener(OrchestrationEventType.REQUEST_ONRAMP, (data) => { + unmount(); + onRamp.mount("wallet", { ...data, showBackButton: true }); + }); + + addFunds.addListener(OrchestrationEventType.REQUEST_GO_BACK, () => { + addFunds.unmount(); + mount(); + }); + addFunds.addListener(AddFundsEventType.CLOSE_WIDGET, () => { + addFunds.unmount(); + }); + + onRamp.addListener(OrchestrationEventType.REQUEST_GO_BACK, () => { + onRamp.unmount(); + mount(); + }); + onRamp.addListener(OnRampEventType.CLOSE_WIDGET, () => { + onRamp.unmount(); + }); }, []); return ( @@ -25,10 +108,10 @@ function WalletUI() { - - - - + + + + ); }