diff --git a/apps/next/pages/_app.tsx b/apps/next/pages/_app.tsx index e110ddaf9..aa37944fc 100644 --- a/apps/next/pages/_app.tsx +++ b/apps/next/pages/_app.tsx @@ -10,11 +10,11 @@ import type { AuthProviderProps } from 'app/provider/auth' import { api } from 'app/utils/api' import type { NextPage } from 'next' import Head from 'next/head' -import type { ReactElement, ReactNode } from 'react' +import { useEffect, type ReactElement, type ReactNode } from 'react' import type { SolitoAppProps } from 'solito' import { Provider } from 'app/provider' import { projectId, config as wagmiConfig } from 'app/provider/wagmi/config' -import { createWeb3Modal } from '@web3modal/wagmi/react' +import { createWeb3Modal, useWeb3ModalTheme } from '@web3modal/wagmi/react' import { baseMainnetClient } from '@my/wagmi' import { YStack, H1, H2 } from '@my/ui' import { IconSendLogo } from 'app/components/icons' @@ -23,6 +23,23 @@ createWeb3Modal({ wagmiConfig, projectId, defaultChain: baseMainnetClient.chain, + themeVariables: { + '--w3m-accent': '#86AE80', + }, + tokens: { + 8453: { + address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', + }, + 845337: { + address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', + }, + 1: { + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + }, + 1337: { + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + }, + }, }) if (process.env.NODE_ENV === 'production') { @@ -40,7 +57,12 @@ function MyApp({ // reference: https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts const getLayout = Component.getLayout || ((page) => page) - const [, setTheme] = useRootTheme() + const [theme, setTheme] = useRootTheme() + const { setThemeMode } = useWeb3ModalTheme() + + useEffect(() => { + setThemeMode(theme) + }, [theme, setThemeMode]) return ( <> diff --git a/packages/app/features/deposit/web3/__snapshots__/screen.test.tsx.snap b/packages/app/features/deposit/web3/__snapshots__/screen.test.tsx.snap index 267474384..6bcb940fd 100644 --- a/packages/app/features/deposit/web3/__snapshots__/screen.test.tsx.snap +++ b/packages/app/features/deposit/web3/__snapshots__/screen.test.tsx.snap @@ -1,388 +1,375 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`DepositWeb3Screen renders the connect to deposit when wallet is not connected 1`] = ` - - - Connect to Deposit - - - You need to connect to a wallet to deposit funds. - - + Connect to Deposit + + - + You need to connect to a wallet to deposit funds. + + + + + Or direct deposit on base + + - - - + 0xb0b...0000 + + - - - - + - - + - - - - - - - - + + + + + , + - - Connect to Deposit - - - -`; - -exports[`DepositWeb3Screen renders the deposit web3 form when wallet is connected 1`] = ` -[ - - - - - - - Depositing from - 0x123 - - + + + + , +] +`; + +exports[`DepositWeb3Screen renders the deposit web3 form when wallet is connected 1`] = ` +[ + + + + + + + + + + + + + + + Send recommends depositing USDC first. This will ensure a smooth sending experience throughout the app + + Token - + + USDC + + + + + - - - - - - - - - - - - - - Amount @@ -894,16 +852,6 @@ exports[`DepositWeb3Screen renders the deposit web3 form when wallet is connecte "value": undefined, }, }, - "token": { - "_f": { - "mount": true, - "name": "token", - "ref": { - "name": "token", - }, - "value": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", - }, - }, }, "_formState": { "dirtyFields": {}, @@ -930,7 +878,6 @@ exports[`DepositWeb3Screen renders the deposit web3 form when wallet is connecte "array": Set {}, "focus": "", "mount": Set { - "token", "amount", }, "unMount": Set {}, @@ -982,9 +929,6 @@ exports[`DepositWeb3Screen renders the deposit web3 form when wallet is connecte { "next": [Function], }, - { - "next": [Function], - }, ], "subscribe": [Function], "unsubscribe": [Function], @@ -995,9 +939,6 @@ exports[`DepositWeb3Screen renders the deposit web3 form when wallet is connecte { "next": [Function], }, - { - "next": [Function], - }, ], "subscribe": [Function], "unsubscribe": [Function], @@ -1016,7 +957,7 @@ exports[`DepositWeb3Screen renders the deposit web3 form when wallet is connecte } focusVisibleStyle={{}} focusable={true} - id=":r8:" + id=":r3:" name="amount" onBlur={[Function]} onChangeText={[Function]} @@ -1051,6 +992,7 @@ exports[`DepositWeb3Screen renders the deposit web3 form when wallet is connecte "position": "relative", } } + testID="amountInput" value="" /> @@ -1065,25 +1007,6 @@ exports[`DepositWeb3Screen renders the deposit web3 form when wallet is connecte } /> - - Balance: - - >0.00 - - USDC - @@ -1115,359 +1038,31 @@ exports[`DepositWeb3Screen renders the deposit web3 form when wallet is connecte - - - - - Deposit - - - - - - View - 0x123 - on - - Basescan - - - - - - - - - , - - - - - - , - - - - - - - USDC - - - - - - ETH + suppressHighlighting={true} + > + Deposit USDC + - - SEND + View + 0x123 + on + + Basescan @@ -1671,6 +1171,76 @@ exports[`DepositWeb3Screen renders the deposit web3 form when wallet is connecte + + , + + + - - - - - - - - - Switch - - + `; diff --git a/packages/app/features/deposit/web3/screen.test.tsx b/packages/app/features/deposit/web3/screen.test.tsx index 0da6b9d50..065e5ead3 100644 --- a/packages/app/features/deposit/web3/screen.test.tsx +++ b/packages/app/features/deposit/web3/screen.test.tsx @@ -10,6 +10,16 @@ jest.mock('wagmi') jest.mock('@my/wagmi') jest.mock('@web3modal/wagmi/react') +jest.mock('app/utils/useSendAccountBalances', () => ({ + useSendAccountBalances: jest.fn().mockReturnValue({ + balances: { + 0: { result: 0n }, + 1: { result: 1n }, + }, + totalBalance: () => 1n, + }), +})) + const { useAccount, useSendTransaction, useBalance, useSwitchAccount } = wagmi as unknown as typeof import('app/__mocks__/wagmi') const { useWriteErc20Transfer } = myWagmi as unknown as typeof import('app/__mocks__/@my/wagmi') @@ -77,11 +87,14 @@ describe('DepositWeb3Screen', () => { ) - await waitFor(() => expect(screen.getByText(`Depositing from ${'0x123'}`)).toBeVisible()) + await waitFor(() => expect(screen.getByTestId('DepositWeb3ScreenBefore')).toBeVisible()) + const usdcLabel = screen.getByTestId('noUsdc') + await waitFor(() => expect(usdcLabel).toBeVisible()) + expect(screen).toMatchSnapshot() const user = userEvent.setup() - await user.type(screen.getByLabelText('Amount'), '0.01') - const depositButton = screen.getByRole('button', { name: 'Deposit' }) + await user.type(screen.getByTestId('amountInput'), '0.01') + const depositButton = screen.getByRole('button', { name: 'Deposit USDC' }) await act(async () => { // await user.press(depositButton) // something about tamagui button is causing this to fail await depositButton.props.onPress() @@ -101,14 +114,10 @@ describe('DepositWeb3Screen', () => { ) - // screen.debug({ message: 'DepositWeb3Screen: render' }) await waitFor(() => expect(screen.getByRole('header', { name: 'Connect to Deposit' })).toBeVisible() ) expect(screen).toMatchSnapshot() - const user = userEvent.setup() - await user.press(screen.getByRole('button', { name: 'Connect to Deposit' })) - expect(useWeb3Modal).toHaveBeenCalled() }) it('renders the switch network screen when base network is not selected', async () => { useAccount.mockReturnValue({ @@ -133,11 +142,6 @@ describe('DepositWeb3Screen', () => { ) - const switchNetworkButton = screen.getByRole('button', { name: 'Switch' }) - await waitFor(() => expect(switchNetworkButton).toBeVisible()) expect(screen).toMatchSnapshot() - const user = userEvent.setup() - await user.press(switchNetworkButton) - expect(useWeb3Modal).toHaveBeenCalled() }) }) diff --git a/packages/app/features/deposit/web3/screen.tsx b/packages/app/features/deposit/web3/screen.tsx index f86167ba9..77c444f69 100644 --- a/packages/app/features/deposit/web3/screen.tsx +++ b/packages/app/features/deposit/web3/screen.tsx @@ -16,7 +16,7 @@ import { import { baseMainnet, useWriteErc20Transfer } from '@my/wagmi' import { useWeb3Modal } from '@web3modal/wagmi/react' import { DepositAddress } from 'app/components/DepositAddress' -import { IconEthereum, IconRefresh } from 'app/components/icons' +import { IconInfoCircle, IconRefresh } from 'app/components/icons' import { IconChainBase } from 'app/components/icons/IconChainBase' import { coins } from 'app/data/coins' import { SchemaForm, formFields } from 'app/utils/SchemaForm' @@ -24,6 +24,7 @@ import { assert } from 'app/utils/assert' import formatAmount from 'app/utils/formatAmount' import { useSendAccount } from 'app/utils/send-accounts' import { shorten } from 'app/utils/strings' +import { useSendAccountBalances } from 'app/utils/useSendAccountBalances' import { useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import { Link } from 'solito/link' @@ -44,15 +45,8 @@ import { z } from 'zod' * 3. Sign transaction */ export function DepositWeb3Screen() { - const { open: openConnectModal } = useWeb3Modal() const { isConnected, chainId, chain } = useAccount() - useEffect(() => { - if (!isConnected) { - openConnectModal?.() - } - }, [isConnected, openConnectModal]) - if (!isConnected) { return ( @@ -62,14 +56,9 @@ export function DepositWeb3Screen() { You need to connect to a wallet to deposit funds. - + + + ) } @@ -83,14 +72,7 @@ export function DepositWeb3Screen() { You are currently on {chain?.name}. Switch to {baseMainnet.name} to deposit funds. - + ) } @@ -102,6 +84,17 @@ export function DepositWeb3Screen() { ) } +function DepositAddressWrapper() { + const { data: sendAccount } = useSendAccount() + + return ( + + Or direct deposit on base + + + ) +} + const schema = z.object({ token: formFields.select.describe('Token'), amount: formFields.text.describe('Amount'), @@ -114,6 +107,8 @@ function DepositForm() { const options = coins.map((coin) => ({ name: coin.symbol, value: coin.token })) const first = options[0] assert(!!first, 'first coin not found') + const { balances } = useSendAccountBalances() + const sendUSDCBalance = balances?.[0]?.result const coin = coins.find((coin) => coin.token === form.watch('token')) const isCoinSelected = !!coin @@ -270,6 +265,7 @@ function DepositForm() { }, amount: { autoFocus: true, + testID: 'amountInput', }, }} formProps={{ @@ -282,18 +278,8 @@ function DepositForm() { }} renderBefore={() => ( - -

- Depositing from {account} -

- - - Or direct deposit on base - - + +
)} renderAfter={({ submit }) => ( @@ -339,7 +325,7 @@ function DepositForm() { br={12} > - {isLoadingReceipt ? 'Depositing...' : 'Deposit'} + {isLoadingReceipt ? 'Depositing...' : `Deposit ${coin?.symbol}`} @@ -360,17 +346,36 @@ function DepositForm() { > {({ token, amount }) => ( + {!sendUSDCBalance && ( + + + + Send recommends depositing USDC first. This will ensure a smooth sending experience + throughout the app + + + )} - {token} + {sendUSDCBalance ? ( + token + ) : ( + + + Token + + + USDC + + + )} {amount} - {depositorBalance?.value !== undefined && !!coin ? ( - - Balance:{' '} - {formatAmount(formatUnits(depositorBalance?.value ?? 0n, coin?.decimals ?? 0))}{' '} - {coin?.symbol} - - ) : null} diff --git a/packages/playwright/tests/deposit.onboarded.spec.ts b/packages/playwright/tests/deposit.onboarded.spec.ts index 4e1ee3cc2..8bd1d1f4a 100644 --- a/packages/playwright/tests/deposit.onboarded.spec.ts +++ b/packages/playwright/tests/deposit.onboarded.spec.ts @@ -72,7 +72,8 @@ test('can deposit USDC with web3 wallet', async ({ await depositWeb3Link.click() await page.waitForURL('/deposit/web3') await expect(page.locator('w3m-modal')).toBeVisible() - + const connectButton = await page.locator('w3m-button') + await connectButton.click() await page.locator('w3m-connect-injected-widget').click() await expect @@ -102,9 +103,7 @@ test('can deposit USDC with web3 wallet', async ({ .toBe(1) await wallet.authorize(HeadlessWeb3Provider.Web3RequestKind.RequestAccounts) - await expect( - page.getByRole('heading', { name: `Depositing from ${account.address}` }) - ).toBeVisible() + await expect(page.getByText('10 USDC')).toBeVisible() expect(await page.getByLabel('Token').inputValue()).toBe(usdcAddress[testBaseClient.chain.id]) await page.getByLabel('Amount').fill('10') @@ -209,7 +208,8 @@ test('can deposit ETH with web3 wallet', async ({ await depositWeb3Button.click() await page.waitForURL('/deposit/web3') await expect(page.locator('w3m-modal')).toBeVisible() - + const connectButton = await page.locator('w3m-button') + await connectButton.click() await page.locator('w3m-connect-injected-widget').click() await expect @@ -239,10 +239,6 @@ test('can deposit ETH with web3 wallet', async ({ .toBe(1) await wallet.authorize(HeadlessWeb3Provider.Web3RequestKind.RequestAccounts) - await expect( - page.getByRole('heading', { name: `Depositing from ${account.address}` }) - ).toBeVisible() - const tokenSelect = page.getByLabel('Token') await page.getByLabel('Token').selectOption('eth') expect(await tokenSelect.inputValue()).toBe('eth') @@ -290,3 +286,120 @@ test('can deposit ETH with web3 wallet', async ({ timeout: 10000, }) }) + +test('can connect and disconnect using wallet button', async ({ + page, + injectWeb3Provider, + accounts, + user: { profile }, + supabase, +}) => { + log = debug(`test:activity:${profile.id}:${test.info().parallelIndex}`) + const { data: sendAccount, error } = await supabase.from('send_accounts').select('*').single() + expect(error).toBeFalsy() + assert(!!sendAccount, 'no send account found') + + const wallet = await injectWeb3Provider() + const account = accounts[0] + assert(!!account, 'no web3 accounts found') + log('account', account) + + await page.goto('/deposit/web3') + + const connectButton = await page.locator('w3m-button') + expect(connectButton).toHaveText('Connect Wallet') + await connectButton.click() + await page.locator('w3m-connect-injected-widget').click() + + await expect + .poll( + async () => { + return wallet.getPendingRequestCount( + HeadlessWeb3Provider.Web3RequestKind.RequestPermissions + ) + }, + { + timeout: 5000, + message: 'Did not receive accounts request', + } + ) + .toBe(1) + await wallet.authorize(HeadlessWeb3Provider.Web3RequestKind.RequestPermissions) + await expect + .poll( + async () => { + return wallet.getPendingRequestCount(HeadlessWeb3Provider.Web3RequestKind.RequestAccounts) + }, + { + timeout: 5000, + message: 'Did not receive accounts request', + } + ) + .toBe(1) + await wallet.authorize(HeadlessWeb3Provider.Web3RequestKind.RequestAccounts) + const walletButton = page.getByTestId('account-button') + await expect(walletButton).toBeVisible() + await walletButton.click() + await page.getByRole('button', { name: 'Disconnect' }).click() + expect(page.locator('w3m-button')).toHaveText('Connect Wallet') +}) + +test('must switch to supported network', async ({ + page, + injectWeb3Provider, + accounts, + user: { profile }, + supabase, +}) => { + log = debug(`test:activity:${profile.id}:${test.info().parallelIndex}`) + const { data: sendAccount, error } = await supabase.from('send_accounts').select('*').single() + expect(error).toBeFalsy() + assert(!!sendAccount, 'no send account found') + + const wallet = await injectWeb3Provider() + const account = accounts[0] + assert(!!account, 'no web3 accounts found') + log('account', account) + + await page.goto('/deposit/web3') + + const walletButton = page.locator('w3m-button') + expect(walletButton).toHaveText('Connect Wallet') + await walletButton.click() + await page.locator('w3m-connect-injected-widget').click() + + await expect + .poll( + async () => { + return wallet.getPendingRequestCount( + HeadlessWeb3Provider.Web3RequestKind.RequestPermissions + ) + }, + { + timeout: 5000, + message: 'Did not receive accounts request', + } + ) + .toBe(1) + await wallet.authorize(HeadlessWeb3Provider.Web3RequestKind.RequestPermissions) + await expect + .poll( + async () => { + return wallet.getPendingRequestCount(HeadlessWeb3Provider.Web3RequestKind.RequestAccounts) + }, + { + timeout: 5000, + message: 'Did not receive accounts request', + } + ) + .toBe(1) + await wallet.authorize(HeadlessWeb3Provider.Web3RequestKind.RequestAccounts) + + await expect(walletButton).toContainText('0.000') + + wallet.addNetwork(1, 'https://eth.public-rpc.com') + wallet.switchNetwork(1) + await expect(page.getByTestId('account-button')).toContainText('Switch Network') + await expect(page.getByRole('heading', { name: 'Switch to Base Localhost' })).toBeVisible() + await expect(page.getByTestId('SubmitButton')).toHaveCount(0) +})