From 00240b4bacac459f411d7ede4d94ee560b2734f7 Mon Sep 17 00:00:00 2001 From: Sharon Sheah <37326128+sharonsheah@users.noreply.github.com> Date: Wed, 28 Feb 2024 11:52:56 +1100 Subject: [PATCH 1/2] feat: WT-2156 Bridge Widget Wallet Images for WC (#1538) --- .../components/Transactions/ChangeWallet.tsx | 54 ++++++++++++++----- .../Transactions/ChangeWalletStyles.ts | 19 +++++++ .../src/lib/hooks/useWalletConnect.ts | 3 ++ .../widgets-lib/src/lib/walletConnect.ts | 50 +++++++++++++++-- .../bridge/components/BridgeReviewSummary.tsx | 52 ++++++++++++++++-- .../components/BridgeReviewSummaryStyles.ts | 16 ++++++ .../components/WalletAndNetworkSelector.tsx | 2 + .../bridge/components/WalletNetworkButton.tsx | 43 ++++++++++++++- .../components/WalletNetworkButtonStyles.ts | 28 ++++++++-- 9 files changed, 242 insertions(+), 25 deletions(-) diff --git a/packages/checkout/widgets-lib/src/components/Transactions/ChangeWallet.tsx b/packages/checkout/widgets-lib/src/components/Transactions/ChangeWallet.tsx index c0df6e7ea5..8c1341b886 100644 --- a/packages/checkout/widgets-lib/src/components/Transactions/ChangeWallet.tsx +++ b/packages/checkout/widgets-lib/src/components/Transactions/ChangeWallet.tsx @@ -1,9 +1,9 @@ import { - Box, Button, EllipsizedText, Logo, + Box, Button, EllipsizedText, FramedImage, Logo, } from '@biom3/react'; -import { useContext } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { BridgeContext } from 'widgets/bridge/context/BridgeContext'; -import { getWalletProviderNameByProvider } from 'lib/providerUtils'; +import { getWalletProviderNameByProvider, isWalletConnectProvider } from 'lib/providerUtils'; import { UserJourney, useAnalytics, @@ -11,7 +11,10 @@ import { import { BridgeWidgetViews } from 'context/view-context/BridgeViewContextTypes'; import { useTranslation } from 'react-i18next'; import { getWalletLogoByName } from 'lib/logoUtils'; -import { headingStyles } from './ChangeWalletStyles'; +import { useWalletConnect } from 'lib/hooks/useWalletConnect'; +import { + headingStyles, wcStickerLogoStyles, wcWalletLogoStyles, wcWalletLogoWrapperStyles, +} from './ChangeWalletStyles'; export interface ChangeWalletProps { onChangeWalletClick: () => void; @@ -20,8 +23,15 @@ export interface ChangeWalletProps { export function ChangeWallet({ onChangeWalletClick }: ChangeWalletProps) { const { t } = useTranslation(); const { - bridgeState: { from }, + bridgeState: { checkout, from }, } = useContext(BridgeContext); + const [walletLogoUrl, setWalletLogoUrl] = useState( + undefined, + ); + const [isWalletConnect, setIsWalletConnect] = useState(false); + const { isWalletConnectEnabled, getWalletLogoUrl } = useWalletConnect({ + checkout, + }); const { track } = useAnalytics(); const walletAddress = from?.walletAddress || ''; @@ -39,18 +49,38 @@ export function ChangeWallet({ onChangeWalletClick }: ChangeWalletProps) { onChangeWalletClick(); }; + useEffect(() => { + if (isWalletConnectEnabled) { + setIsWalletConnect(isWalletConnectProvider(from?.web3Provider)); + (async () => { + setWalletLogoUrl(await getWalletLogoUrl()); + })(); + } + }, [isWalletConnectEnabled, from]); + return ( - + {isWalletConnect && walletLogoUrl ? ( + + + + + ) : ( + + )} { }) ), [ethereumProvider, walletConnectModal]); + const getWalletLogoUrl = useCallback(async () => await WalletConnectManager.getInstance().getWalletLogoUrl(), []); + return { isWalletConnectEnabled, ethereumProvider, walletConnectBusy, walletConnectModal, openWalletConnectModal, + getWalletLogoUrl, }; }; diff --git a/packages/checkout/widgets-lib/src/lib/walletConnect.ts b/packages/checkout/widgets-lib/src/lib/walletConnect.ts index d0acb67970..aea4b8a5c9 100644 --- a/packages/checkout/widgets-lib/src/lib/walletConnect.ts +++ b/packages/checkout/widgets-lib/src/lib/walletConnect.ts @@ -25,6 +25,8 @@ const darkThemeVariables = { '--wcm-container-border-radius': '8px', // eslint-disable-next-line @typescript-eslint/naming-convention '--wcm-wallet-icon-border-radius': '8px', + // eslint-disable-next-line @typescript-eslint/naming-convention + '--wcm-overlay-background-color': 'rgba(255, 255, 255, 0.1)', }; const lightThemeVariables = { @@ -36,8 +38,17 @@ const lightThemeVariables = { '--wcm-container-border-radius': '8px', // eslint-disable-next-line @typescript-eslint/naming-convention '--wcm-wallet-icon-border-radius': '8px', + // eslint-disable-next-line @typescript-eslint/naming-convention + '--wcm-overlay-background-color': 'rgba(255, 255, 255, 0.1)', }; +// Whitelisted wallet ids on WalletConnect explorer API +const metamaskId = 'c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96'; +const frontierId = '85db431492aa2e8672e93f4ea7acf10c88b97b867b0d373107af63dc4880f041'; +const coinbaseId = 'fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa'; +// const phantomId = 'a797aa35c0fadbfc1a53e7f675162ed5226968b44a19ee3d24385c64d1d3c393'; +// const rainbowId = '1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369'; + export class WalletConnectManager { private static instance: WalletConnectManager; @@ -53,6 +64,8 @@ export class WalletConnectManager { private ethereumProvider!: EthereumProvider; + private walletListings!: any; + private validateConfig(config: WalletConnectConfiguration): boolean { if (!config.projectId || config.projectId === '') { // eslint-disable-next-line no-console @@ -100,10 +113,9 @@ export class WalletConnectManager { projectId: this.walletConnectConfig.projectId, chains: this.environment === Environment.PRODUCTION ? productionModalChains : testnetModalChains, explorerRecommendedWalletIds: [ - 'c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96', // MetaMask - '85db431492aa2e8672e93f4ea7acf10c88b97b867b0d373107af63dc4880f041', // Frontier - // 'a797aa35c0fadbfc1a53e7f675162ed5226968b44a19ee3d24385c64d1d3c393', // Phantom - // '1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369', // Rainbow + metamaskId, + frontierId, + coinbaseId, ], explorerExcludedWalletIds: 'ALL', themeMode: this.theme, @@ -149,4 +161,34 @@ export class WalletConnectManager { } }); } + + private async loadWalletListings(): Promise { + // eslint-disable-next-line max-len + const walletListingsApi = `https://explorer-api.walletconnect.com/v3/wallets?projectId=${this.walletConnectConfig.projectId}&ids=${metamaskId},${frontierId},${coinbaseId}`; + try { + const response = await fetch(walletListingsApi); + const data = await response.json(); + return data; + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error fetching wallet listings', error); + } + return undefined; + } + + public async getWalletLogoUrl(): Promise { + if (!this.walletListings) { + this.walletListings = await this.loadWalletListings(); + } + const walletName = this.ethereumProvider?.session?.peer.metadata.name; + + if (!this.walletListings || !walletName) { + return undefined; + } + + const matchedWallet = Object.values(this.walletListings.listings) + .find((wallet: any) => walletName.toLowerCase().includes(wallet.slug)) as any; + + return matchedWallet.image_url.md; + } } diff --git a/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeReviewSummary.tsx b/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeReviewSummary.tsx index 64e9c71c8f..dc7f74cbe5 100644 --- a/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeReviewSummary.tsx +++ b/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeReviewSummary.tsx @@ -3,7 +3,7 @@ import { } from 'react'; import { BridgeWidgetViews } from 'context/view-context/BridgeViewContextTypes'; import { - Body, Box, Button, Heading, Icon, MenuItem, + Body, Box, Button, Heading, Icon, Logo, MenuItem, } from '@biom3/react'; import { GasEstimateBridgeToL2Result, @@ -15,6 +15,7 @@ import { getWalletProviderNameByProvider, isMetaMaskProvider, isPassportProvider, + isWalletConnectProvider, } from 'lib/providerUtils'; import { calculateCryptoToFiat } from 'lib/utils'; import { @@ -32,6 +33,7 @@ import { } from 'context/analytics-provider/SegmentAnalyticsProvider'; import { useTranslation } from 'react-i18next'; import { getWalletLogoByName } from 'lib/logoUtils'; +import { useWalletConnect } from 'lib/hooks/useWalletConnect'; import { networkIconStyles } from './WalletNetworkButtonStyles'; import { arrowIconStyles, @@ -41,6 +43,8 @@ import { bridgeReviewHeadingStyles, bridgeReviewWrapperStyles, topMenuItemStyles, + wcStickerLogoStyles, + wcWalletLogoStyles, } from './BridgeReviewSummaryStyles'; import { BridgeContext } from '../context/BridgeContext'; import { @@ -75,6 +79,17 @@ export function BridgeReviewSummary() { const [transaction, setTransaction] = useState( undefined, ); + const [fromWalletLogoUrl, setFromWalletLogoUrl] = useState( + undefined, + ); + const [toWalletLogoUrl, setToWalletLogoUrl] = useState( + undefined, + ); + const [fromWalletIsWalletConnect, setFromWalletIsWalletConnect] = useState(false); + const [toWalletIsWalletConnect, setToWalletIsWalletConnect] = useState(false); + const { isWalletConnectEnabled, getWalletLogoUrl } = useWalletConnect({ + checkout, + }); const displayAmount = useMemo( () => (token?.symbol ? `${token?.symbol} ${amount}` : `${amount}`), @@ -189,6 +204,17 @@ export function BridgeReviewSummary() { })(); }, []); + useEffect(() => { + if (isWalletConnectEnabled) { + setFromWalletIsWalletConnect(isWalletConnectProvider(from?.web3Provider)); + setToWalletIsWalletConnect(isWalletConnectProvider(to?.web3Provider)); + (async () => { + setFromWalletLogoUrl(await getWalletLogoUrl()); + setToWalletLogoUrl(await getWalletLogoUrl()); + })(); + } + }, [isWalletConnectEnabled, from, to]); + const submitBridge = useCallback(async () => { if (!approveTransaction || !transaction) return; @@ -269,7 +295,17 @@ export function BridgeReviewSummary() { emphasized sx={bottomMenuItemStyles} > - {fromWalletProviderName && ( + {fromWalletIsWalletConnect && fromWalletLogoUrl && ( + <> + + + + )} + {fromWalletProviderName && !fromWalletIsWalletConnect && ( - {toWalletProviderName && ( + {toWalletProviderName && toWalletIsWalletConnect && toWalletLogoUrl && ( + <> + + + + )} + {toWalletProviderName && !toWalletIsWalletConnect && ( ( + undefined, + ); + const [isWalletConnect, setIsWalletConnect] = useState(false); + const { + bridgeState: { checkout }, + } = useContext(BridgeContext); + const { isWalletConnectEnabled, getWalletLogoUrl } = useWalletConnect({ + checkout, + }); + + useEffect(() => { + if (isWalletConnectEnabled) { + setIsWalletConnect(isWalletConnectProvider(walletProvider)); + (async () => { + setWalletLogoUrl(await getWalletLogoUrl()); + })(); + } + }, [isWalletConnectEnabled, walletProvider]); return ( - + {isWalletConnect && walletLogoUrl ? ( + <> + + + + ) : ( + + )} ({ width: 'base.icon.size.500', - padding: walletName === WalletProviderName.PASSPORT ? 'base.spacing.x1' : '', + padding: + // eslint-disable-next-line no-constant-condition + walletName === WalletProviderName.PASSPORT || 'walletconnect' + ? 'base.spacing.x1' + : '', backgroundColor: 'base.color.translucent.standard.200', borderRadius: 'base.borderRadius.x2', }); @@ -39,11 +60,10 @@ export const networkButtonStyles = { borderRadius: 'base.borderRadius.x18', }; -export const networkIconStyles = (chainId:ChainId) => ({ +export const networkIconStyles = (chainId: ChainId) => ({ fill: 'base.color.brand.2', width: 'base.icon.size.300', backgroundColor: logoColour[chainId], borderRadius: '100%', padding: 'base.spacing.x1', -} -); +}); From 45ec7fc8923fb581536e537d860710907cfb56a9 Mon Sep 17 00:00:00 2001 From: Zach Couchman Date: Wed, 28 Feb 2024 12:42:32 +1100 Subject: [PATCH 2/2] feat: wt 2178 switch network drawer (#1537) Co-authored-by: Zach Couchman Co-authored-by: Sharon Sheah <37326128+sharonsheah@users.noreply.github.com> --- .../NetworkSwitchDrawer.tsx | 148 ++++++++++++++++++ packages/checkout/widgets-lib/src/factory.ts | 6 +- .../src/lib/hooks/useWalletConnect.ts | 2 +- .../checkout/widgets-lib/src/locales/en.json | 12 +- .../checkout/widgets-lib/src/locales/ja.json | 12 +- .../checkout/widgets-lib/src/locales/ko.json | 12 +- .../checkout/widgets-lib/src/locales/zh.json | 12 +- .../bridge/components/BridgeReviewSummary.tsx | 66 +++++++- .../components/WalletAndNetworkSelector.tsx | 31 +--- .../src/widgets/bridge/views/Bridge.tsx | 3 +- .../src/widgets/swap/SwapWidget.tsx | 4 +- .../widgets/swap/components/SwapButton.tsx | 25 ++- .../src/widgets/swap/components/SwapForm.tsx | 11 ++ .../ui/marketplace-orchestrator/MainPage.tsx | 10 +- 14 files changed, 309 insertions(+), 45 deletions(-) create mode 100644 packages/checkout/widgets-lib/src/components/NetworkSwitchDrawer/NetworkSwitchDrawer.tsx diff --git a/packages/checkout/widgets-lib/src/components/NetworkSwitchDrawer/NetworkSwitchDrawer.tsx b/packages/checkout/widgets-lib/src/components/NetworkSwitchDrawer/NetworkSwitchDrawer.tsx new file mode 100644 index 0000000000..1190b629f0 --- /dev/null +++ b/packages/checkout/widgets-lib/src/components/NetworkSwitchDrawer/NetworkSwitchDrawer.tsx @@ -0,0 +1,148 @@ +import { + Body, Box, Button, CloudImage, Drawer, Heading, Logo, +} from '@biom3/react'; +import { Web3Provider } from '@ethersproject/providers'; +import { ChainId, Checkout } from '@imtbl/checkout-sdk'; +import { Environment } from '@imtbl/config'; +import { FooterLogo } from 'components/Footer/FooterLogo'; +import { getL1ChainId } from 'lib'; +import { getChainNameById } from 'lib/chains'; +import { + isMetaMaskProvider, + isWalletConnectProvider, +} from 'lib/providerUtils'; +import { getRemoteImage } from 'lib/utils'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +export interface NetworkSwitchDrawerProps { + visible: boolean; + targetChainId: ChainId; + provider: Web3Provider; + checkout: Checkout; + onCloseDrawer: () => void; + onNetworkSwitch?: (provider: Web3Provider) => void; +} +export function NetworkSwitchDrawer({ + visible, + targetChainId, + provider, + checkout, + onCloseDrawer, + onNetworkSwitch, +}: NetworkSwitchDrawerProps) { + const { t } = useTranslation(); + + const targetChainName = getChainNameById(targetChainId); + const networkSwitchImage = useMemo(() => { + if (targetChainId === getL1ChainId(checkout.config)) { + const ethNetworkImageUrl = getRemoteImage(Environment.SANDBOX, '/switchnetworkethereum.svg'); + return ; + } + return ; + }, [targetChainId]); + + const handleSwitchNetwork = useCallback(async () => { + if (!checkout) return; + const switchNetworkResult = await checkout.switchNetwork({ + provider, + chainId: targetChainId, + }); + if (onNetworkSwitch) { + onNetworkSwitch(switchNetworkResult.provider); + } + }, [checkout, provider, onNetworkSwitch, targetChainId]); + + const isWalletConnect = isWalletConnectProvider(provider); + + const walletConnectPeerName = useMemo(() => { + if (!isWalletConnect) return ''; + return (provider.provider as any)?.session?.peer?.metadata?.name as string; + }, [provider, isWalletConnect]); + + const isMetaMaskMobileWalletPeer = useMemo( + () => walletConnectPeerName?.toLowerCase().includes('metamask'), + [walletConnectPeerName], + ); + + const walletDisplayName = useMemo(() => { + if (isMetaMaskProvider(provider)) return 'MetaMask wallet'; + if (isWalletConnect && walletConnectPeerName) return walletConnectPeerName; + return 'wallet'; + }, [provider, isWalletConnect, walletConnectPeerName]); + + const requireManualSwitch = isWalletConnect && isMetaMaskMobileWalletPeer; + + return ( + + + + {networkSwitchImage} + + {t('drawers.networkSwitch.heading', { + wallet: walletDisplayName, + })} + + {/** MetaMask mobile requires manual switch */} + {requireManualSwitch && ( + + {t('drawers.networkSwitch.manualSwitch.body', { + chain: targetChainName, + })} + + )} + {!requireManualSwitch && ( + + {t('drawers.networkSwitch.controlledSwitch.body', { + chain: targetChainName, + })} + + )} + + + + + {!requireManualSwitch && ( + + )} + + + + + ); +} diff --git a/packages/checkout/widgets-lib/src/factory.ts b/packages/checkout/widgets-lib/src/factory.ts index 815c434cf0..df662e10ac 100644 --- a/packages/checkout/widgets-lib/src/factory.ts +++ b/packages/checkout/widgets-lib/src/factory.ts @@ -17,6 +17,7 @@ import { WalletConnectManager } from 'lib/walletConnect'; import { sendProviderUpdatedEvent, addProviderListenersForWidgetRoot, + DEFAULT_THEME, } from './lib'; import './i18n'; @@ -28,12 +29,13 @@ export class WidgetsFactory implements IWidgetsFactory { constructor(sdk: Checkout, widgetConfig: WidgetConfiguration) { this.sdk = sdk; this.widgetConfig = widgetConfig; - if (widgetConfig.walletConnect && widgetConfig.theme) { + if (!this.widgetConfig.theme) this.widgetConfig.theme = DEFAULT_THEME; + if (widgetConfig.walletConnect) { try { WalletConnectManager.getInstance().initialise( sdk.config.environment, widgetConfig.walletConnect, - widgetConfig.theme, + this.widgetConfig.theme, ); } catch (err) { // eslint-disable-next-line no-console diff --git a/packages/checkout/widgets-lib/src/lib/hooks/useWalletConnect.ts b/packages/checkout/widgets-lib/src/lib/hooks/useWalletConnect.ts index 3164f3aa44..7f73b09d6e 100644 --- a/packages/checkout/widgets-lib/src/lib/hooks/useWalletConnect.ts +++ b/packages/checkout/widgets-lib/src/lib/hooks/useWalletConnect.ts @@ -71,7 +71,7 @@ export const useWalletConnect = ({ checkout }: UseWalletConnectParams) => { // eslint-disable-next-line no-console console.log('activate succeeded but there is no connected session'); } - // eslint-disable-next-line no-console + // eslint-disable-next-line no-console }).catch((err) => console.log('activate existing pairing error', err)); } } catch (err) { diff --git a/packages/checkout/widgets-lib/src/locales/en.json b/packages/checkout/widgets-lib/src/locales/en.json index b7cb68146c..145737c8ef 100644 --- a/packages/checkout/widgets-lib/src/locales/en.json +++ b/packages/checkout/widgets-lib/src/locales/en.json @@ -632,6 +632,16 @@ "buttons": { "cancel": "Dismiss" } + }, + "networkSwitch": { + "heading": "Switch network in your {{wallet}}", + "manualSwitch": { + "body": "You'll need to open your mobile wallet and switch to the {{chain}} network to proceed" + }, + "controlledSwitch": { + "body": "You'll need to switch to the {{chain}} network to proceed" + }, + "switchButton": "Switch to {{chain}}" } } -} +} \ No newline at end of file diff --git a/packages/checkout/widgets-lib/src/locales/ja.json b/packages/checkout/widgets-lib/src/locales/ja.json index bc634a2e37..c93e92f9ba 100644 --- a/packages/checkout/widgets-lib/src/locales/ja.json +++ b/packages/checkout/widgets-lib/src/locales/ja.json @@ -625,6 +625,16 @@ "buttons": { "cancel": "閉じる" } + }, + "networkSwitch": { + "heading": "あなたの{{wallet}}でネットワークを切り替える", + "manualSwitch": { + "body": "続行するにはモバイルウォレットを開き、{{chain}}ネットワークに切り替える必要があります" + }, + "controlledSwitch": { + "body": "{{chain}}ネットワークに切り替える必要があります" + }, + "switchButton": "{{chain}}に切り替える" } } -} +} \ No newline at end of file diff --git a/packages/checkout/widgets-lib/src/locales/ko.json b/packages/checkout/widgets-lib/src/locales/ko.json index fcaaa85084..7943cd41ae 100644 --- a/packages/checkout/widgets-lib/src/locales/ko.json +++ b/packages/checkout/widgets-lib/src/locales/ko.json @@ -622,6 +622,16 @@ "buttons": { "cancel": "취소" } + }, + "networkSwitch": { + "heading": "귀하의 {{wallet}}에서 네트워크를 전환하세요", + "manualSwitch": { + "body": "계속하려면 모바일 지갑을 열고 {{chain}} 네트워크로 전환해야 합니다" + }, + "controlledSwitch": { + "body": "{{chain}} 네트워크로 전환해야 합니다" + }, + "switchButton": "{{chain}}로 전환하기" } } -} +} \ No newline at end of file diff --git a/packages/checkout/widgets-lib/src/locales/zh.json b/packages/checkout/widgets-lib/src/locales/zh.json index 39cc45fba7..2e4c5b1689 100644 --- a/packages/checkout/widgets-lib/src/locales/zh.json +++ b/packages/checkout/widgets-lib/src/locales/zh.json @@ -622,6 +622,16 @@ "buttons": { "cancel": "关闭" } + }, + "networkSwitch": { + "heading": "在您的{{wallet}}中切换网络", + "manualSwitch": { + "body": "您需要打开您的移动钱包并切换到{{chain}}网络才能继续进行" + }, + "controlledSwitch": { + "body": "您需要切换到{{chain}}网络才能继续进行" + }, + "switchButton": "切换到{{chain}}" } } -} +} \ No newline at end of file diff --git a/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeReviewSummary.tsx b/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeReviewSummary.tsx index dc7f74cbe5..e94e279f2a 100644 --- a/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeReviewSummary.tsx +++ b/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeReviewSummary.tsx @@ -22,7 +22,9 @@ import { DEFAULT_QUOTE_REFRESH_INTERVAL, DEFAULT_TOKEN_DECIMALS, NATIVE, + addChainChangedListener, networkIcon, + removeChainChangedListener, } from 'lib'; import { useInterval } from 'lib/hooks/useInterval'; import { ApproveBridgeResponse, BridgeTxResponse } from '@imtbl/bridge-sdk'; @@ -33,6 +35,8 @@ import { } from 'context/analytics-provider/SegmentAnalyticsProvider'; import { useTranslation } from 'react-i18next'; import { getWalletLogoByName } from 'lib/logoUtils'; +import { NetworkSwitchDrawer } from 'components/NetworkSwitchDrawer/NetworkSwitchDrawer'; +import { Web3Provider } from '@ethersproject/providers'; import { useWalletConnect } from 'lib/hooks/useWalletConnect'; import { networkIconStyles } from './WalletNetworkButtonStyles'; import { @@ -46,7 +50,7 @@ import { wcStickerLogoStyles, wcWalletLogoStyles, } from './BridgeReviewSummaryStyles'; -import { BridgeContext } from '../context/BridgeContext'; +import { BridgeActions, BridgeContext } from '../context/BridgeContext'; import { ViewActions, ViewContext, @@ -64,6 +68,7 @@ export function BridgeReviewSummary() { bridgeState: { checkout, tokenBridge, from, to, token, amount, }, + bridgeDispatch, } = useContext(BridgeContext); const { track } = useAnalytics(); @@ -79,6 +84,8 @@ export function BridgeReviewSummary() { const [transaction, setTransaction] = useState( undefined, ); + const [showSwitchNetworkDrawer, setShowSwitchNetworkDrawer] = useState(false); + const [fromWalletLogoUrl, setFromWalletLogoUrl] = useState( undefined, ); @@ -204,6 +211,40 @@ export function BridgeReviewSummary() { })(); }, []); + const handleNetworkSwitch = useCallback((provider: Web3Provider) => { + bridgeDispatch({ + payload: { + type: BridgeActions.SET_WALLETS_AND_NETWORKS, + from: { + web3Provider: provider, + walletAddress: from?.walletAddress!, + network: from?.network!, + }, + to: { + web3Provider: to?.web3Provider!, + walletAddress: to?.walletAddress!, + network: to?.network!, + }, + }, + }); + }, [from?.web3Provider, from?.network, to?.web3Provider, to?.network]); + + useEffect(() => { + if (!from?.web3Provider) return; + + const handleChainChanged = () => { + const newProvider = new Web3Provider(from?.web3Provider.provider); + handleNetworkSwitch(newProvider); + setShowSwitchNetworkDrawer(false); + }; + addChainChangedListener(from?.web3Provider, handleChainChanged); + + // eslint-disable-next-line consistent-return + return () => { + removeChainChangedListener(from?.web3Provider, handleChainChanged); + }; + }, [from?.web3Provider]); + useEffect(() => { if (isWalletConnectEnabled) { setFromWalletIsWalletConnect(isWalletConnectProvider(from?.web3Provider)); @@ -218,6 +259,19 @@ export function BridgeReviewSummary() { const submitBridge = useCallback(async () => { if (!approveTransaction || !transaction) return; + try { + const currentChainId = await (from?.web3Provider.provider as any).request({ method: 'eth_chainId', params: [] }); + // eslint-disable-next-line radix + const parsedChainId = parseInt(currentChainId.toString()); + if (parsedChainId !== from?.network) { + setShowSwitchNetworkDrawer(true); + return; + } + } catch (err) { + // eslint-disable-next-line no-console + console.error('Current network check failed', err); + } + track({ userJourney: UserJourney.BRIDGE, screen: 'Summary', @@ -254,7 +308,7 @@ export function BridgeReviewSummary() { }, }, }); - }, [viewDispatch, approveTransaction, transaction]); + }, [viewDispatch, approveTransaction, transaction, from?.web3Provider, from?.network]); return ( @@ -417,6 +471,14 @@ export function BridgeReviewSummary() { )} + setShowSwitchNetworkDrawer(false)} + onNetworkSwitch={handleNetworkSwitch} + /> ); } diff --git a/packages/checkout/widgets-lib/src/widgets/bridge/components/WalletAndNetworkSelector.tsx b/packages/checkout/widgets-lib/src/widgets/bridge/components/WalletAndNetworkSelector.tsx index 07edd53400..8a0afc845f 100644 --- a/packages/checkout/widgets-lib/src/widgets/bridge/components/WalletAndNetworkSelector.tsx +++ b/packages/checkout/widgets-lib/src/widgets/bridge/components/WalletAndNetworkSelector.tsx @@ -279,35 +279,10 @@ export function WalletAndNetworkSelector() { if (!fromWalletWeb3Provider) return; clearToWalletSelections(); - - if (isPassportProvider(fromWalletWeb3Provider)) { - setFromNetworkDrawerOpen(false); - setFromNetwork(chainId); - return; - } - - const currentNetwork = await fromWalletWeb3Provider?.getNetwork(); - if (currentNetwork?.chainId === chainId) { - setFromNetworkDrawerOpen(false); - setFromNetwork(chainId); - return; - } - - let switchNetwork; - try { - switchNetwork = await checkout.switchNetwork({ - provider: fromWalletWeb3Provider, - chainId, - }); - setFromWalletWeb3Provider(switchNetwork.provider); - setFromNetworkDrawerOpen(false); - setFromNetwork(switchNetwork.network.chainId); - } catch (err) { - // eslint-disable-next-line no-console - console.error(err); - } + setFromNetworkDrawerOpen(false); + setFromNetwork(chainId); }, - [checkout, fromWalletWeb3Provider, fromWalletProviderName, fromNetwork], + [checkout, fromWalletWeb3Provider], ); const handleSettingToNetwork = useCallback(() => { diff --git a/packages/checkout/widgets-lib/src/widgets/bridge/views/Bridge.tsx b/packages/checkout/widgets-lib/src/widgets/bridge/views/Bridge.tsx index d516f4c722..52855922d7 100644 --- a/packages/checkout/widgets-lib/src/widgets/bridge/views/Bridge.tsx +++ b/packages/checkout/widgets-lib/src/widgets/bridge/views/Bridge.tsx @@ -57,6 +57,7 @@ export function Bridge({ amount, tokenAddress, defaultTokenImage }: BridgeProps) const tokensAndBalances = await getAllowedBalances({ checkout, provider: from.web3Provider, + chainId: from?.network, allowTokenListType: TokenFilterTypes.BRIDGE, // Skip retry given that in this case it is not needed; // refreshBalances will be, automatically, called again @@ -87,7 +88,7 @@ export function Bridge({ amount, tokenAddress, defaultTokenImage }: BridgeProps) // eslint-disable-next-line no-console console.debug(e); } - }, [checkout, from?.web3Provider]); + }, [checkout, from?.web3Provider, from?.network]); useInterval(refreshBalances, REFRESH_TOKENS_INTERVAL_MS); useEffect(() => { diff --git a/packages/checkout/widgets-lib/src/widgets/swap/SwapWidget.tsx b/packages/checkout/widgets-lib/src/widgets/swap/SwapWidget.tsx index c64e3ffc03..ac2f84a8c7 100644 --- a/packages/checkout/widgets-lib/src/widgets/swap/SwapWidget.tsx +++ b/packages/checkout/widgets-lib/src/widgets/swap/SwapWidget.tsx @@ -202,7 +202,9 @@ export default function SwapWidget({ if (!(await loadBalances())) return; - showSwapView(); + if (viewState.view.type === SharedViews.LOADING_VIEW) { + showSwapView(); + } })(); }, [checkout, provider]); diff --git a/packages/checkout/widgets-lib/src/widgets/swap/components/SwapButton.tsx b/packages/checkout/widgets-lib/src/widgets/swap/components/SwapButton.tsx index 7ce9ae15e0..24571c428d 100644 --- a/packages/checkout/widgets-lib/src/widgets/swap/components/SwapButton.tsx +++ b/packages/checkout/widgets-lib/src/widgets/swap/components/SwapButton.tsx @@ -3,6 +3,7 @@ import { useContext, useState } from 'react'; import { TransactionResponse } from '@imtbl/dex-sdk'; import { CheckoutErrorType } from '@imtbl/checkout-sdk'; import { useTranslation } from 'react-i18next'; +import { getL2ChainId } from 'lib'; import { PrefilledSwapForm, SwapWidgetViews } from '../../../context/view-context/SwapViewContextTypes'; import { ViewContext, @@ -26,10 +27,18 @@ export interface SwapButtonProps { data?: SwapFormData; insufficientFundsForGas: boolean; openNotEnoughImxDrawer: () => void; + openNetworkSwitchDrawer: () => void; } export function SwapButton({ - loading, updateLoading, validator, transaction, data, insufficientFundsForGas, openNotEnoughImxDrawer, + loading, + updateLoading, + validator, + transaction, + data, + insufficientFundsForGas, + openNotEnoughImxDrawer, + openNetworkSwitchDrawer, }: SwapButtonProps) { const { t } = useTranslation(); const [showTxnRejectedState, setShowTxnRejectedState] = useState(false); @@ -63,6 +72,20 @@ export function SwapButton({ return; } + try { + // check for switch network here + const currentChainId = await (provider.provider as any).request({ method: 'eth_chainId', params: [] }); + // eslint-disable-next-line radix + const parsedChainId = parseInt(currentChainId.toString()); + if (parsedChainId !== getL2ChainId(checkout.config)) { + openNetworkSwitchDrawer(); + return; + } + } catch (err) { + // eslint-disable-next-line no-console + console.error('Current network check failed', err); + } + if (!transaction) return; try { updateLoading(true); diff --git a/packages/checkout/widgets-lib/src/widgets/swap/components/SwapForm.tsx b/packages/checkout/widgets-lib/src/widgets/swap/components/SwapForm.tsx index 0792ce1015..c2ffeef347 100644 --- a/packages/checkout/widgets-lib/src/widgets/swap/components/SwapForm.tsx +++ b/packages/checkout/widgets-lib/src/widgets/swap/components/SwapForm.tsx @@ -11,6 +11,7 @@ import { TransactionResponse } from '@imtbl/dex-sdk'; import { UserJourney, useAnalytics } from 'context/analytics-provider/SegmentAnalyticsProvider'; import { useTranslation } from 'react-i18next'; import { Environment } from '@imtbl/config'; +import { NetworkSwitchDrawer } from 'components/NetworkSwitchDrawer/NetworkSwitchDrawer'; import { amountInputValidation as textInputValidator } from '../../../lib/validations/amountInputValidations'; import { SwapContext } from '../context/SwapContext'; import { CryptoFiatActions, CryptoFiatContext } from '../../../context/crypto-fiat-context/CryptoFiatContext'; @@ -23,6 +24,7 @@ import { NATIVE, DEFAULT_TOKEN_VALIDATION_DECIMALS, ESTIMATE_DEBOUNCE, + getL2ChainId, } from '../../../lib'; import { quotesProcessor } from '../functions/FetchQuote'; import { SelectInput } from '../../../components/FormComponents/SelectInput/SelectInput'; @@ -152,6 +154,7 @@ export function SwapForm({ data, theme }: SwapFromProps) { // Drawers const [showNotEnoughImxDrawer, setShowNotEnoughImxDrawer] = useState(false); const [showUnableToSwapDrawer, setShowUnableToSwapDrawer] = useState(false); + const [showNetworkSwitchDrawer, setShowNetworkSwitchDrawer] = useState(false); useEffect(() => { if (tokenBalances.length === 0) return; @@ -836,6 +839,7 @@ export function SwapForm({ data, theme }: SwapFromProps) { }} insufficientFundsForGas={insufficientFundsForGas} openNotEnoughImxDrawer={openNotEnoughImxDrawer} + openNetworkSwitchDrawer={() => setShowNetworkSwitchDrawer(true)} /> + setShowNetworkSwitchDrawer(false)} + /> ); } 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 11ca880071..95e9c55399 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.SANDBOX, + environment: Environment.PRODUCTION, publishableKey: 'pk_imapik-test-pCHFU0GpQImZx9UzSnU3', }, passport, @@ -44,8 +44,8 @@ export const MainPage = () => { walletConnect: { projectId: '938b553484e344b1e0b4bb80edf8c362', metadata: { - name: 'Marketplace Orchestrator', - description: '', + name: 'Checkout Marketplace', + description: 'Checkout Marketplace', url: 'http://localhost:3000/marketplace-orchestrator', icons: [] } @@ -156,9 +156,9 @@ export const MainPage = () => { return ( - + Immutable Checkout Marketplace - +