diff --git a/packages/checkout/sdk/src/types/config.ts b/packages/checkout/sdk/src/types/config.ts index d9ece6688c..54f464007e 100644 --- a/packages/checkout/sdk/src/types/config.ts +++ b/packages/checkout/sdk/src/types/config.ts @@ -125,6 +125,13 @@ export type OnRampConfig = { export type ConnectConfig = { /** A boolean value for enabling/disabling WalletConnect */ walletConnect: boolean; + /** A configuration for injected wallets */ + injected: { + /** List for sorting injected wallets via wallet rdns */ + priorityWalletRdns: string[]; + /** List for blocking injected wallets via wallet rdns */ + blacklistWalletRdns: string[]; + } }; /** @@ -236,7 +243,7 @@ export type ChainsTokensConfig = { * @property {boolean | undefined} blockscout - */ export type ChainTokensConfig = { -/** List of allowed tokens for a given chain. */ + /** List of allowed tokens for a given chain. */ allowed?: TokenInfo[]; /** Feature flag to enable/disable blockscout integration. */ blockscout?: boolean; diff --git a/packages/checkout/widgets-lib/src/components/RawImage/RawImage.tsx b/packages/checkout/widgets-lib/src/components/RawImage/RawImage.tsx index 3faf77a7bd..7be964dc3b 100644 --- a/packages/checkout/widgets-lib/src/components/RawImage/RawImage.tsx +++ b/packages/checkout/widgets-lib/src/components/RawImage/RawImage.tsx @@ -1,11 +1,10 @@ -import { Box, useConvertSxToEmotionStyles } from '@biom3/react'; +import { Box, BoxProps, useConvertSxToEmotionStyles } from '@biom3/react'; import { imageStyles, rawImageStyles } from './RawImageStyles'; -export interface RawImageProps { +export type RawImageProps = { src: string; alt: string; - sx?: any; -} +} & BoxProps; export function RawImage({ src, diff --git a/packages/checkout/widgets-lib/src/components/RawImage/RawImageStyles.ts b/packages/checkout/widgets-lib/src/components/RawImage/RawImageStyles.ts index aa5c97eb92..0ba4c8acfb 100644 --- a/packages/checkout/widgets-lib/src/components/RawImage/RawImageStyles.ts +++ b/packages/checkout/widgets-lib/src/components/RawImage/RawImageStyles.ts @@ -16,7 +16,7 @@ export const rawImageStyles = { }; export const imageStyles = { - width: '32px', + width: 'auto', height: '100%', objectFit: 'contain', objectPosition: '50% 50%', diff --git a/packages/checkout/widgets-lib/src/lib/animation/listAnimation.ts b/packages/checkout/widgets-lib/src/lib/animation/listAnimation.ts new file mode 100644 index 0000000000..9bdc04ccb5 --- /dev/null +++ b/packages/checkout/widgets-lib/src/lib/animation/listAnimation.ts @@ -0,0 +1,17 @@ +export const listVariants = { + hidden: { opacity: 0 }, + show: { opacity: 1 }, +}; + +export const listItemVariants = { + hidden: { y: 8, opacity: 0 }, + show: (i) => ({ + y: 0, + opacity: 1, + transition: { + delay: i * 0.1, + duration: 0.2, + ease: 'easeOut', + }, + }), +}; diff --git a/packages/checkout/widgets-lib/src/lib/hooks/useAnimation.ts b/packages/checkout/widgets-lib/src/lib/hooks/useAnimation.ts deleted file mode 100644 index 8c4509abac..0000000000 --- a/packages/checkout/widgets-lib/src/lib/hooks/useAnimation.ts +++ /dev/null @@ -1,24 +0,0 @@ -export const useAnimation = () => { - const listVariants = { - hidden: { opacity: 0 }, - show: { opacity: 1 }, - }; - - const listItemVariants = { - hidden: { y: 8, opacity: 0 }, - show: (i) => ({ - y: 0, - opacity: 1, - transition: { - delay: i * 0.1, - duration: 0.2, - ease: 'easeOut', - }, - }), - }; - - return { - listVariants, - listItemVariants, - }; -}; diff --git a/packages/checkout/widgets-lib/src/lib/hooks/useInjectedProviders.ts b/packages/checkout/widgets-lib/src/lib/hooks/useInjectedProviders.ts index 8f41e47d51..6787df0763 100644 --- a/packages/checkout/widgets-lib/src/lib/hooks/useInjectedProviders.ts +++ b/packages/checkout/widgets-lib/src/lib/hooks/useInjectedProviders.ts @@ -12,40 +12,35 @@ export interface UseInjectedProvidersParams { checkout: Checkout | null; } +type ConnectConfig = { + injected: { + priorityWalletRdns: WalletProviderRdns | string[]; + blacklistWalletRdns: WalletProviderRdns | string[]; + }; +}; + let passportWeb3Provider: Web3Provider; -const processProviders = (checkout: Checkout | null, injectedProviders: EIP6963ProviderDetail[]) => { - // Apply wallet providers allowlist - const allowedWalletRdns = [ - 'com.immutable.passport', // Immutable Passport - 'io.metamask', // MetaMask - // 'xyz.frontier.wallet', // Frontier - // 'me.rainbow', // Rainbow - // 'com.coinbase.wallet', // Coinbase Wallet - ]; - const uniqueRdnsSet = new Set(); +const processProviders = ( + checkout: Checkout | null, + injectedProviders: EIP6963ProviderDetail[], + priorityWalletRdns: WalletProviderRdns | string[] = [], + blacklistWalletRdns: WalletProviderRdns | string[] = [], +) => { + // Filter providers const filteredProviders = injectedProviders - .filter((provider) => { - if (allowedWalletRdns.includes(provider.info.rdns)) { - if (!uniqueRdnsSet.has(provider.info.rdns)) { - uniqueRdnsSet.add(provider.info.rdns); - return true; - } - } - - return false; - }) + .filter((provider) => !blacklistWalletRdns.includes(provider.info.rdns)) .sort((a, b) => { - // Get the index of the rdns for each provider - const indexA = allowedWalletRdns.indexOf(a.info.rdns); - const indexB = allowedWalletRdns.indexOf(b.info.rdns); + let indexA = priorityWalletRdns.indexOf(a.info.rdns); + if (indexA < 0) indexA = 999; + let indexB = priorityWalletRdns.indexOf(b.info.rdns); + if (indexB < 0) indexB = 999; - // Sort based on the index return indexA - indexB; }); // Add passport from checkout config if not from injected providers if (checkout?.passport - && allowedWalletRdns.includes(WalletProviderRdns.PASSPORT) + // && priorityWalletRdns.includes(WalletProviderRdns.PASSPORT) TODO: // Uncomment this when config JSON is updated && !filteredProviders.some((provider) => provider.info.rdns === WalletProviderRdns.PASSPORT)) { if (!passportWeb3Provider) { passportWeb3Provider = new Web3Provider(checkout.passport.connectEvm()); @@ -57,12 +52,21 @@ const processProviders = (checkout: Checkout | null, injectedProviders: EIP6963P }; /** - * Hook that supplies a filters and sorted list of EIP-6963 injected providers. + * Hook that supplies a sorted list of EIP-6963 injected providers. */ export const useInjectedProviders = ({ checkout }: UseInjectedProvidersParams) => { + let defaultPriorityWalletRdns: WalletProviderRdns | string[] = []; + let defaultBlacklistWalletRdns: WalletProviderRdns | string[] = []; + (async () => { + const connectConfig = await checkout?.config.remote.getConfig('connect') as ConnectConfig; + defaultPriorityWalletRdns = connectConfig.injected?.priorityWalletRdns ?? []; + defaultBlacklistWalletRdns = connectConfig.injected?.blacklistWalletRdns ?? []; + })(); const defaultProviders = processProviders( checkout, InjectedProvidersManager.getInstance().getProviders(), + defaultPriorityWalletRdns, + defaultBlacklistWalletRdns, ); const [providers, setProviders] = useState( defaultProviders, @@ -76,7 +80,15 @@ export const useInjectedProviders = ({ checkout }: UseInjectedProvidersParams) = const cancelSubscription = InjectedProvidersManager .getInstance() .subscribe(async (injectedProviders: EIP6963ProviderDetail[]) => { - const filteredProviders = processProviders(checkout, injectedProviders); + const connectConfig = await checkout?.config.remote.getConfig('connect') as ConnectConfig; + const priorityWalletRdns = connectConfig.injected?.priorityWalletRdns ?? []; + const blacklistWalletRdns = connectConfig.injected?.blacklistWalletRdns ?? []; + const filteredProviders = processProviders( + checkout, + injectedProviders, + priorityWalletRdns, + blacklistWalletRdns, + ); setProviders(filteredProviders); }); diff --git a/packages/checkout/widgets-lib/src/lib/walletConnect.ts b/packages/checkout/widgets-lib/src/lib/walletConnect.ts index b31f9075ef..df1a240f94 100644 --- a/packages/checkout/widgets-lib/src/lib/walletConnect.ts +++ b/packages/checkout/widgets-lib/src/lib/walletConnect.ts @@ -138,7 +138,7 @@ export class WalletConnectManager { chains: this.environment === Environment.PRODUCTION ? productionModalChains : testnetModalChains, explorerRecommendedWalletIds: this.environment === Environment.PRODUCTION ? productionWalletWhitelist : sandboxWalletWhitelist, - explorerExcludedWalletIds: 'ALL', + explorerExcludedWalletIds: undefined, // 'ALL', themeMode: this.theme, themeVariables: this.theme === WidgetTheme.DARK ? darkThemeVariables : lightThemeVariables, }); diff --git a/packages/checkout/widgets-lib/src/locales/en.json b/packages/checkout/widgets-lib/src/locales/en.json index 45497f6c39..91791c5bde 100644 --- a/packages/checkout/widgets-lib/src/locales/en.json +++ b/packages/checkout/widgets-lib/src/locales/en.json @@ -4,6 +4,9 @@ "body": { "heading": "Connect a wallet", "content": "You'll need to connect or create a digital wallet to buy, sell, trade and store your coins and collectibles." + }, + "walletSelection": { + "heading": "Choose a wallet" } }, "CONNECT_SUCCESS": { @@ -543,7 +546,11 @@ }, "walletconnect": { "heading": "Other wallets", - "description": "Allows you to connect with any blockchain wallet including MetaMask" + "description": "Allows you to connect with any blockchain mobile wallet including MetaMask" + }, + "browserwallet": { + "heading": "Browser wallets", + "description": "Digital wallet for accessing blockchain applications and web3" } }, "drawers": { diff --git a/packages/checkout/widgets-lib/src/locales/ja.json b/packages/checkout/widgets-lib/src/locales/ja.json index 2922bdb486..41661301f1 100644 --- a/packages/checkout/widgets-lib/src/locales/ja.json +++ b/packages/checkout/widgets-lib/src/locales/ja.json @@ -4,6 +4,9 @@ "body": { "heading": "ウォレットを接続する", "content": "購入、販売、取引、およびコインとコレクティブルを保存するには、デジタルウォレットを接続または作成する必要があります。" + }, + "walletSelection": { + "heading": "ウォレットを選択" } }, "CONNECT_SUCCESS": { @@ -551,6 +554,10 @@ "walletconnect": { "heading": "その他のウォレット", "description": "MetaMaskを含む任意のブロックチェーンウォレットに接続できます" + }, + "browserwallet": { + "heading": "ブラウザウォレット", + "description": "ブロックチェーンアプリケーションおよびweb3にアクセスするためのデジタルウォレット" } }, "drawers": { diff --git a/packages/checkout/widgets-lib/src/locales/ko.json b/packages/checkout/widgets-lib/src/locales/ko.json index 589ea8fd5a..a3a3f97867 100644 --- a/packages/checkout/widgets-lib/src/locales/ko.json +++ b/packages/checkout/widgets-lib/src/locales/ko.json @@ -4,6 +4,9 @@ "body": { "heading": "지갑 연결", "content": "구매, 판매, 거래 및 코인과 수집품을 저장하려면 디지털 지갑을 연결하거나 만들어야 합니다." + }, + "walletSelection": { + "heading": "지갑 선택" } }, "CONNECT_SUCCESS": { @@ -544,6 +547,10 @@ "walletconnect": { "heading": "기타 지갑", "description": "MetaMask를 포함한 모든 블록체인 지갑에 연결할 수 있습니다" + }, + "browserwallet": { + "heading": "브라우저 지갑", + "description": "블록체인 애플리케이션 및 web3에 접근하기 위한 디지털 지갑" } }, "drawers": { diff --git a/packages/checkout/widgets-lib/src/locales/zh.json b/packages/checkout/widgets-lib/src/locales/zh.json index 19889e47ba..e283be0991 100644 --- a/packages/checkout/widgets-lib/src/locales/zh.json +++ b/packages/checkout/widgets-lib/src/locales/zh.json @@ -4,6 +4,9 @@ "body": { "heading": "连接钱包", "content": "您需要连接或创建数字钱包以购买、出售、交易和存储您的硬币和收藏品。" + }, + "walletSelection": { + "heading": "选择一个钱包" } }, "CONNECT_SUCCESS": { @@ -544,6 +547,10 @@ "walletconnect": { "heading": "其他钱包", "description": "允许您连接到包括MetaMask在内的任何区块链钱包" + }, + "browserwallet": { + "heading": "浏览器钱包", + "description": "用于访问区块链应用和web3的数字钱包" } }, "drawers": { diff --git a/packages/checkout/widgets-lib/src/widgets/bridge/components/WalletDrawer.tsx b/packages/checkout/widgets-lib/src/widgets/bridge/components/WalletDrawer.tsx index 15084b6513..dd191b8d80 100644 --- a/packages/checkout/widgets-lib/src/widgets/bridge/components/WalletDrawer.tsx +++ b/packages/checkout/widgets-lib/src/widgets/bridge/components/WalletDrawer.tsx @@ -5,10 +5,10 @@ import { motion } from 'framer-motion'; import { WalletItem } from './WalletItem'; import { walletItemListStyles } from './WalletDrawerStyles'; import { WalletConnectItem } from './WalletConnectItem'; -import { useAnimation } from '../../../lib/hooks/useAnimation'; import { useWalletConnect } from '../../../lib/hooks/useWalletConnect'; import { WalletChangeEvent } from './WalletDrawerEvents'; import { EIP1193Provider, EIP6963ProviderDetail, walletConnectProviderInfo } from '../../../lib/provider'; +import { listItemVariants, listVariants } from '../../../lib/animation/listAnimation'; interface WalletDrawerProps { testId: string; @@ -16,6 +16,7 @@ interface WalletDrawerProps { heading: string; defaultText?: string; }, + showWalletConnect?: boolean; showWalletSelectorTarget: boolean; walletOptions: EIP6963ProviderDetail[]; showDrawer: boolean; @@ -26,6 +27,7 @@ export function WalletDrawer({ testId, drawerText, walletOptions, + showWalletConnect = true, showWalletSelectorTarget, showDrawer, setShowDrawer, @@ -33,7 +35,6 @@ export function WalletDrawer({ }: WalletDrawerProps) { const { isWalletConnectEnabled, openWalletConnectModal } = useWalletConnect(); const [walletItemLoading, setWalletItemLoading] = useState(false); - const { listVariants, listItemVariants } = useAnimation(); const { heading, defaultText } = drawerText; const handleWalletItemClick = async (providerDetail: EIP6963ProviderDetail) => { @@ -124,7 +125,7 @@ export function WalletDrawer({ )} /> ))} - {isWalletConnectEnabled && ( + {isWalletConnectEnabled && showWalletConnect && ( void; + providers?: EIP6963ProviderDetail[]; +}; + +export function BrowserWalletItem({ + onClick, + providers, +}: BrowserWalletItemProps) { + const { t } = useTranslation(); + + return ( + onClick()} + sx={{ marginBottom: 'base.spacing.x1' }} + > + {((providers || []).length > 0) && ( + + + ')`, + }} + /> + + )} + {(!providers || providers.length === 0) && ( + + )} + + {t('wallets.browserwallet.heading')} + + + + {t('wallets.browserwallet.description')} + + + ); +} diff --git a/packages/checkout/widgets-lib/src/widgets/connect/components/WalletConnectItem.tsx b/packages/checkout/widgets-lib/src/widgets/connect/components/WalletConnectItem.tsx new file mode 100644 index 0000000000..29c5ac75bf --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/connect/components/WalletConnectItem.tsx @@ -0,0 +1,37 @@ +import { useTranslation } from 'react-i18next'; +import { MenuItem } from '@biom3/react'; +import { useWalletConnect } from '../../../lib/hooks/useWalletConnect'; + +export type WalletConnectItemProps = { + onConnect: () => void; +}; + +export function WalletConnectItem({ + onConnect, +}: WalletConnectItemProps) { + const { t } = useTranslation(); + const { walletConnectBusy } = useWalletConnect(); + + return ( + onConnect()} + sx={{ marginBottom: 'base.spacing.x1' }} + > + + + {t('wallets.walletconnect.heading')} + + + + {t('wallets.walletconnect.description')} + + + ); +} diff --git a/packages/checkout/widgets-lib/src/widgets/connect/components/WalletList.tsx b/packages/checkout/widgets-lib/src/widgets/connect/components/WalletList.tsx index d745881de6..282b7c97fc 100644 --- a/packages/checkout/widgets-lib/src/widgets/connect/components/WalletList.tsx +++ b/packages/checkout/widgets-lib/src/widgets/connect/components/WalletList.tsx @@ -1,10 +1,10 @@ -import { Box, MenuItem } from '@biom3/react'; +import { Box } from '@biom3/react'; import { ChainId, WalletProviderName, WalletProviderRdns, } from '@imtbl/checkout-sdk'; import { - useCallback, useContext, useMemo, + useCallback, useContext, useMemo, useState, } from 'react'; import { motion } from 'framer-motion'; import { useTranslation } from 'react-i18next'; @@ -23,7 +23,6 @@ import { } from '../../../context/analytics-provider/SegmentAnalyticsProvider'; import { useWalletConnect } from '../../../lib/hooks/useWalletConnect'; import { useInjectedProviders } from '../../../lib/hooks/useInjectedProviders'; -import { useAnimation } from '../../../lib/hooks/useAnimation'; import { walletListStyle } from './WalletListStyles'; import { EIP1193Provider, @@ -31,6 +30,11 @@ import { getProviderSlugFromRdns, } from '../../../lib/provider'; import { getL1ChainId } from '../../../lib'; +import { listItemVariants, listVariants } from '../../../lib/animation/listAnimation'; +import { WalletDrawer } from '../../bridge/components/WalletDrawer'; +import { WalletChangeEvent } from '../../bridge/components/WalletDrawerEvents'; +import { WalletConnectItem } from './WalletConnectItem'; +import { BrowserWalletItem } from './BrowserWalletItem'; export interface WalletListProps { targetChainId: ChainId; @@ -45,13 +49,20 @@ export function WalletList(props: WalletListProps) { } = useContext(ConnectContext); const { viewDispatch } = useContext(ViewContext); const { track } = useAnalytics(); - const { listVariants, listItemVariants } = useAnimation(); const { providers } = useInjectedProviders({ checkout }); + const [showWalletDrawer, setShowWalletDrawer] = useState(false); + const { isWalletConnectEnabled, openWalletConnectModal } = useWalletConnect(); + const filteredProviders = useMemo(() => ( - providers.filter((provider) => ( - !(provider.info.rdns === WalletProviderRdns.PASSPORT && targetChainId === getL1ChainId(checkout!.config)))) + providers.filter((provider) => (!(provider.info.rdns === WalletProviderRdns.PASSPORT))) ), [providers]); + // Don't allow Passport if targetChainId is L1 + const passportProviderDetail = useMemo(() => ( + targetChainId !== getL1ChainId(checkout!.config) + && providers.find((provider) => provider.info.rdns === WalletProviderRdns.PASSPORT) + ), [providers, checkout]); + const selectWeb3Provider = useCallback((web3Provider: any, providerName: string) => { connectDispatch({ payload: { @@ -67,7 +78,31 @@ export function WalletList(props: WalletListProps) { }); }, []); - const { isWalletConnectEnabled, walletConnectBusy, openWalletConnectModal } = useWalletConnect(); + const selectProviderDetail = (providerDetail: EIP6963ProviderDetail) => { + try { + selectWeb3Provider( + new Web3Provider(providerDetail.provider as any), + getProviderSlugFromRdns(providerDetail.info.rdns), + ); + + viewDispatch({ + payload: { + type: ViewActions.UPDATE_VIEW, + view: { type: ConnectWidgetViews.READY_TO_CONNECT }, + }, + }); + } catch (err: any) { + // eslint-disable-next-line no-console + console.error(err); + + viewDispatch({ + payload: { + type: ViewActions.UPDATE_VIEW, + view: { type: SharedViews.ERROR_VIEW, error: err }, + }, + }); + } + }; const connectCallback = async (ethereumProvider) => { if (ethereumProvider.connected && ethereumProvider.session) { @@ -101,6 +136,24 @@ export function WalletList(props: WalletListProps) { }); }; + const handleWalletChange = async (event: WalletChangeEvent) => { + setShowWalletDrawer(false); + + const { providerDetail } = event; + track({ + userJourney: UserJourney.CONNECT, + screen: 'ConnectWallet', + control: providerDetail.info.name, + controlType: 'MenuItem', + extras: { + wallet: getProviderSlugFromRdns(providerDetail.info.rdns), + walletRdns: providerDetail.info.rdns, + walletUuid: providerDetail.info.uuid, + }, + }); + selectProviderDetail(providerDetail); + }; + const onWalletClick = useCallback( async (providerDetail: EIP6963ProviderDetail) => { track({ @@ -114,35 +167,15 @@ export function WalletList(props: WalletListProps) { walletUuid: providerDetail.info.uuid, }, }); - if (checkout) { - try { - selectWeb3Provider( - new Web3Provider(providerDetail.provider as any), - getProviderSlugFromRdns(providerDetail.info.rdns), - ); - - viewDispatch({ - payload: { - type: ViewActions.UPDATE_VIEW, - view: { type: ConnectWidgetViews.READY_TO_CONNECT }, - }, - }); - } catch (err: any) { - // eslint-disable-next-line no-console - console.error(err); - - viewDispatch({ - payload: { - type: ViewActions.UPDATE_VIEW, - view: { type: SharedViews.ERROR_VIEW, error: err }, - }, - }); - } - } + selectProviderDetail(providerDetail); }, - [track], + [track, checkout], ); + const onBrowserWalletsClick = useCallback(() => { + setShowWalletDrawer(true); + }, [track]); + return ( - {filteredProviders.map((providerDetail, index) => ( + {passportProviderDetail && ( + + )} + /> + )} + {filteredProviders.length === 1 && ( + )} /> - ))} + )} + {filteredProviders.length > 1 && ( + + + + )} {isWalletConnectEnabled && ( 0 ? 1 : 0)} key="walletconnect" > - handleWalletConnectConnection()} - sx={{ marginBottom: 'base.spacing.x1' }} - > - - - {t('wallets.walletconnect.heading')} - - - - {t('wallets.walletconnect.description')} - - + )} + + { + setShowWalletDrawer(show); + }} + onWalletChange={handleWalletChange} + /> ); } diff --git a/packages/checkout/widgets-sample-app/src/components/ui/marketplace-orchestrator/MainPage.tsx b/packages/checkout/widgets-sample-app/src/components/ui/marketplace-orchestrator/MainPage.tsx index 7fa9bfeeae..f992978ea4 100644 --- a/packages/checkout/widgets-sample-app/src/components/ui/marketplace-orchestrator/MainPage.tsx +++ b/packages/checkout/widgets-sample-app/src/components/ui/marketplace-orchestrator/MainPage.tsx @@ -24,7 +24,7 @@ import { LanguageSelector } from './LanguageSelector'; // Create one instance of Checkout and inject Passport const checkout = new Checkout({ baseConfig: { - environment: Environment.PRODUCTION, + environment: Environment.SANDBOX, publishableKey: 'pk_imapik-test-pCHFU0GpQImZx9UzSnU3', }, passport,