diff --git a/.github/workflows/nextjs_bundle_analysis.yml b/.github/workflows/nextjs_bundle_analysis.yml index 1840f8d380..2e9672b0df 100644 --- a/.github/workflows/nextjs_bundle_analysis.yml +++ b/.github/workflows/nextjs_bundle_analysis.yml @@ -28,6 +28,8 @@ jobs: with: secrets: ${{ toJSON(secrets) }} + # Here's the first place where next-bundle-analysis' own script is used + # This step pulls the raw bundle stats for the current bundle - name: Analyze bundle run: npx -p nextjs-bundle-analysis report @@ -45,6 +47,19 @@ jobs: branch: ${{ github.event.pull_request.base.ref }} path: .next/analyze/base + # And here's the second place - this runs after we have both the current and + # base branch bundle stats, and will compare them to determine what changed. + # There are two configurable arguments that come from package.json: + # + # - budget: optional, set a budget (bytes) against which size changes are measured + # it's set to 350kb here by default, as informed by the following piece: + # https://infrequently.org/2021/03/the-performance-inequality-gap/ + # + # - red-status-percentage: sets the percent size increase where you get a red + # status indicator, defaults to 20% + # + # Either of these arguments can be changed or removed by editing the `nextBundleAnalysis` + # entry in your package.json file. - name: Compare with base branch bundle if: success() && github.event.number run: ls -laR .next/analyze/base && npx -p nextjs-bundle-analysis compare @@ -58,8 +73,26 @@ jobs: echo "$(cat .next/analyze/__bundle_analysis_comment.txt)" >> $GITHUB_OUTPUT echo EOF >> $GITHUB_OUTPUT - - name: Comment - uses: marocchino/sticky-pull-request-comment@v2 + - name: Find Comment + uses: peter-evans/find-comment@v2 + if: success() && github.event.number + id: fc + with: + issue-number: ${{ github.event.number }} + body-includes: '' + + - name: Create Comment + uses: peter-evans/create-or-update-comment@v2 + if: success() && github.event.number && steps.fc.outputs.comment-id == 0 + with: + issue-number: ${{ github.event.number }} + body: ${{ steps.get-comment-body.outputs.body }} + + - name: Update Comment + uses: peter-evans/create-or-update-comment@v2 + if: success() && github.event.number && steps.fc.outputs.comment-id != 0 with: - header: next-bundle-analysis - message: ${{ steps.get-comment-body.outputs.body }} + issue-number: ${{ github.event.number }} + body: ${{ steps.get-comment-body.outputs.body }} + comment-id: ${{ steps.fc.outputs.comment-id }} + edit-mode: replace diff --git a/.gitignore b/.gitignore index 505387fd77..97916164f6 100644 --- a/.gitignore +++ b/.gitignore @@ -52,5 +52,4 @@ yalc.lock /public/worker-*.js /public/workbox-*.js /public/workbox-*.js.map -/public/fallback* -/public/*.js.LICENSE.txt \ No newline at end of file +/public/fallback* \ No newline at end of file diff --git a/src/components/common/ConnectWallet/WalletDetails.tsx b/src/components/common/ConnectWallet/WalletDetails.tsx index 6a2ad4caf7..5f0442f50a 100644 --- a/src/components/common/ConnectWallet/WalletDetails.tsx +++ b/src/components/common/ConnectWallet/WalletDetails.tsx @@ -1,13 +1,8 @@ -import { Box, Divider, Skeleton, SvgIcon, Typography } from '@mui/material' -import dynamic from 'next/dynamic' +import { Box, Divider, SvgIcon, Typography } from '@mui/material' import type { ReactElement } from 'react' import LockIcon from '@/public/images/common/lock.svg' - -const SocialSigner = dynamic(() => import('@/components/common/SocialSigner'), { - loading: () => , -}) - +import SocialSigner from '@/components/common/SocialSigner' import WalletLogin from '@/components/welcome/WelcomeLogin/WalletLogin' const WalletDetails = ({ onConnect }: { onConnect: () => void }): ReactElement => { diff --git a/src/components/common/SocialSigner/index.tsx b/src/components/common/SocialSigner/index.tsx index 0282008027..dbc62649bb 100644 --- a/src/components/common/SocialSigner/index.tsx +++ b/src/components/common/SocialSigner/index.tsx @@ -1,7 +1,4 @@ -import useSocialWallet from '@/hooks/wallets/mpc/useSocialWallet' -import { type ISocialWalletService } from '@/services/mpc/interfaces' import { Box, Button, LinearProgress, SvgIcon, Tooltip, Typography } from '@mui/material' -import { COREKIT_STATUS } from '@web3auth/mpc-core-kit' import { useCallback, useContext, useMemo, useState } from 'react' import { PasswordRecovery } from '@/components/common/SocialSigner/PasswordRecovery' import GoogleLogo from '@/public/images/welcome/logo-google.svg' @@ -18,6 +15,8 @@ import { isSocialLoginWallet } from '@/services/mpc/SocialLoginModule' import { CGW_NAMES } from '@/hooks/wallets/consts' import { type ChainInfo } from '@safe-global/safe-gateway-typescript-sdk' import { TxModalContext } from '@/components/tx-flow' +import { COREKIT_STATUS } from '@web3auth/mpc-core-kit' +import useSocialWallet from '@/hooks/wallets/mpc/useSocialWallet' import madProps from '@/utils/mad-props' import { asError } from '@/services/exceptions/utils' import ErrorMessage from '@/components/tx/ErrorMessage' @@ -42,7 +41,7 @@ const useIsSocialWalletEnabled = () => { } type SocialSignerLoginProps = { - socialWalletService: ISocialWalletService | undefined + socialWalletService: ReturnType wallet: ReturnType supportedChains: ReturnType isMPCLoginEnabled: ReturnType diff --git a/src/components/welcome/WelcomeLogin/index.tsx b/src/components/welcome/WelcomeLogin/index.tsx index 9fcc869f44..ad768bf84a 100644 --- a/src/components/welcome/WelcomeLogin/index.tsx +++ b/src/components/welcome/WelcomeLogin/index.tsx @@ -1,9 +1,9 @@ +import SocialSigner from '@/components/common/SocialSigner' import { AppRoutes } from '@/config/routes' import { useHasFeature } from '@/hooks/useChains' import { FEATURES } from '@/utils/chains' -import { Paper, SvgIcon, Typography, Divider, Link, Box, Skeleton } from '@mui/material' +import { Paper, SvgIcon, Typography, Divider, Link, Box } from '@mui/material' import SafeLogo from '@/public/images/logo-text.svg' -import dynamic from 'next/dynamic' import css from './styles.module.css' import { useRouter } from 'next/router' import WalletLogin from './WalletLogin' @@ -11,10 +11,6 @@ import { LOAD_SAFE_EVENTS, CREATE_SAFE_EVENTS } from '@/services/analytics/event import Track from '@/components/common/Track' import { trackEvent } from '@/services/analytics' -const SocialSigner = dynamic(() => import('@/components/common/SocialSigner'), { - loading: () => , -}) - const WelcomeLogin = () => { const router = useRouter() const isSocialLoginEnabled = useHasFeature(FEATURES.SOCIAL_LOGIN) diff --git a/src/hooks/wallets/mpc/__tests__/useMPC.test.ts b/src/hooks/wallets/mpc/__tests__/useMPC.test.ts index 7c839a0cb8..2cdfd3f689 100644 --- a/src/hooks/wallets/mpc/__tests__/useMPC.test.ts +++ b/src/hooks/wallets/mpc/__tests__/useMPC.test.ts @@ -1,7 +1,8 @@ import * as useOnboard from '@/hooks/wallets/useOnboard' import * as socialWalletOptions from '@/services/mpc/config' -import { waitFor } from '@/tests/test-utils' -import { _getMPCCoreKitInstance, initMPC, setMPCCoreKitInstance } from '../useMPC' +import { renderHook, waitFor } from '@/tests/test-utils' +import { _getMPCCoreKitInstance, setMPCCoreKitInstance, useInitMPC } from '../useMPC' +import * as useChains from '@/hooks/useChains' import { type ChainInfo, RPC_AUTHENTICATION } from '@safe-global/safe-gateway-typescript-sdk' import { hexZeroPad } from 'ethers/lib/utils' import { ONBOARD_MPC_MODULE_LABEL } from '@/services/mpc/SocialLoginModule' @@ -62,55 +63,53 @@ class EventEmittingMockProvider { } } -describe('initMPC', () => { - const mockOnboard = { - state: { - get: () => ({ - wallets: [], - walletModules: [], - }), - }, - } as unknown as OnboardAPI - - const mockChain = { - chainId: '5', - chainName: 'Goerli', - blockExplorerUriTemplate: { - address: 'https://goerli.someprovider.io/{address}', - txHash: 'https://goerli.someprovider.io/{txHash}', - api: 'https://goerli.someprovider.io/', - }, - nativeCurrency: { - decimals: 18, - logoUri: 'https://logo.goerli.com', - name: 'Goerli ETH', - symbol: 'ETH', - }, - rpcUri: { - authentication: RPC_AUTHENTICATION.NO_AUTHENTICATION, - value: 'https://goerli.somerpc.io', - }, - } as unknown as ChainInfo - +describe('useInitMPC', () => { beforeEach(() => { jest.resetAllMocks() jest.spyOn(socialWalletOptions, 'isSocialWalletOptions').mockReturnValue(true) }) - it('should set the coreKit if user is not logged in yet', async () => { - jest.spyOn(useOnboard, 'connectWallet').mockImplementation(() => Promise.resolve(undefined)) + const connectWalletSpy = jest.fn().mockImplementation(() => Promise.resolve()) + jest.spyOn(useOnboard, 'connectWallet').mockImplementation(connectWalletSpy) jest.spyOn(useOnboard, 'getConnectedWallet').mockReturnValue(null) + jest.spyOn(useOnboard, 'default').mockReturnValue({ + state: { + get: () => ({ + wallets: [], + walletModules: [], + }), + }, + } as unknown as OnboardAPI) + jest.spyOn(useChains, 'useCurrentChain').mockReturnValue({ + chainId: '5', + chainName: 'Goerli', + blockExplorerUriTemplate: { + address: 'https://goerli.someprovider.io/{address}', + txHash: 'https://goerli.someprovider.io/{txHash}', + api: 'https://goerli.someprovider.io/', + }, + nativeCurrency: { + decimals: 18, + logoUri: 'https://logo.goerli.com', + name: 'Goerli ETH', + symbol: 'ETH', + }, + rpcUri: { + authentication: RPC_AUTHENTICATION.NO_AUTHENTICATION, + value: 'https://goerli.somerpc.io', + }, + } as unknown as ChainInfo) const mockWeb3AuthMpcCoreKit = jest.spyOn(require('@web3auth/mpc-core-kit'), 'Web3AuthMPCCoreKit') mockWeb3AuthMpcCoreKit.mockImplementation(() => { return new MockMPCCoreKit(COREKIT_STATUS.INITIALIZED, null) }) - await initMPC(mockChain, mockOnboard) + renderHook(() => useInitMPC()) await waitFor(() => { expect(_getMPCCoreKitInstance()).toBeDefined() - expect(useOnboard.connectWallet).not.toBeCalled() + expect(connectWalletSpy).not.toBeCalled() }) }) @@ -118,6 +117,33 @@ describe('initMPC', () => { const connectWalletSpy = jest.fn().mockImplementation(() => Promise.resolve()) jest.spyOn(useOnboard, 'connectWallet').mockImplementation(connectWalletSpy) jest.spyOn(useOnboard, 'getConnectedWallet').mockReturnValue(null) + jest.spyOn(useOnboard, 'default').mockReturnValue({ + state: { + get: () => ({ + wallets: [], + walletModules: [], + }), + }, + } as unknown as OnboardAPI) + jest.spyOn(useChains, 'useCurrentChain').mockReturnValue({ + chainId: '5', + chainName: 'Goerli', + blockExplorerUriTemplate: { + address: 'https://goerli.someprovider.io/{address}', + txHash: 'https://goerli.someprovider.io/{txHash}', + api: 'https://goerli.someprovider.io/', + }, + nativeCurrency: { + decimals: 18, + logoUri: 'https://logo.goerli.com', + name: 'Goerli ETH', + symbol: 'ETH', + }, + rpcUri: { + authentication: RPC_AUTHENTICATION.NO_AUTHENTICATION, + value: 'https://goerli.somerpc.io', + }, + } as unknown as ChainInfo) const mockWeb3AuthMpcCoreKit = jest.spyOn(require('@web3auth/mpc-core-kit'), 'Web3AuthMPCCoreKit') const mockProvider = jest.fn() @@ -125,7 +151,7 @@ describe('initMPC', () => { return new MockMPCCoreKit(COREKIT_STATUS.LOGGED_IN, mockProvider as unknown as MPCProvider) }) - await initMPC(mockChain, mockOnboard) + renderHook(() => useInitMPC()) await waitFor(() => { expect(connectWalletSpy).toBeCalled() @@ -134,13 +160,41 @@ describe('initMPC', () => { }) it('should copy event handlers and emit chainChanged if the current chain is updated', async () => { - jest.spyOn(useOnboard, 'connectWallet').mockImplementation(() => Promise.resolve(undefined)) + const connectWalletSpy = jest.fn().mockImplementation(() => Promise.resolve()) + jest.spyOn(useOnboard, 'connectWallet').mockImplementation(connectWalletSpy) jest.spyOn(useOnboard, 'getConnectedWallet').mockReturnValue({ address: hexZeroPad('0x1', 20), label: ONBOARD_MPC_MODULE_LABEL, chainId: '1', provider: {} as unknown as EIP1193Provider, }) + jest.spyOn(useOnboard, 'default').mockReturnValue({ + state: { + get: () => ({ + wallets: [], + walletModules: [], + }), + }, + } as unknown as OnboardAPI) + jest.spyOn(useChains, 'useCurrentChain').mockReturnValue({ + chainId: '5', + chainName: 'Goerli', + blockExplorerUriTemplate: { + address: 'https://goerli.someprovider.io/{address}', + txHash: 'https://goerli.someprovider.io/{txHash}', + api: 'https://goerli.someprovider.io/', + }, + nativeCurrency: { + decimals: 18, + logoUri: 'https://logo.goerli.com', + name: 'Goerli ETH', + symbol: 'ETH', + }, + rpcUri: { + authentication: RPC_AUTHENTICATION.NO_AUTHENTICATION, + value: 'https://goerli.somerpc.io', + }, + } as unknown as ChainInfo) const mockWeb3AuthMpcCoreKit = jest.spyOn(require('@web3auth/mpc-core-kit'), 'Web3AuthMPCCoreKit') const mockChainChangedListener = jest.fn() @@ -161,12 +215,12 @@ describe('initMPC', () => { return new MockMPCCoreKit(COREKIT_STATUS.LOGGED_IN, mockProvider as unknown as MPCProvider) }) - await initMPC(mockChain, mockOnboard) + renderHook(() => useInitMPC()) await waitFor(() => { expect(mockChainChangedListener).toHaveBeenCalledWith('0x5') expect(_getMPCCoreKitInstance()).toBeDefined() - expect(useOnboard.connectWallet).not.toBeCalled() + expect(connectWalletSpy).not.toBeCalled() }) }) }) diff --git a/src/hooks/wallets/mpc/useMPC.ts b/src/hooks/wallets/mpc/useMPC.ts index c73a8e2fee..b0b9d8544a 100644 --- a/src/hooks/wallets/mpc/useMPC.ts +++ b/src/hooks/wallets/mpc/useMPC.ts @@ -1,83 +1,88 @@ -import { IS_PRODUCTION } from '@/config/constants' -import { connectWallet, getConnectedWallet } from '@/hooks/wallets/useOnboard' +import { useEffect } from 'react' import ExternalStore from '@/services/ExternalStore' -import { SOCIAL_WALLET_OPTIONS } from '@/services/mpc/config' -import { ONBOARD_MPC_MODULE_LABEL } from '@/services/mpc/SocialLoginModule' -import { type ChainInfo } from '@safe-global/safe-gateway-typescript-sdk' -import { type OnboardAPI } from '@web3-onboard/core' +import { COREKIT_STATUS, Web3AuthMPCCoreKit, WEB3AUTH_NETWORK } from '@web3auth/mpc-core-kit' import { CHAIN_NAMESPACES } from '@web3auth/base' -import type { Web3AuthMPCCoreKit } from '@web3auth/mpc-core-kit' -import { COREKIT_STATUS } from '@web3auth/mpc-core-kit' + +import { useCurrentChain } from '@/hooks/useChains' import { getRpcServiceUrl } from '../web3' +import useOnboard, { connectWallet, getConnectedWallet } from '@/hooks/wallets/useOnboard' +import { useInitSocialWallet } from './useSocialWallet' +import { ONBOARD_MPC_MODULE_LABEL } from '@/services/mpc/SocialLoginModule' +import { isSocialWalletOptions, SOCIAL_WALLET_OPTIONS } from '@/services/mpc/config' +import { IS_PRODUCTION } from '@/config/constants' const { getStore, setStore, useStore } = new ExternalStore() -export const initMPC = async (chain: ChainInfo, onboard: OnboardAPI) => { - const chainConfig = { - chainId: `0x${Number(chain.chainId).toString(16)}`, - chainNamespace: CHAIN_NAMESPACES.EIP155, - rpcTarget: getRpcServiceUrl(chain.rpcUri), - displayName: chain.chainName, - blockExplorer: new URL(chain.blockExplorerUriTemplate.address).origin, - ticker: chain.nativeCurrency.symbol, - tickerName: chain.nativeCurrency.name, - } +export const useInitMPC = () => { + const chain = useCurrentChain() + const onboard = useOnboard() + useInitSocialWallet() - const currentInstance = getStore() - let previousChainChangedListeners: Function[] = [] - if (currentInstance?.provider) { - // We are already connected. We copy onboards event listener for the chainChanged event to propagate a potentially new chainId - const oldProvider = currentInstance.provider - previousChainChangedListeners = oldProvider.listeners('chainChanged') - } + useEffect(() => { + if (!chain || !onboard || !isSocialWalletOptions(SOCIAL_WALLET_OPTIONS)) { + return + } - const { Web3AuthMPCCoreKit, WEB3AUTH_NETWORK } = await import('@web3auth/mpc-core-kit') + const chainConfig = { + chainId: `0x${Number(chain.chainId).toString(16)}`, + chainNamespace: CHAIN_NAMESPACES.EIP155, + rpcTarget: getRpcServiceUrl(chain.rpcUri), + displayName: chain.chainName, + blockExplorer: new URL(chain.blockExplorerUriTemplate.address).origin, + ticker: chain.nativeCurrency.symbol, + tickerName: chain.nativeCurrency.name, + } - const web3AuthCoreKit = new Web3AuthMPCCoreKit({ - web3AuthClientId: SOCIAL_WALLET_OPTIONS.web3AuthClientId, - // Available networks are "sapphire_devnet", "sapphire_mainnet" - web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, - baseUrl: `${window.location.origin}/`, - uxMode: 'popup', - enableLogging: !IS_PRODUCTION, - //@ts-ignore - chainConfig, - manualSync: true, - hashedFactorNonce: 'safe-global-sfa-nonce', - }) + const currentInstance = getStore() + let previousChainChangedListeners: Function[] = [] + if (currentInstance?.provider) { + // We are already connected. We copy onboards event listener for the chainChanged event to propagate a potentially new chainId + const oldProvider = currentInstance.provider + previousChainChangedListeners = oldProvider.listeners('chainChanged') + } - return web3AuthCoreKit - .init() - .then(() => { - setStore(web3AuthCoreKit) - // If rehydration was successful, connect to onboard - if (web3AuthCoreKit.status !== COREKIT_STATUS.LOGGED_IN || !web3AuthCoreKit.provider) { - return web3AuthCoreKit - } - - const connectedWallet = getConnectedWallet(onboard.state.get().wallets) - if (!connectedWallet) { - connectWallet(onboard, { - autoSelect: { - label: ONBOARD_MPC_MODULE_LABEL, - disableModals: true, - }, - }).catch((reason) => console.error('Error connecting to MPC module:', reason)) - } else { - const newProvider = web3AuthCoreKit.provider + const web3AuthCoreKit = new Web3AuthMPCCoreKit({ + web3AuthClientId: SOCIAL_WALLET_OPTIONS.web3AuthClientId, + // Available networks are "sapphire_devnet", "sapphire_mainnet" + web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, + baseUrl: `${window.location.origin}/`, + uxMode: 'popup', + enableLogging: !IS_PRODUCTION, + chainConfig, + manualSync: true, + hashedFactorNonce: 'safe-global-sfa-nonce', + }) - // To propagate the changedChain we disconnect and connect - if (previousChainChangedListeners.length > 0 && newProvider) { - previousChainChangedListeners.forEach((previousListener) => - newProvider.addListener('chainChanged', (...args: []) => previousListener(...args)), - ) - newProvider.emit('chainChanged', `0x${Number(chainConfig.chainId).toString(16)}`) + web3AuthCoreKit + .init() + .then(() => { + setStore(web3AuthCoreKit) + // If rehydration was successful, connect to onboard + if (web3AuthCoreKit.status !== COREKIT_STATUS.LOGGED_IN || !web3AuthCoreKit.provider) { + return } - } + const connectedWallet = getConnectedWallet(onboard.state.get().wallets) + if (!connectedWallet) { + connectWallet(onboard, { + autoSelect: { + label: ONBOARD_MPC_MODULE_LABEL, + disableModals: true, + }, + }).catch((reason) => console.error('Error connecting to MPC module:', reason)) + } else { + const newProvider = web3AuthCoreKit.provider - return web3AuthCoreKit - }) - .catch((error) => console.error(error)) + // To propagate the changedChain we disconnect and connect + if (previousChainChangedListeners.length > 0 && newProvider) { + previousChainChangedListeners.forEach((previousListener) => + newProvider.addListener('chainChanged', (...args: []) => previousListener(...args)), + ) + newProvider.emit('chainChanged', `0x${Number(chainConfig.chainId).toString(16)}`) + } + } + }) + .catch((error) => console.error(error)) + }, [chain, onboard]) } export const _getMPCCoreKitInstance = getStore diff --git a/src/hooks/wallets/mpc/useRehydrateSocialWallet.ts b/src/hooks/wallets/mpc/useRehydrateSocialWallet.ts deleted file mode 100644 index 799d7f2036..0000000000 --- a/src/hooks/wallets/mpc/useRehydrateSocialWallet.ts +++ /dev/null @@ -1,68 +0,0 @@ -import useAddressBook from '@/hooks/useAddressBook' -import useChainId from '@/hooks/useChainId' -import { useCurrentChain } from '@/hooks/useChains' -import useOnboard, { connectWallet } from '@/hooks/wallets/useOnboard' -import { ONBOARD_MPC_MODULE_LABEL } from '@/services/mpc/SocialLoginModule' -import { useAppDispatch } from '@/store' -import { upsertAddressBookEntry } from '@/store/addressBookSlice' -import { type WalletState } from '@web3-onboard/core' -import { type UserInfo } from '@web3auth/mpc-core-kit' -import { useCallback, useEffect } from 'react' -import { checksumAddress } from '@/utils/addresses' - -const useRehydrateSocialWallet = () => { - const chain = useCurrentChain() - const onboard = useOnboard() - const currentChainId = useChainId() - const addressBook = useAddressBook() - const dispatch = useAppDispatch() - - const updateAddressBook = useCallback( - (userInfo: UserInfo | undefined, wallets: WalletState[] | undefined | void) => { - if (!userInfo || !wallets || !currentChainId || wallets.length === 0) return - - const address = wallets[0].accounts[0]?.address - if (address) { - const signerAddress = checksumAddress(address) - if (addressBook[signerAddress] === undefined) { - const email = userInfo.email - dispatch(upsertAddressBookEntry({ address: signerAddress, chainId: currentChainId, name: email })) - } - } - }, - [addressBook, currentChainId, dispatch], - ) - - useEffect(() => { - if (!chain || !onboard) return - - const rehydrate = async () => { - const { initMPC } = await import('./useMPC') - const { initSocialWallet } = await import('./useSocialWallet') - const mpcCoreKit = await initMPC(chain, onboard) - - if (!mpcCoreKit) return - - const socialWalletService = await initSocialWallet(mpcCoreKit) - - const onConnect = async () => { - const wallets = await connectWallet(onboard, { - autoSelect: { - label: ONBOARD_MPC_MODULE_LABEL, - disableModals: true, - }, - }).catch((reason) => console.error('Error connecting to MPC module:', reason)) - - // If the signer is not in the address book => add the user's email as name - const userInfo = socialWalletService?.getUserInfo() - updateAddressBook(userInfo, wallets) - } - - socialWalletService.setOnConnect(onConnect) - } - - void rehydrate() - }, [chain, onboard, updateAddressBook]) -} - -export default useRehydrateSocialWallet diff --git a/src/hooks/wallets/mpc/useSocialWallet.ts b/src/hooks/wallets/mpc/useSocialWallet.ts index 8db21735d6..d98998ffdd 100644 --- a/src/hooks/wallets/mpc/useSocialWallet.ts +++ b/src/hooks/wallets/mpc/useSocialWallet.ts @@ -1,15 +1,59 @@ +import useAddressBook from '@/hooks/useAddressBook' +import useChainId from '@/hooks/useChainId' import ExternalStore from '@/services/ExternalStore' import type { ISocialWalletService } from '@/services/mpc/interfaces' -import { type Web3AuthMPCCoreKit } from '@web3auth/mpc-core-kit' +import { ONBOARD_MPC_MODULE_LABEL } from '@/services/mpc/SocialLoginModule' +import SocialWalletService from '@/services/mpc/SocialWalletService' +import { useAppDispatch } from '@/store' +import { upsertAddressBookEntry } from '@/store/addressBookSlice' +import { checksumAddress } from '@/utils/addresses' +import { useCallback, useEffect } from 'react' +import useOnboard, { connectWallet } from '../useOnboard' +import useMpc from './useMPC' const { getStore, setStore, useStore } = new ExternalStore() -export const initSocialWallet = async (mpcCoreKit: Web3AuthMPCCoreKit) => { - const SocialWalletService = (await import('@/services/mpc/SocialWalletService')).default - const socialWalletService = new SocialWalletService(mpcCoreKit) - setStore(socialWalletService) +export const useInitSocialWallet = () => { + const mpcCoreKit = useMpc() + const onboard = useOnboard() + const addressBook = useAddressBook() + const currentChainId = useChainId() + const dispatch = useAppDispatch() + const socialWalletService = useStore() - return socialWalletService + const onConnect = useCallback(async () => { + if (!onboard || !socialWalletService) return + + const wallets = await connectWallet(onboard, { + autoSelect: { + label: ONBOARD_MPC_MODULE_LABEL, + disableModals: true, + }, + }).catch((reason) => console.error('Error connecting to MPC module:', reason)) + + // If the signer is not in the address book => add the user's email as name + const userInfo = socialWalletService.getUserInfo() + if (userInfo && wallets && currentChainId && wallets.length > 0) { + const address = wallets[0].accounts[0]?.address + if (address) { + const signerAddress = checksumAddress(address) + if (addressBook[signerAddress] === undefined) { + const email = userInfo.email + dispatch(upsertAddressBookEntry({ address: signerAddress, chainId: currentChainId, name: email })) + } + } + } + }, [addressBook, currentChainId, dispatch, onboard, socialWalletService]) + + useEffect(() => { + socialWalletService?.setOnConnect(onConnect) + }, [onConnect, socialWalletService]) + + useEffect(() => { + if (mpcCoreKit) { + setStore(new SocialWalletService(mpcCoreKit)) + } + }, [mpcCoreKit]) } export const getSocialWalletService = getStore diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 04fb7abb8f..b4dc4d94c5 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,4 +1,3 @@ -import useRehydrateSocialWallet from '@/hooks/wallets/mpc/useRehydrateSocialWallet' import PasswordRecoveryModal from '@/services/mpc/PasswordRecoveryModal' import Sentry from '@/services/sentry' // needs to be imported first import type { ReactNode } from 'react' @@ -39,6 +38,7 @@ import useSafeMessageNotifications from '@/hooks/messages/useSafeMessageNotifica import useSafeMessagePendingStatuses from '@/hooks/messages/useSafeMessagePendingStatuses' import useChangedValue from '@/hooks/useChangedValue' import { TxModalProvider } from '@/components/tx-flow' +import { useInitMPC } from '@/hooks/wallets/mpc/useMPC' import { WalletConnectProvider } from '@/services/walletconnect/WalletConnectContext' import useABTesting from '@/services/tracking/useAbTesting' import { AbTest } from '@/services/tracking/abTesting' @@ -64,7 +64,7 @@ const InitApp = (): null => { useTxTracking() useSafeMsgTracking() useBeamer() - useRehydrateSocialWallet() + useInitMPC() useABTesting(AbTest.HUMAN_DESCRIPTION) return null diff --git a/src/services/mpc/SocialLoginModule.ts b/src/services/mpc/SocialLoginModule.ts index 84052dfbed..6e34ce89d8 100644 --- a/src/services/mpc/SocialLoginModule.ts +++ b/src/services/mpc/SocialLoginModule.ts @@ -1,9 +1,14 @@ +import { _getMPCCoreKitInstance } from '@/hooks/wallets/mpc/useMPC' +import { getSocialWalletService } from '@/hooks/wallets/mpc/useSocialWallet' import { getWeb3ReadOnly } from '@/hooks/wallets/web3' +import * as PasswordRecoveryModal from '@/services/mpc/PasswordRecoveryModal' import { FEATURES, hasFeature } from '@/utils/chains' import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk' import { type WalletInit, ProviderRpcError } from '@web3-onboard/common' import { type EIP1193Provider } from '@web3-onboard/core' -import { type Web3AuthMPCCoreKit } from '@web3auth/mpc-core-kit' +import { COREKIT_STATUS } from '@web3auth/mpc-core-kit' + +const getMPCProvider = () => _getMPCCoreKitInstance()?.provider const assertDefined = (mpcProvider: T | undefined) => { if (!mpcProvider) { @@ -18,9 +23,9 @@ export const isSocialLoginWallet = (walletLabel: string | undefined) => { return walletLabel === ONBOARD_MPC_MODULE_LABEL } -const getConnectedAccounts = async (provider: typeof Web3AuthMPCCoreKit.prototype.provider | undefined) => { +const getConnectedAccounts = async () => { try { - const web3 = assertDefined(provider) + const web3 = assertDefined(getMPCProvider()) return web3.request({ method: 'eth_accounts' }) } catch (e) { throw new ProviderRpcError({ @@ -45,13 +50,6 @@ function MpcModule(chain: ChainInfo): WalletInit { label: ONBOARD_MPC_MODULE_LABEL, getIcon: async () => (await import('./icon')).default, getInterface: async () => { - const { _getMPCCoreKitInstance } = await import('@/hooks/wallets/mpc/useMPC') - const { getSocialWalletService } = await import('@/hooks/wallets/mpc/useSocialWallet') - const { COREKIT_STATUS } = await import('@web3auth/mpc-core-kit') - const { open } = await import('./PasswordRecoveryModal') - - const getMPCProvider = () => _getMPCCoreKitInstance()?.provider - const provider: EIP1193Provider = { on: (event, listener) => { const web3 = assertDefined(getMPCProvider()) @@ -87,11 +85,11 @@ function MpcModule(chain: ChainInfo): WalletInit { const status = await socialWalletService.loginAndCreate() if (status === COREKIT_STATUS.REQUIRED_SHARE) { - open(() => { - getConnectedAccounts(getMPCProvider()).then(resolve).catch(reject) + PasswordRecoveryModal.open(() => { + getConnectedAccounts().then(resolve).catch(reject) }) } else { - getConnectedAccounts(getMPCProvider()).then(resolve).catch(reject) + getConnectedAccounts().then(resolve).catch(reject) } } return