diff --git a/cypress/e2e/add_owner.cy.js b/cypress/e2e/add_owner.cy.js index 3bc9275b1f..d63a568e5f 100644 --- a/cypress/e2e/add_owner.cy.js +++ b/cypress/e2e/add_owner.cy.js @@ -4,8 +4,6 @@ const offset = 7 describe('Adding an owner', () => { before(() => { - cy.connectE2EWallet() - cy.visit(`/${TEST_SAFE}/settings/setup`) cy.contains('button', 'Accept selection').click() diff --git a/cypress/e2e/create_safe.cy.js b/cypress/e2e/create_safe.cy.js index 04ea76eefc..3906b002d2 100644 --- a/cypress/e2e/create_safe.cy.js +++ b/cypress/e2e/create_safe.cy.js @@ -1,7 +1,5 @@ describe('Create Safe', () => { it('should create a new Safe', () => { - cy.connectE2EWallet() - cy.visit('/welcome') // Close cookie banner diff --git a/cypress/e2e/non_owner_spending_limit.cy.js b/cypress/e2e/non_owner_spending_limit.cy.js index da18f7f3c9..581a9f426b 100644 --- a/cypress/e2e/non_owner_spending_limit.cy.js +++ b/cypress/e2e/non_owner_spending_limit.cy.js @@ -3,8 +3,6 @@ const SPENDING_LIMIT_SAFE = 'gor:0xBE3C5aFF7f66c23fe71c3047911f9Aa0026b281B' describe('Check non-owner spending limit beneficiary modal', () => { before(() => { - cy.connectE2EWallet() - cy.visit(`/${SPENDING_LIMIT_SAFE}/home`, { failOnStatusCode: false }) cy.contains('Accept selection').click() diff --git a/cypress/e2e/smoke/create_safe_simple.cy.js b/cypress/e2e/smoke/create_safe_simple.cy.js index 30b2540be7..1cd65e41f8 100644 --- a/cypress/e2e/smoke/create_safe_simple.cy.js +++ b/cypress/e2e/smoke/create_safe_simple.cy.js @@ -3,8 +3,6 @@ const OWNER_ADDRESS = '0xE297437d6b53890cbf004e401F3acc67c8b39665' describe('Create Safe form', () => { it('should navigate to the form', () => { - cy.connectE2EWallet() - cy.visit('/welcome') // Close cookie banner diff --git a/cypress/e2e/smoke/create_tx.cy.js b/cypress/e2e/smoke/create_tx.cy.js index 74b13e352d..0d09229447 100644 --- a/cypress/e2e/smoke/create_tx.cy.js +++ b/cypress/e2e/smoke/create_tx.cy.js @@ -6,7 +6,6 @@ const currentNonce = 3 describe('Queue a transaction on 1/N', () => { before(() => { - cy.connectE2EWallet() cy.useProdCGW() cy.visit(`/home?safe=${SAFE}`) diff --git a/cypress/e2e/smoke/nfts.cy.js b/cypress/e2e/smoke/nfts.cy.js index 3a5d8e9d46..a1613d9376 100644 --- a/cypress/e2e/smoke/nfts.cy.js +++ b/cypress/e2e/smoke/nfts.cy.js @@ -2,8 +2,6 @@ const TEST_SAFE = 'gor:0x97d314157727D517A706B5D08507A1f9B44AaaE9' describe('Assets > NFTs', () => { before(() => { - cy.connectE2EWallet() - cy.visit(`/balances/nfts?safe=${TEST_SAFE}`) cy.contains('button', 'Accept selection').click() cy.contains(/E2E Wallet @ G(รถ|oe)rli/) diff --git a/cypress/e2e/smoke/pending_actions.cy.js b/cypress/e2e/smoke/pending_actions.cy.js index e381212f6d..080996a301 100644 --- a/cypress/e2e/smoke/pending_actions.cy.js +++ b/cypress/e2e/smoke/pending_actions.cy.js @@ -2,9 +2,7 @@ const SAFE = 'gor:0xCD4FddB8FfA90012DFE11eD4bf258861204FeEAE' describe('Pending actions', () => { before(() => { - cy.connectE2EWallet() cy.useProdCGW() - cy.visit(`/welcome`) cy.contains('button', 'Accept selection').click() }) diff --git a/cypress/e2e/spending_limit.cy.js b/cypress/e2e/spending_limit.cy.js index 9def385933..403d1b93ce 100644 --- a/cypress/e2e/spending_limit.cy.js +++ b/cypress/e2e/spending_limit.cy.js @@ -3,8 +3,6 @@ const SPENDING_LIMIT_SAFE = 'gor:0x28F95E682D1dd632b54Dc61740575f49DB39Eb7F' describe('Check spending limit modal', () => { before(() => { - cy.connectE2EWallet() - cy.visit(`/${SPENDING_LIMIT_SAFE}/home`, { failOnStatusCode: false }) cy.contains('Accept selection').click() diff --git a/cypress/e2e/tx_modal.cy.js b/cypress/e2e/tx_modal.cy.js index 1d50ca9f6a..89b92256d4 100644 --- a/cypress/e2e/tx_modal.cy.js +++ b/cypress/e2e/tx_modal.cy.js @@ -5,8 +5,6 @@ const SAFE_NONCE = '6' describe('Tx Modal', () => { before(() => { - cy.connectE2EWallet() - // Open the Safe used for testing cy.visit(`/${TEST_SAFE}`) cy.contains('a', 'Accept selection').click() diff --git a/cypress/e2e/tx_simulation.cy.js b/cypress/e2e/tx_simulation.cy.js index aeaed28cb7..885fc17b33 100644 --- a/cypress/e2e/tx_simulation.cy.js +++ b/cypress/e2e/tx_simulation.cy.js @@ -3,8 +3,6 @@ const RECIPIENT_ADDRESS = '0x6a5602335a878ADDCa4BF63a050E34946B56B5bC' describe('Tx Simulation', () => { before(() => { - cy.connectE2EWallet() - // Open the Safe used for testing cy.visit(`/${TEST_SAFE}/home`, { failOnStatusCode: false }) cy.contains('button', 'Accept selection').click() diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 75eae8fba5..2f9a1336ed 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -1,10 +1,3 @@ -Cypress.Commands.add('connectE2EWallet', () => { - cy.on('window:before:load', (window) => { - // Does not work unless `JSON.stringify` is used - window.localStorage.setItem('SAFE_v2__lastWallet', JSON.stringify('E2E Wallet')) - }) -}) - Cypress.Commands.add('useProdCGW', () => { cy.on('window:before:load', (window) => { window.localStorage.setItem('SAFE_v2__debugProdCgw', JSON.stringify(true)) diff --git a/src/components/batch/BatchSidebar/index.tsx b/src/components/batch/BatchSidebar/index.tsx index 19aaf48363..350a4c4dbb 100644 --- a/src/components/batch/BatchSidebar/index.tsx +++ b/src/components/batch/BatchSidebar/index.tsx @@ -75,7 +75,12 @@ const BatchSidebar = ({ isOpen, onToggle }: { isOpen: boolean; onToggle: (open: - diff --git a/src/components/batch/BatchSidebar/styles.module.css b/src/components/batch/BatchSidebar/styles.module.css index 1f8ba735f9..a762c5aa37 100644 --- a/src/components/batch/BatchSidebar/styles.module.css +++ b/src/components/batch/BatchSidebar/styles.module.css @@ -26,7 +26,7 @@ } .txs ul { - padding: 0 var(--space-3) var(--space-3); + padding: 0 var(--space-3) var(--space-2); display: flex; flex-direction: column; gap: var(--space-1); @@ -43,6 +43,10 @@ height: calc(100% + 31px); } +.confirmButton { + margin-top: var(--space-1); +} + .txs svg { color: var(--color-border-main); transition: color 0.1s ease-in; diff --git a/src/components/common/ConnectWallet/AccountCenter.tsx b/src/components/common/ConnectWallet/AccountCenter.tsx index 5909748a2e..0994631fee 100644 --- a/src/components/common/ConnectWallet/AccountCenter.tsx +++ b/src/components/common/ConnectWallet/AccountCenter.tsx @@ -5,7 +5,7 @@ import css from '@/components/common/ConnectWallet/styles.module.css' import EthHashInfo from '@/components/common/EthHashInfo' import ExpandLessIcon from '@mui/icons-material/ExpandLess' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import useOnboard, { forgetLastWallet, switchWallet } from '@/hooks/wallets/useOnboard' +import useOnboard, { switchWallet } from '@/hooks/wallets/useOnboard' import { useAppSelector } from '@/store' import { selectChainById } from '@/store/chainsSlice' import Identicon from '@/components/common/Identicon' @@ -36,7 +36,6 @@ const AccountCenter = ({ wallet }: { wallet: ConnectedWallet }) => { label: wallet.label, }) - forgetLastWallet() handleClose() } diff --git a/src/components/common/NavTabs/index.tsx b/src/components/common/NavTabs/index.tsx index 1d0adc8a63..4637d02e72 100644 --- a/src/components/common/NavTabs/index.tsx +++ b/src/components/common/NavTabs/index.tsx @@ -32,7 +32,7 @@ const NextLinkComposed = forwardRef(function NextCompo const NavTabs = ({ tabs }: { tabs: NavItem[] }) => { const router = useRouter() - const activeTab = tabs.map((tab) => tab.href).indexOf(router.pathname) + const activeTab = Math.max(0, tabs.map((tab) => tab.href).indexOf(router.pathname)) const query = router.query.safe ? { safe: router.query.safe } : undefined return ( diff --git a/src/components/tx-flow/flows/SuccessScreen/StatusMessage.tsx b/src/components/tx-flow/flows/SuccessScreen/StatusMessage.tsx index 572e5ee48a..db0102266d 100644 --- a/src/components/tx-flow/flows/SuccessScreen/StatusMessage.tsx +++ b/src/components/tx-flow/flows/SuccessScreen/StatusMessage.tsx @@ -9,17 +9,17 @@ const getStep = (status: PendingStatus, error?: Error) => { case PendingStatus.PROCESSING: case PendingStatus.RELAYING: return { - description: 'Transaction is now processing.', + description: 'Transaction is now processing', instruction: 'The transaction was confirmed and is now being processed.', } case PendingStatus.INDEXING: return { - description: 'Transaction was processed.', + description: 'Transaction was processed', instruction: 'It is now being indexed.', } default: return { - description: error ? 'Transaction failed' : 'Transaction was successful.', + description: error ? 'Transaction failed' : 'Transaction was successful', instruction: error ? error.message : '', } } diff --git a/src/hooks/wallets/useOnboard.ts b/src/hooks/wallets/useOnboard.ts index d440c01c8c..5e38bb1ab1 100644 --- a/src/hooks/wallets/useOnboard.ts +++ b/src/hooks/wallets/useOnboard.ts @@ -4,14 +4,14 @@ import { type ChainInfo } from '@safe-global/safe-gateway-typescript-sdk' import { getAddress } from 'ethers/lib/utils' import useChains, { useCurrentChain } from '@/hooks/useChains' import ExternalStore from '@/services/ExternalStore' -import { localItem } from '@/services/local-storage/local' import { logError, Errors } from '@/services/exceptions' import { trackEvent, WALLET_EVENTS } from '@/services/analytics' import { useInitPairing } from '@/services/pairing/hooks' -import { isWalletUnlocked, WalletNames } from '@/utils/wallets' import { useAppSelector } from '@/store' import { type EnvState, selectRpc } from '@/store/settingsSlice' -import { WALLET_KEYS } from './consts' +import { E2E_WALLET_NAME } from '@/tests/e2e-wallet' + +const WALLETCONNECT = 'WalletConnect' export type ConnectedWallet = { label: string @@ -22,12 +22,6 @@ export type ConnectedWallet = { icon?: string } -const lastWalletStorage = localItem('lastWallet') - -export const forgetLastWallet = () => { - lastWalletStorage.remove() -} - const { getStore, setStore, useStore } = new ExternalStore() export const initOnboard = async ( @@ -67,16 +61,15 @@ export const getConnectedWallet = (wallets: WalletState[]): ConnectedWallet | nu } } -const getWalletConnectLabel = async ({ label, provider }: ConnectedWallet): Promise => { - if (label.toUpperCase() !== WALLET_KEYS.WALLETCONNECT.toUpperCase()) return - +const getWalletConnectLabel = async (wallet: ConnectedWallet): Promise => { const UNKNOWN_PEER = 'Unknown' - const { default: WalletConnect } = await import('@walletconnect/client') - - const peerWallet = - ((provider as unknown as any).connector as InstanceType).peerMeta?.name || UNKNOWN_PEER - - return peerWallet ?? UNKNOWN_PEER + const { label } = wallet + const isWalletConnect = label.startsWith(WALLETCONNECT) + if (!isWalletConnect) return + const { connector } = wallet.provider as unknown as any + const peerWalletV2 = connector.session?.peer?.metadata?.name + const peerWalletV1 = connector.peerMeta?.name + return peerWalletV2 || peerWalletV1 || UNKNOWN_PEER } const trackWalletType = (wallet: ConnectedWallet) => { @@ -118,7 +111,7 @@ export const connectWallet = async ( // On mobile, automatically choose WalletConnect if there is no injected wallet if (!options && isMobile() && !hasInjectedWallet()) { options = { - autoSelect: WalletNames.WALLET_CONNECT_V2, + autoSelect: WALLETCONNECT, } } @@ -133,17 +126,6 @@ export const connectWallet = async ( return } - // Save the last used wallet and track the wallet type - const newWallet = getConnectedWallet(wallets) - - if (newWallet) { - // Save - lastWalletStorage.set(newWallet.label) - - // Track - trackWalletType(newWallet) - } - isConnecting = false return wallets @@ -153,7 +135,7 @@ export const switchWallet = (onboard: OnboardAPI) => { connectWallet(onboard) } -// Disable/enable wallets according to chain and cache the last used wallet +// Disable/enable wallets according to chain export const useInitOnboard = () => { const { configs } = useChains() const chain = useCurrentChain() @@ -178,21 +160,37 @@ export const useInitOnboard = () => { onboard.state.actions.setWalletModules(supportedWallets) } - // Connect to the last connected wallet enableWallets().then(() => { - if (onboard.state.get().wallets.length > 0) return - - const label = lastWalletStorage.get() - if (!label) return - - isWalletUnlocked(label).then((isUnlocked) => { - isUnlocked && - connectWallet(onboard, { - autoSelect: { label, disableModals: false }, - }) - }) + // e2e wallet + if (typeof window !== 'undefined' && window.Cypress) { + connectWallet(onboard, { + autoSelect: { label: E2E_WALLET_NAME, disableModals: true }, + }) + } }) }, [chain, onboard]) + + // Track connected wallet + useEffect(() => { + let lastConnectedWallet = '' + if (!onboard) return + + const walletSubscription = onboard.state.select('wallets').subscribe((wallets) => { + const newWallet = getConnectedWallet(wallets) + if (newWallet) { + if (newWallet.label !== lastConnectedWallet) { + lastConnectedWallet = newWallet.label + trackWalletType(newWallet) + } + } else { + lastConnectedWallet = '' + } + }) + + return () => { + walletSubscription.unsubscribe() + } + }, [onboard]) } export default useStore diff --git a/src/services/onboard.ts b/src/services/onboard.ts index d50faf53ca..f34f28b10c 100644 --- a/src/services/onboard.ts +++ b/src/services/onboard.ts @@ -58,9 +58,8 @@ export const createOnboard = ( connect: { removeWhereIsMyWalletWarning: true, + autoConnectLastWallet: true, }, - - // TODO: Investigate using `autoConnectLastWallet` instead of our `lastWalletStorage` }) return onboard diff --git a/src/utils/wallets.ts b/src/utils/wallets.ts index 969732049f..cb3dd8b2e9 100644 --- a/src/utils/wallets.ts +++ b/src/utils/wallets.ts @@ -1,13 +1,8 @@ -import { ProviderLabel } from '@web3-onboard/injected-wallets' -import { hasValidPairingSession } from '@/services/pairing/utils' -import { PAIRING_MODULE_LABEL } from '@/services/pairing/module' -import { E2E_WALLET_NAME } from '@/tests/e2e-wallet' import type { EthersError } from '@/utils/ethers-utils' import { ErrorCode } from '@ethersproject/logger' import { type ConnectedWallet } from '@/hooks/wallets/useOnboard' import { getWeb3ReadOnly, isSmartContract } from '@/hooks/wallets/web3' import { WALLET_KEYS } from '@/hooks/wallets/consts' -import { WALLET_CONNECT_V1_MODULE_NAME } from '@/hooks/wallets/wallets' const isWCRejection = (err: Error): boolean => { return /rejected/.test(err?.message) @@ -21,43 +16,6 @@ export const isWalletRejection = (err: EthersError | Error): boolean => { return isEthersRejection(err as EthersError) || isWCRejection(err) } -export const WalletNames = { - METAMASK: ProviderLabel.MetaMask, - WALLET_CONNECT: WALLET_CONNECT_V1_MODULE_NAME, - WALLET_CONNECT_V2: 'WalletConnect', - SAFE_MOBILE_PAIRING: PAIRING_MODULE_LABEL, -} - -/* Check if the wallet is unlocked. */ -export const isWalletUnlocked = async (walletName: string): Promise => { - if (typeof window === 'undefined') return false - - if (window.ethereum?.isConnected?.()) { - return true - } - - // Only MetaMask exposes a method to check if the wallet is unlocked - if (walletName === WalletNames.METAMASK) { - return window.ethereum?._metamask?.isUnlocked?.() || false - } - - // Wallet connect creates a localStorage entry when connected and removes it when disconnected - if (walletName === WalletNames.WALLET_CONNECT) { - return window.localStorage.getItem('walletconnect') !== null - } - - // Our own Safe mobile pairing module - if (walletName === WalletNames.SAFE_MOBILE_PAIRING && hasValidPairingSession()) { - return hasValidPairingSession() - } - - if (walletName === E2E_WALLET_NAME) { - return Boolean(window.Cypress) - } - - return false -} - export const isHardwareWallet = (wallet: ConnectedWallet): boolean => { return [WALLET_KEYS.LEDGER, WALLET_KEYS.TREZOR, WALLET_KEYS.KEYSTONE].includes( wallet.label.toUpperCase() as WALLET_KEYS,