diff --git a/.nvmrc b/.nvmrc index b009dfb9d..3c032078a 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -lts/* +18 diff --git a/e2e/connect.test.tsx b/e2e/connect.test.tsx index d6c922e5a..8dc1afd8a 100644 --- a/e2e/connect.test.tsx +++ b/e2e/connect.test.tsx @@ -10,44 +10,83 @@ import { tokens } from '@uniswap/default-token-list' import { SwapWidget } from '../src' +const HARDHAT_ACCOUNT_DISPLAY_STRING = `${hardhat.account.address?.substring( + 0, + 6 +)}...${hardhat.account.address?.substring(hardhat.account.address.length - 4)}` + describe('connect', () => { let component: RenderResult - let wallet: HTMLElement + let account: HTMLElement + let connectWallet: HTMLElement let toolbar: HTMLElement + let tokenSelect: HTMLElement beforeEach(async () => { component = render() - wallet = await component.findByTestId('wallet') + connectWallet = await component.findByTestId('connect-wallet') toolbar = await component.findByTestId('toolbar') + tokenSelect = (await component.findAllByTestId('token-select'))[0] }) - it('prompts for wallet connection in the Toolbar', async () => { - expect(wallet.hidden).toBeTruthy() - expect(toolbar.textContent).toBe('Connect wallet to swap') + describe('with no params, using fallback JSON RPC URL', () => { + it('prompts for wallet connection in the Wallet and Toolbar', async () => { + expect(toolbar.textContent).toBe('Connecting…') + await waitFor(() => expect(toolbar.textContent).not.toBe('Connecting…')) + expect(connectWallet.textContent).toBe('Connect wallet to swap') + expect(toolbar.textContent).toBe('Connect wallet to swap') + }) + + it('expects widget not to be disabled', async () => { + expect(tokenSelect).toHaveProperty('disabled', true) + await waitFor(() => expect(toolbar.textContent).not.toBe('Connecting…')) + expect(tokenSelect).toHaveProperty('disabled', false) + }) }) - describe('with jsonRpcEndpoint', () => { - it('prompts for wallet connection in the Wallet', async () => { - component.rerender() - expect(wallet.hidden).toBeTruthy() + describe('with valid jsonRpcUrlMap', () => { + it('prompts for wallet connection in the Wallet and Toolbar', async () => { + component = render() + expect(connectWallet.textContent).toBe('Connect wallet to swap') expect(toolbar.textContent).toBe('Connecting…') + await waitFor(() => expect(toolbar.textContent).not.toBe('Connecting…')) + expect(connectWallet.textContent).toBe('Connect wallet to swap') + expect(toolbar.textContent).toBe('Connect wallet to swap') + }) + it('expects widget not to be disabled', async () => { + component = render() + expect(tokenSelect).toHaveProperty('disabled', true) await waitFor(() => expect(toolbar.textContent).not.toBe('Connecting…')) - expect(wallet.hidden).toBeFalsy() - expect(wallet.textContent).toBe('Connect wallet to swap') - expect(toolbar.textContent).toBe('Enter an amount') + expect(tokenSelect).toHaveProperty('disabled', false) }) }) - describe('with provider', () => { - it('does not prompt for wallet connection', async () => { - component.rerender() - expect(wallet.hidden).toBeTruthy() + describe('with wallet provider', () => { + it('displays connected account chip', async () => { + component = render() + await waitFor(() => expect(toolbar.textContent).not.toBe('Connecting…')) + account = await component.findByTestId('account') + expect(account.textContent?.toLowerCase()).toBe(HARDHAT_ACCOUNT_DISPLAY_STRING) + }) + + it('does not prompt for wallet connection in toolbar', async () => { + component = render() expect(toolbar.textContent).toBe('Connecting…') + await waitFor( + async () => { + toolbar = (await component.findAllByTestId('toolbar'))[1] + expect(toolbar.textContent).toBe('Enter an amount') + }, + { timeout: 10000 } + ) + }) + it('expects widget not to be disabled', async () => { + component = render() + expect(tokenSelect).toHaveProperty('disabled', true) await waitFor(() => expect(toolbar.textContent).not.toBe('Connecting…')) - expect(wallet.hidden).toBeTruthy() - expect(toolbar.textContent).toBe('Enter an amount') + expect(tokenSelect).toHaveProperty('disabled', false) }) }) }) diff --git a/package.json b/package.json index da2b43f62..a8ed42344 100644 --- a/package.json +++ b/package.json @@ -67,11 +67,14 @@ "@uniswap/token-lists": "^1.0.0-beta.30", "@uniswap/v2-sdk": "^3.0.1", "@uniswap/v3-sdk": "^3.8.2", - "@web3-react/core": "8.0.30-beta.0", - "@web3-react/eip1193": "8.0.23-beta.0", - "@web3-react/empty": "8.0.17-beta.0", - "@web3-react/types": "8.0.17-beta.0", - "@web3-react/url": "8.0.22-beta.0", + "@web3-react/core": "8.0.35-beta.0", + "@web3-react/eip1193": "8.0.26-beta.0", + "@web3-react/empty": "8.0.20-beta.0", + "@web3-react/metamask": "8.0.28-beta.0", + "@web3-react/network": "8.0.27-beta.0", + "@web3-react/types": "8.0.20-beta.0", + "@web3-react/url": "8.0.25-beta.0", + "@web3-react/walletconnect": "8.0.35-beta.0", "ajv": "^6.12.3", "cids": "^1.0.0", "ethers": "^5.1.4", @@ -85,6 +88,7 @@ "node-vibrant": "^3.2.1-alpha.1", "polished": "^3.3.2", "popper-max-size-modifier": "^0.2.0", + "qrcode": "^1.5.0", "react-feather": "^2.0.8", "react-popper": "^2.2.3", "react-virtualized-auto-sizer": "^1.0.2", @@ -136,7 +140,8 @@ "@types/lingui__react": "^2.8.3", "@types/ms.macro": "^2.0.0", "@types/multicodec": "^1.0.0", - "@types/node": "^13.13.5", + "@types/node": "^18.6.3", + "@types/qrcode": "^1.4.2", "@types/qs": "^6.9.2", "@types/react": "^17.0.2", "@types/react-dom": "^17.0.1", @@ -154,8 +159,6 @@ "@uniswap/v3-core": "1.0.0", "@uniswap/v3-periphery": "^1.1.1", "@walletconnect/ethereum-provider": "^1.7.1", - "@web3-react/metamask": "8.0.24-beta.0", - "@web3-react/walletconnect": "8.0.31-beta.0", "babel-jest": "^27.5.1", "babel-loader": "^8.2.5", "babel-plugin-macros": "^3.1.0", diff --git a/src/assets/images/identicons/IdenticonGradient-0.png b/src/assets/images/identicons/IdenticonGradient-0.png new file mode 100644 index 000000000..bcb318660 Binary files /dev/null and b/src/assets/images/identicons/IdenticonGradient-0.png differ diff --git a/src/assets/images/identicons/IdenticonGradient-1.png b/src/assets/images/identicons/IdenticonGradient-1.png new file mode 100644 index 000000000..f83406e77 Binary files /dev/null and b/src/assets/images/identicons/IdenticonGradient-1.png differ diff --git a/src/assets/images/identicons/IdenticonGradient-2.png b/src/assets/images/identicons/IdenticonGradient-2.png new file mode 100644 index 000000000..2b82db596 Binary files /dev/null and b/src/assets/images/identicons/IdenticonGradient-2.png differ diff --git a/src/assets/images/identicons/IdenticonGradient-3.png b/src/assets/images/identicons/IdenticonGradient-3.png new file mode 100644 index 000000000..acb5d920e Binary files /dev/null and b/src/assets/images/identicons/IdenticonGradient-3.png differ diff --git a/src/assets/images/identicons/IdenticonGradient-4.png b/src/assets/images/identicons/IdenticonGradient-4.png new file mode 100644 index 000000000..038d56075 Binary files /dev/null and b/src/assets/images/identicons/IdenticonGradient-4.png differ diff --git a/src/assets/images/identicons/IdenticonGradient-5.png b/src/assets/images/identicons/IdenticonGradient-5.png new file mode 100644 index 000000000..82d5c9fbe Binary files /dev/null and b/src/assets/images/identicons/IdenticonGradient-5.png differ diff --git a/src/assets/images/identicons/IdenticonGradient-6.png b/src/assets/images/identicons/IdenticonGradient-6.png new file mode 100644 index 000000000..321131777 Binary files /dev/null and b/src/assets/images/identicons/IdenticonGradient-6.png differ diff --git a/src/assets/images/identicons/IdenticonGradient-7.png b/src/assets/images/identicons/IdenticonGradient-7.png new file mode 100644 index 000000000..89992c10e Binary files /dev/null and b/src/assets/images/identicons/IdenticonGradient-7.png differ diff --git a/src/assets/images/identicons/IdenticonGradient-8.png b/src/assets/images/identicons/IdenticonGradient-8.png new file mode 100644 index 000000000..0a2a4165f Binary files /dev/null and b/src/assets/images/identicons/IdenticonGradient-8.png differ diff --git a/src/assets/images/identicons/IdenticonGradient-9.png b/src/assets/images/identicons/IdenticonGradient-9.png new file mode 100644 index 000000000..37546a725 Binary files /dev/null and b/src/assets/images/identicons/IdenticonGradient-9.png differ diff --git a/src/assets/images/metamaskIcon.png b/src/assets/images/metamaskIcon.png new file mode 100644 index 000000000..85714ea29 Binary files /dev/null and b/src/assets/images/metamaskIcon.png differ diff --git a/src/assets/images/walletConnectIcon.svg b/src/assets/images/walletConnectIcon.svg new file mode 100644 index 000000000..f78a7b850 --- /dev/null +++ b/src/assets/images/walletConnectIcon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/svg/wallet_disconnect.svg b/src/assets/svg/wallet_disconnect.svg new file mode 100644 index 000000000..b1edce83f --- /dev/null +++ b/src/assets/svg/wallet_disconnect.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/ConnectWallet/ConnectWallet.tsx b/src/components/ConnectWallet/ConnectWallet.tsx new file mode 100644 index 000000000..ee5bfe768 --- /dev/null +++ b/src/components/ConnectWallet/ConnectWallet.tsx @@ -0,0 +1,51 @@ +import { Trans } from '@lingui/macro' +import { Wallet as WalletIcon } from 'icons' +import { useAtomValue } from 'jotai/utils' +import { useState } from 'react' +import { onConnectWalletClickAtom } from 'state/wallet' +import styled from 'styled-components/macro' +import { ThemedText } from 'theme' + +import { TextButton } from '../Button' +import Dialog from '../Dialog' +import Row from '../Row' +import { ConnectWalletDialog } from './ConnectWalletDialog' + +interface ConnectWalletProps { + disabled?: boolean +} + +const WalletButton = styled(TextButton)<{ hidden?: boolean }>` + filter: none; + visibility: ${({ hidden }) => (hidden ? 'hidden' : 'visible')}; +` + +export default function ConnectWallet({ disabled }: ConnectWalletProps) { + // Opens a dialog that initiates own wallet connection flow + const [open, setOpen] = useState(false) + const onClose = () => setOpen(false) + + const onConnectWalletClick = useAtomValue(onConnectWalletClickAtom) + const onClick = () => { + const promise = onConnectWalletClick && onConnectWalletClick() + return promise ? promise.then((open) => setOpen(open)) : setOpen(true) + } + + return ( + <> + + {open && ( + + + + )} + + ) +} diff --git a/src/components/ConnectWallet/ConnectWalletDialog.tsx b/src/components/ConnectWallet/ConnectWalletDialog.tsx new file mode 100644 index 000000000..3f46866d6 --- /dev/null +++ b/src/components/ConnectWallet/ConnectWalletDialog.tsx @@ -0,0 +1,231 @@ +import { Trans } from '@lingui/macro' +import { Web3ReactHooks } from '@web3-react/core' +import { EIP1193 } from '@web3-react/eip1193' +import { Url } from '@web3-react/url' +import { URI_AVAILABLE, WalletConnect } from '@web3-react/walletconnect' +import METAMASK_ICON_URL from 'assets/images/metamaskIcon.png' +import WALLETCONNECT_ICON_URL from 'assets/images/walletConnectIcon.svg' +import Button from 'components/Button' +import Column from 'components/Column' +import { Header } from 'components/Dialog' +import Row from 'components/Row' +import EventEmitter from 'events' +import { connections, defaultChainIdAtom, Web3Connection } from 'hooks/connectWeb3/useWeb3React' +import { atom, useAtom } from 'jotai' +import { useAtomValue } from 'jotai/utils' +import QRCode from 'qrcode' +import { useCallback, useEffect, useState } from 'react' +import styled from 'styled-components/macro' +import { lightTheme, ThemedText } from 'theme' + +const NO_WALLET_HELP_CENTER_URL = 'https://help.uniswap.org/en/articles/5391585-how-to-get-a-wallet' +const onError = (error: Error) => console.error('web3 error:', error) + +const Body = styled(Column)` + height: calc(100% - 2.5em); + padding: 0em 0.75em 0.75em; +` + +const SecondaryOptionsRow = styled(Row)` + align-self: end; + grid-template-columns: repeat(2, calc(50% - 0.5em / 2)); + height: fit-content; +` + +const StyledButtonContents = styled(Column)` + gap: 0.75em; + justify-items: center; +` + +const StyledMainButton = styled(Button)` + background-color: ${({ theme }) => theme.container}; + border-radius: ${({ theme }) => theme.borderRadius * 0.75}em; + height: 200px; + padding: 22px; +` + +const StyledMainButtonRow = styled(Row)` + grid-template-columns: repeat(2, calc(50% - 1em / 2)); + justify-items: center; +` + +const StyledSmallButton = styled(Button)` + background-color: ${({ theme }) => theme.container}; + border-radius: ${({ theme }) => theme.borderRadius * 0.75}em; + height: 88px; + padding: 16px; +` + +const StyledNoWalletText = styled(ThemedText.Subhead1)` + line-height: 20px; + white-space: pre-wrap; +` + +const QRCodeWrapper = styled.div` + height: 110px; + width: 110px; + path { + /* Maximize contrast: transparent in light theme, otherwise hard-coded to light theme. */ + fill: ${({ theme }) => (theme.container === lightTheme.container ? '#00000000' : lightTheme.container)}; + } +` + +function ButtonContents({ walletName, logoSrc, caption }: ButtonProps) { + return ( + + {walletName} + {walletName} + {caption && ( + + {caption} + + )} + + ) +} + +interface ButtonProps { + walletName?: string + logoSrc?: string + caption?: string + connection?: Web3Connection + onClick?: () => void +} + +const wcQRUriAtom = atom(undefined) + +function toQrCodeSvg(qrUri: string): Promise { + return QRCode.toString(qrUri, { + // Leave a margin to increase contrast in dark mode. + margin: 1, + // Use 55*2=110 for the width to prevent distortion. The generated viewbox is "0 0 55 55". + width: 110, + type: 'svg', + }) +} + +function WalletConnectButton({ walletName, logoSrc, connection: wcTileConnection, onClick }: ButtonProps) { + const [walletConnect] = wcTileConnection as [WalletConnect, Web3ReactHooks] + const defaultChainId = useAtomValue(defaultChainIdAtom) + + const [qrUri, setQrUri] = useAtom(wcQRUriAtom) + const [qrCodeSvg, setQrCodeSvg] = useState('') + + useEffect(() => { + let stale = false + if (qrUri) { + toQrCodeSvg(qrUri).then((qrCodeSvg) => { + if (stale) return + setQrCodeSvg(qrCodeSvg) + }) + } else { + walletConnect.activate(defaultChainId).catch((e) => { + if (stale) return + onError?.(e) + }) + } + return () => { + stale = true + } + }, [qrUri, walletConnect, defaultChainId]) + + useEffect(() => { + const disconnectListener = async (err: Error | null, _: any) => { + if (err) onError?.(err) + // Clear saved QR URI after disconnection + setQrUri(undefined) + walletConnect.deactivate() + } + walletConnect.provider?.connector.on('disconnect', disconnectListener) + + // Need both URI event listeners + walletConnect.events.on(URI_AVAILABLE, async (uri: string) => { + if (uri) { + setQrUri(uri) + } + }) + + const uriListener = async (err: Error | null, payload: any) => { + if (err) onError?.(err) + const uri: string = payload.params[0] + if (uri) { + setQrUri(uri) + } + } + walletConnect.provider?.connector.on('display_uri', uriListener) + + return () => { + walletConnect.events.off(URI_AVAILABLE) + ;(walletConnect.provider?.connector as unknown as EventEmitter | undefined)?.off('display_uri', uriListener) + } + }) + + return ( + + + + + + + ) +} + +function MetaMaskButton({ walletName, logoSrc, onClick }: ButtonProps) { + return ( + + + + ) +} + +function NoWalletButton() { + return ( + window.open(NO_WALLET_HELP_CENTER_URL)}> + + {`I don't have a wallet`} + + + ) +} + +export function ConnectWalletDialog() { + const [firstConnector] = connections[0] + const integratorProvidedConnector = firstConnector instanceof EIP1193 || firstConnector instanceof Url + const [mmConnection, wcTileConnection, wcPopupConnection]: Web3Connection[] = integratorProvidedConnector + ? connections.slice(1) + : connections + const defaultChainId = useAtomValue(defaultChainIdAtom) + + const activateWalletConnectPopup = useCallback(() => { + const [walletConnectPopup] = wcPopupConnection + walletConnectPopup.activate(defaultChainId)?.catch(onError) + }, [wcPopupConnection, defaultChainId]) + const activateMetaMask = useCallback(() => { + const [metamask] = mmConnection + metamask.activate(defaultChainId)?.catch(onError) + }, [mmConnection, defaultChainId]) + + return ( + <> +
Connect wallet} /> + + + + + + + + + + + ) +} diff --git a/src/components/ConnectWallet/ConnectedWalletChip.tsx b/src/components/ConnectWallet/ConnectedWalletChip.tsx new file mode 100644 index 000000000..e8dc61592 --- /dev/null +++ b/src/components/ConnectWallet/ConnectedWalletChip.tsx @@ -0,0 +1,48 @@ +import { Trans } from '@lingui/macro' +import { useWeb3React } from '@web3-react/core' +import { TextButton } from 'components/Button' +import Row from 'components/Row' +import { WalletDisconnect as WalletDisconnectIcon } from 'icons' +import Identicon from 'icons/identicon' +import { useState } from 'react' +import styled from 'styled-components/macro' +import { ThemedText } from 'theme' + +const AccountButton = styled(TextButton)<{ hidden?: boolean }>` + filter: none; + visibility: ${({ hidden }) => (hidden ? 'hidden' : 'visible')}; +` + +export default function ConnectedWalletChip({ disabled, account }: { disabled?: boolean; account?: string }) { + const [hover, setHover] = useState(false) + const { connector } = useWeb3React() + + return ( + <> + + + ) +} diff --git a/src/components/ConnectWallet/index.tsx b/src/components/ConnectWallet/index.tsx new file mode 100644 index 000000000..755ea1931 --- /dev/null +++ b/src/components/ConnectWallet/index.tsx @@ -0,0 +1,39 @@ +import { useWeb3React } from '@web3-react/core' +import { connections, defaultChainIdAtom, getConnectorName } from 'hooks/connectWeb3/useWeb3React' +import { useAtomValue } from 'jotai/utils' +import { useEffect } from 'react' + +import ConnectedWalletChip from './ConnectedWalletChip' +import ConnectWallet from './ConnectWallet' + +interface WalletProps { + disabled?: boolean +} + +export default function Wallet({ disabled }: WalletProps) { + // Attempt to connect eagerly on mount + const defaultChainId = useAtomValue(defaultChainIdAtom) + useEffect(() => { + connections.forEach(([wallet, _]) => { + const success = wallet.connectEagerly ? wallet.connectEagerly(defaultChainId) : wallet.activate(defaultChainId) + success?.catch(() => { + if (stale) return + console.log(`Could not connect to ${getConnectorName(wallet)}`) + }) + }) + + let stale = false + return () => { + stale = true + } + }, [defaultChainId]) + + const { account, isActive } = useWeb3React() + + const isAccountConnected = isActive && Boolean(account) + return isAccountConnected ? ( + + ) : ( + + ) +} diff --git a/src/components/EtherscanLink.tsx b/src/components/EtherscanLink.tsx index 94f67f124..188794c40 100644 --- a/src/components/EtherscanLink.tsx +++ b/src/components/EtherscanLink.tsx @@ -1,5 +1,5 @@ +import { useWeb3React } from '@web3-react/core' import { SupportedChainId } from 'constants/chains' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import { Link } from 'icons' import { ReactNode, useMemo } from 'react' import styled from 'styled-components/macro' @@ -22,7 +22,7 @@ interface EtherscanLinkProps { } export default function EtherscanLink({ data, type, color = 'currentColor', children }: EtherscanLinkProps) { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const url = useMemo( () => data && getExplorerLink(chainId || SupportedChainId.MAINNET, data, type), [chainId, data, type] diff --git a/src/components/Swap/SwapButton/index.tsx b/src/components/Swap/SwapButton/index.tsx index 4dda80537..25a978fc4 100644 --- a/src/components/Swap/SwapButton/index.tsx +++ b/src/components/Swap/SwapButton/index.tsx @@ -1,10 +1,10 @@ import { Trans } from '@lingui/macro' +import { useWeb3React } from '@web3-react/core' import { useSwapInfo } from 'hooks/swap' import { useSwapApprovalOptimizedTrade } from 'hooks/swap/useSwapApproval' import { useSwapCallback } from 'hooks/swap/useSwapCallback' import useWrapCallback, { WrapType } from 'hooks/swap/useWrapCallback' import { useAddTransaction } from 'hooks/transactions' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useSetOldestValidBlock } from 'hooks/useIsValidBlock' import useTransactionDeadline from 'hooks/useTransactionDeadline' import { Spinner } from 'icons' @@ -27,7 +27,7 @@ interface SwapButtonProps { } export default memo(function SwapButton({ disabled }: SwapButtonProps) { - const { account, chainId } = useActiveWeb3React() + const { account, chainId } = useWeb3React() const { [Field.INPUT]: { currency: inputCurrency, diff --git a/src/components/Swap/Toolbar/index.tsx b/src/components/Swap/Toolbar/index.tsx index 58065648a..136ad746a 100644 --- a/src/components/Swap/Toolbar/index.tsx +++ b/src/components/Swap/Toolbar/index.tsx @@ -1,7 +1,7 @@ +import { useWeb3React } from '@web3-react/core' import { ALL_SUPPORTED_CHAIN_IDS } from 'constants/chains' import { useIsAmountPopulated, useSwapInfo } from 'hooks/swap' import useWrapCallback, { WrapType } from 'hooks/swap/useWrapCallback' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import { largeIconCss } from 'icons' import { memo, useMemo } from 'react' import { TradeState } from 'state/routing/types' @@ -18,7 +18,7 @@ const ToolbarRow = styled(Row)` ` export default memo(function Toolbar() { - const { active, activating, chainId } = useActiveWeb3React() + const { account, isActivating, chainId } = useWeb3React() const { [Field.INPUT]: { currency: inputCurrency, balance: inputBalance, amount: inputAmount }, [Field.OUTPUT]: { currency: outputCurrency, usdc: outputUSDC }, @@ -28,8 +28,12 @@ export default memo(function Toolbar() { const isAmountPopulated = useIsAmountPopulated() const { type: wrapType } = useWrapCallback() const caption = useMemo(() => { - if (!active || !chainId) { - if (activating) return + if (state === TradeState.SYNCING || state === TradeState.LOADING) { + return + } + + if (!account || !chainId) { + if (isActivating) return return } @@ -38,9 +42,6 @@ export default memo(function Toolbar() { } if (inputCurrency && outputCurrency && isAmountPopulated) { - if (state === TradeState.SYNCING || state === TradeState.LOADING) { - return - } if (inputBalance && inputAmount?.greaterThan(inputBalance)) { return } @@ -60,19 +61,19 @@ export default memo(function Toolbar() { return }, [ - activating, - active, + account, chainId, - impact, - inputAmount, - inputBalance, inputCurrency, - isAmountPopulated, outputCurrency, - outputUSDC, + isAmountPopulated, + isActivating, state, - trade, + inputBalance, + inputAmount, wrapType, + trade, + outputUSDC, + impact, ]) return ( diff --git a/src/components/Swap/index.tsx b/src/components/Swap/index.tsx index 438751406..ceb848c58 100644 --- a/src/components/Swap/index.tsx +++ b/src/components/Swap/index.tsx @@ -1,20 +1,23 @@ +import { JsonRpcProvider } from '@ethersproject/providers' import { Trans } from '@lingui/macro' +import { useWeb3React } from '@web3-react/core' +import { Provider as Eip1193Provider } from '@web3-react/types' +import Wallet from 'components/ConnectWallet' import { SwapInfoProvider } from 'hooks/swap/useSwapInfo' import useSyncConvenienceFee, { FeeOptions } from 'hooks/swap/useSyncConvenienceFee' import useSyncTokenDefaults, { TokenDefaults } from 'hooks/swap/useSyncTokenDefaults' import { usePendingTransactions } from 'hooks/transactions' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import useHasFocus from 'hooks/useHasFocus' import useOnSupportedNetwork from 'hooks/useOnSupportedNetwork' import { useAtom } from 'jotai' import { useEffect, useState } from 'react' import { displayTxHashAtom, onReviewSwapClickAtom } from 'state/swap' import { SwapTransactionInfo, Transaction, TransactionType, WrapTransactionInfo } from 'state/transactions' +import { onConnectWalletClickAtom } from 'state/wallet' import Dialog from '../Dialog' import Header from '../Header' import { BoundaryProvider } from '../Popover' -import Wallet from '../Wallet' import Input from './Input' import Output from './Output' import ReverseButton from './ReverseButton' @@ -40,9 +43,13 @@ function getTransactionFromMap( return } +// SwapProps also currently includes props needed for wallet connection, since the wallet connection component exists within the Swap component +// TODO(kristiehuang): refactor WalletConnection outside of Swap component export interface SwapProps extends TokenDefaults, FeeOptions { + hideConnectionUI?: boolean + provider?: Eip1193Provider | JsonRpcProvider routerUrl?: string - onConnectWallet?: () => void + onConnectWalletClick?: () => void | Promise onReviewSwapClick?: () => void | Promise } @@ -58,7 +65,14 @@ export default function Swap(props: SwapProps) { } }, [props.onReviewSwapClick, onReviewSwapClick, setOnReviewSwapClick]) - const { active, account } = useActiveWeb3React() + const [onConnectWalletClick, setOnConnectWalletClick] = useAtom(onConnectWalletClickAtom) + useEffect(() => { + if (props.onConnectWalletClick !== onConnectWalletClick) { + setOnConnectWalletClick((old: (() => void | Promise) | undefined) => (old = props.onConnectWalletClick)) + } + }, [props.onConnectWalletClick, onConnectWalletClick, setOnConnectWalletClick]) + + const { isActive } = useWeb3React() const [wrapper, setWrapper] = useState(null) const [displayTxHash, setDisplayTxHash] = useAtom(displayTxHashAtom) @@ -66,14 +80,14 @@ export default function Swap(props: SwapProps) { const displayTx = getTransactionFromMap(pendingTxs, displayTxHash) const onSupportedNetwork = useOnSupportedNetwork() - const isDisabled = !(active && onSupportedNetwork) + const isDisabled = !(isActive && onSupportedNetwork) const focused = useHasFocus(wrapper) return ( <>
Swap}> - +
diff --git a/src/components/TokenSelect/TokenButton.tsx b/src/components/TokenSelect/TokenButton.tsx index 93ae9812b..5ae447499 100644 --- a/src/components/TokenSelect/TokenButton.tsx +++ b/src/components/TokenSelect/TokenButton.tsx @@ -72,6 +72,7 @@ export default function TokenButton({ value, collapsed, disabled, onClick }: Tok style={style} transition={shouldTransition} onTransitionEnd={() => setShouldTransition(false)} + data-testid="token-select" > () useCurrencyBalances(account, tokenList !== prefetchedTokenList.current ? tokenList : undefined) @@ -31,7 +31,7 @@ function usePrefetchBalances() { } function useAreBalancesLoaded(): boolean { - const { account } = useActiveWeb3React() + const { account } = useWeb3React() const tokens = useTokenList() const native = useNativeCurrency() const currencies = useMemo(() => [native, ...tokens], [native, tokens]) @@ -70,7 +70,7 @@ export function TokenSelectDialog({ value, onSelect, onClose }: TokenSelectDialo useEffect(() => input.current?.focus({ preventScroll: true }), [input]) const [options, setOptions] = useState | null>(null) - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const listHasTokens = useMemo(() => list.some((token) => token.chainId === chainId), [chainId, list]) if (!listHasTokens && isLoaded) { diff --git a/src/components/Wallet.tsx b/src/components/Wallet.tsx deleted file mode 100644 index 17ddfd96c..000000000 --- a/src/components/Wallet.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Trans } from '@lingui/macro' -import { Wallet as WalletIcon } from 'icons' -import styled from 'styled-components/macro' -import { ThemedText } from 'theme' - -import { TextButton } from './Button' -import Row from './Row' - -interface WalletProps { - disabled?: boolean - onClick?: () => void -} - -const WalletButton = styled(TextButton)<{ hidden?: boolean }>` - filter: none; - visibility: ${({ hidden }) => hidden && 'hidden'}; -` - -export default function Wallet({ disabled, onClick }: WalletProps) { - return ( - - ) -} diff --git a/src/components/Widget.tsx b/src/components/Widget.tsx index 90e81dc5c..096fde60a 100644 --- a/src/components/Widget.tsx +++ b/src/components/Widget.tsx @@ -1,9 +1,11 @@ import { JsonRpcProvider } from '@ethersproject/providers' import { TokenInfo } from '@uniswap/token-lists' import { Provider as Eip1193Provider } from '@web3-react/types' +import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from 'constants/chains' +import { JSON_RPC_FALLBACK_ENDPOINTS } from 'constants/jsonRpcEndpoints' import { DEFAULT_LOCALE, SUPPORTED_LOCALES, SupportedLocale } from 'constants/locales' +import { ActiveWeb3Provider } from 'hooks/connectWeb3/useWeb3React' import { TransactionsUpdater } from 'hooks/transactions' -import { ActiveWeb3Provider } from 'hooks/useActiveWeb3React' import { BlockNumberProvider } from 'hooks/useBlockNumber' import { TokenListProvider } from 'hooks/useTokenList' import { Provider as I18nProvider } from 'i18n' @@ -19,6 +21,8 @@ import { UNMOUNTING } from 'utils/animations' import { Modal, Provider as DialogProvider } from './Dialog' import ErrorBoundary, { ErrorHandler } from './Error/ErrorBoundary' +const DEFAULT_CHAIN_ID = SupportedChainId.MAINNET + const WidgetWrapper = styled.div<{ width?: number | string }>` -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; @@ -88,7 +92,8 @@ export type WidgetProps = { theme?: Theme locale?: SupportedLocale provider?: Eip1193Provider | JsonRpcProvider - jsonRpcEndpoint?: string | JsonRpcProvider + jsonRpcUrlMap?: { [chainId: number]: string[] } + defaultChainId?: SupportedChainId tokenList?: string | TokenInfo[] width?: string | number dialog?: HTMLElement | null @@ -100,7 +105,7 @@ export type WidgetProps = { } export default function Widget(props: PropsWithChildren) { - const { children, theme, provider, jsonRpcEndpoint, dialog: userDialog, className, onError } = props + const { children, theme, provider, dialog: userDialog, className, onError } = props const width = useMemo(() => { if (props.width && props.width < 300) { console.warn(`Widget width must be at least 300px (you set it to ${props.width}). Falling back to 300px.`) @@ -115,6 +120,27 @@ export default function Widget(props: PropsWithChildren) { } return props.locale ?? DEFAULT_LOCALE }, [props.locale]) + const defaultChainId = useMemo(() => { + if (!props.defaultChainId) return DEFAULT_CHAIN_ID + if (!ALL_SUPPORTED_CHAIN_IDS.includes(props.defaultChainId)) { + console.warn(`Unsupported chainId: ${props.defaultChainId}. Falling back to 1 (Ethereum Mainnet).`) + return DEFAULT_CHAIN_ID + } + return props.defaultChainId + }, [props.defaultChainId]) + const jsonRpcUrlMap: string | JsonRpcProvider | { [chainId: number]: string[] } = useMemo(() => { + if (!props.jsonRpcUrlMap) return JSON_RPC_FALLBACK_ENDPOINTS + for (const supportedChainId of ALL_SUPPORTED_CHAIN_IDS) { + if (!Object.keys(props.jsonRpcUrlMap).includes(`${supportedChainId}`)) { + const fallbackRpc = JSON_RPC_FALLBACK_ENDPOINTS[supportedChainId as number] + console.warn( + `Did not provide a jsonRpcUrlMap for chainId: ${supportedChainId}. Falling back to public RPC endpoint ${fallbackRpc}, which may be unreliable and severly rate-limited.` + ) + props.jsonRpcUrlMap[supportedChainId as number] = fallbackRpc + } + } + return props.jsonRpcUrlMap + }, [props.jsonRpcUrlMap]) const [dialog, setDialog] = useState(null) return ( @@ -127,7 +153,11 @@ export default function Widget(props: PropsWithChildren) { - + setTheme((theme) => ({ ...theme, ...(darkMode ? darkTheme : lightTheme) })), [darkMode, setTheme]) - const jsonRpcEndpoint = useJsonRpcEndpoint() - const connector = useProvider() + const jsonRpcUrlMap = INFURA_NETWORK_URLS + + const defaultNetwork = useOption('defaultChainId', { + options: Object.keys(CHAIN_NAMES_TO_IDS), + defaultValue: 'mainnet', + }) + const defaultChainId = defaultNetwork ? CHAIN_NAMES_TO_IDS[defaultNetwork] : undefined + + const connector = useProvider(defaultChainId) const tokenLists: Record = { Default: tokens, @@ -67,14 +76,21 @@ function Fixture() { defaultInputAmount={defaultInputAmount} defaultOutputTokenAddress={defaultOutputToken} defaultOutputAmount={defaultOutputAmount} + hideConnectionUI={hideConnectionUI} locale={locale} - jsonRpcEndpoint={jsonRpcEndpoint} + jsonRpcUrlMap={jsonRpcUrlMap} + defaultChainId={defaultChainId} provider={connector} theme={theme} tokenList={tokenList} width={width} routerUrl={routerUrl} - onConnectWallet={() => console.log('onConnectWallet')} // this handler is included as a test of functionality, but only logs + onConnectWalletClick={() => + new Promise((resolve) => { + console.log('integrator provided a onConnectWalletClick') + resolve(true) // to open our built-in wallet connect flow + }) + } onReviewSwapClick={() => { return new Promise((resolve) => resolve(true)) }} diff --git a/src/cosmos/useJsonRpcEndpoint.ts b/src/cosmos/useJsonRpcEndpoint.ts deleted file mode 100644 index 331a1dd42..000000000 --- a/src/cosmos/useJsonRpcEndpoint.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { SupportedChainId } from '@uniswap/widgets' - -import useOption, { NONE } from './useOption' - -const INFURA_KEY = process.env.INFURA_KEY -if (INFURA_KEY === undefined) { - console.warn(`INFURA_KEY must be a defined environment variable to use JsonRpcEndpoints in the cosmos viewer`) -} - -export const INFURA_NETWORK_URLS: { [key in SupportedChainId]?: string } = INFURA_KEY - ? { - [SupportedChainId.MAINNET]: `https://mainnet.infura.io/v3/${INFURA_KEY}`, - [SupportedChainId.RINKEBY]: `https://rinkeby.infura.io/v3/${INFURA_KEY}`, - [SupportedChainId.ROPSTEN]: `https://ropsten.infura.io/v3/${INFURA_KEY}`, - [SupportedChainId.GOERLI]: `https://goerli.infura.io/v3/${INFURA_KEY}`, - [SupportedChainId.KOVAN]: `https://kovan.infura.io/v3/${INFURA_KEY}`, - [SupportedChainId.OPTIMISM]: `https://optimism-mainnet.infura.io/v3/${INFURA_KEY}`, - [SupportedChainId.OPTIMISTIC_KOVAN]: `https://optimism-kovan.infura.io/v3/${INFURA_KEY}`, - [SupportedChainId.ARBITRUM_ONE]: `https://arbitrum-mainnet.infura.io/v3/${INFURA_KEY}`, - [SupportedChainId.ARBITRUM_RINKEBY]: `https://arbitrum-rinkeby.infura.io/v3/${INFURA_KEY}`, - [SupportedChainId.POLYGON]: `https://polygon-mainnet.infura.io/v3/${INFURA_KEY}`, - [SupportedChainId.POLYGON_MUMBAI]: `https://polygon-mumbai.infura.io/v3/${INFURA_KEY}`, - } - : {} - -export default function useJsonRpcEndpoint() { - const endpoints = Object.entries(INFURA_NETWORK_URLS).reduce<{ [chainId: string]: string }>( - (acc, [chainId, url]) => ({ - ...acc, - [SupportedChainId[Number(chainId)]]: url, - }), - {} - ) - - return useOption('jsonRpcEndpoint', { - options: endpoints, - defaultValue: INFURA_NETWORK_URLS[SupportedChainId.MAINNET] ? SupportedChainId[SupportedChainId.MAINNET] : NONE, - }) -} diff --git a/src/cosmos/useProvider.ts b/src/cosmos/useProvider.ts index 0e3ce5807..8bf9653b0 100644 --- a/src/cosmos/useProvider.ts +++ b/src/cosmos/useProvider.ts @@ -2,21 +2,48 @@ import { initializeConnector } from '@web3-react/core' import { MetaMask } from '@web3-react/metamask' import { Connector } from '@web3-react/types' import { WalletConnect } from '@web3-react/walletconnect' +import { SupportedChainId } from 'constants/chains' import { useEffect, useState } from 'react' -import { INFURA_NETWORK_URLS } from './useJsonRpcEndpoint' import useOption from './useOption' +const INFURA_KEY = process.env.INFURA_KEY +if (INFURA_KEY === undefined) { + console.error(`INFURA_KEY must be a defined environment variable to use jsonRpcUrlMap in the cosmos viewer`) +} + +export const INFURA_NETWORK_URLS: { [chainId: number]: string[] } = INFURA_KEY + ? { + [SupportedChainId.MAINNET]: [`https://mainnet.infura.io/v3/${INFURA_KEY}`], + [SupportedChainId.RINKEBY]: [`https://rinkeby.infura.io/v3/${INFURA_KEY}`], + [SupportedChainId.ROPSTEN]: [`https://ropsten.infura.io/v3/${INFURA_KEY}`], + [SupportedChainId.GOERLI]: [`https://goerli.infura.io/v3/${INFURA_KEY}`], + [SupportedChainId.KOVAN]: [`https://kovan.infura.io/v3/${INFURA_KEY}`], + [SupportedChainId.OPTIMISM]: [`https://optimism-mainnet.infura.io/v3/${INFURA_KEY}`], + [SupportedChainId.OPTIMISTIC_KOVAN]: [`https://optimism-kovan.infura.io/v3/${INFURA_KEY}`], + [SupportedChainId.ARBITRUM_ONE]: [`https://arbitrum-mainnet.infura.io/v3/${INFURA_KEY}`], + [SupportedChainId.ARBITRUM_RINKEBY]: [`https://arbitrum-rinkeby.infura.io/v3/${INFURA_KEY}`], + [SupportedChainId.POLYGON]: [`https://polygon-mainnet.infura.io/v3/${INFURA_KEY}`], + [SupportedChainId.POLYGON_MUMBAI]: [`https://polygon-mumbai.infura.io/v3/${INFURA_KEY}`], + } + : {} + enum Wallet { MetaMask = 'MetaMask', WalletConnect = 'WalletConenct', } -const [metaMask] = initializeConnector((actions) => new MetaMask(actions)) +const [metaMask] = initializeConnector((actions) => new MetaMask({ actions })) const [walletConnect] = initializeConnector( - (actions) => new WalletConnect(actions, { rpc: INFURA_NETWORK_URLS as { [chainId: number]: string } }) + (actions) => + new WalletConnect({ + actions, + options: { + rpc: INFURA_NETWORK_URLS as { [chainId: number]: string[] }, + }, + }) ) -export default function useProvider() { +export default function useProvider(defaultChainId?: number) { const connectorType = useOption('provider', { options: [Wallet.MetaMask, Wallet.WalletConnect] }) const [connector, setConnector] = useState() useEffect(() => { @@ -27,14 +54,14 @@ export default function useProvider() { } async function activateConnector(connectorType: Wallet | undefined) { - let connector: Connector + let connector: Connector | undefined switch (connectorType) { case Wallet.MetaMask: - await metaMask.activate() + await metaMask.activate(defaultChainId) connector = metaMask break case Wallet.WalletConnect: - await walletConnect.activate() + await walletConnect.activate(defaultChainId) connector = walletConnect } if (!stale) { @@ -44,7 +71,7 @@ export default function useProvider() { }) } } - }, [connectorType]) + }, [connectorType, defaultChainId]) return connector?.provider } diff --git a/src/hooks/connectWeb3/useWeb3React.tsx b/src/hooks/connectWeb3/useWeb3React.tsx new file mode 100644 index 000000000..296ff4f20 --- /dev/null +++ b/src/hooks/connectWeb3/useWeb3React.tsx @@ -0,0 +1,116 @@ +import { JsonRpcProvider } from '@ethersproject/providers' +import { initializeConnector, Web3ReactHooks, Web3ReactProvider } from '@web3-react/core' +import { EIP1193 } from '@web3-react/eip1193' +import { MetaMask } from '@web3-react/metamask' +import { Network } from '@web3-react/network' +import { Connector, Provider as Eip1193Provider, Web3ReactStore } from '@web3-react/types' +import { WalletConnect } from '@web3-react/walletconnect' +import { SupportedChainId } from 'constants/chains' +import { atom, useAtom } from 'jotai' +import { PropsWithChildren, useEffect, useMemo } from 'react' +import JsonRpcConnector from 'utils/JsonRpcConnector' + +export type Web3Connection = [Connector, Web3ReactHooks] +export let connections: Web3Connection[] = [] +export const defaultChainIdAtom = atom(1) + +function toWeb3Connection([connector, hooks]: [ + T, + Web3ReactHooks, + Web3ReactStore +]): Web3Connection { + return [connector, hooks] +} + +export function getConnectorName(connector: Connector) { + if (connector instanceof MetaMask) return 'MetaMask' + if (connector instanceof WalletConnect) return 'WalletConnect' + if (connector instanceof Network) return 'Network' + if (connector instanceof JsonRpcConnector) return 'JsonRpcConnector' + if (connector instanceof EIP1193) return 'EIP1193' + return 'Unknown' +} + +function getConnectionFromProvider(onError: (error: Error) => void, provider?: JsonRpcProvider | Eip1193Provider) { + if (!provider) return + if (JsonRpcProvider.isProvider(provider)) { + return toWeb3Connection(initializeConnector((actions) => new JsonRpcConnector(actions, provider))) + } else if (JsonRpcProvider.isProvider((provider as any).provider)) { + throw new Error('Eip1193Bridge is experimental: pass your ethers Provider directly') + } else { + return toWeb3Connection(initializeConnector((actions) => new EIP1193({ actions, provider, onError }))) + } +} + +function getConnectionFromWalletConnect( + useDefault: boolean, + jsonRpcUrlMap: { [chainId: number]: string[] }, + defaultChainId: SupportedChainId, + onError: (error: Error) => void +) { + return toWeb3Connection( + initializeConnector( + (actions) => + new WalletConnect({ + actions, + options: { + rpc: jsonRpcUrlMap, + qrcode: useDefault, + }, + onError, + defaultChainId, + }) + ) + ) +} + +interface ActiveWeb3ProviderProps { + provider?: Eip1193Provider | JsonRpcProvider + jsonRpcUrlMap: { [chainId: number]: string[] } + defaultChainId: SupportedChainId +} + +export function ActiveWeb3Provider({ + provider, + jsonRpcUrlMap, + defaultChainId: propsDefaultChainId, + children, +}: PropsWithChildren) { + const onError = console.error + const [defaultChainId, setDefaultChainId] = useAtom(defaultChainIdAtom) + useEffect(() => { + if (propsDefaultChainId !== defaultChainId) setDefaultChainId(propsDefaultChainId) + }, [propsDefaultChainId, defaultChainId, setDefaultChainId]) + + const integratorConnection = useMemo(() => getConnectionFromProvider(onError, provider), [onError, provider]) + const metaMaskConnection = useMemo( + () => toWeb3Connection(initializeConnector((actions) => new MetaMask({ actions, onError }))), + [onError] + ) + const walletConnectConnectionQR = useMemo( + () => getConnectionFromWalletConnect(false, jsonRpcUrlMap, defaultChainId, onError), + [jsonRpcUrlMap, defaultChainId, onError] + ) // WC via tile QR code scan + const walletConnectConnectionPopup = useMemo( + () => getConnectionFromWalletConnect(true, jsonRpcUrlMap, defaultChainId, onError), + [jsonRpcUrlMap, defaultChainId, onError] + ) // WC via built-in popup + + const networkConnection = useMemo( + () => + toWeb3Connection( + initializeConnector((actions) => new Network({ actions, urlMap: jsonRpcUrlMap, defaultChainId })) + ), + [jsonRpcUrlMap, defaultChainId] + ) + + connections = [metaMaskConnection, walletConnectConnectionQR, walletConnectConnectionPopup, networkConnection] + if (integratorConnection) connections = [integratorConnection, ...connections] + + const key = `${connections.length}+${Object.entries(jsonRpcUrlMap)}+${propsDefaultChainId}+${defaultChainId}` + return ( + + {children} + + ) +} diff --git a/src/hooks/multicall.ts b/src/hooks/multicall.ts index b2c648986..f2ce869e3 100644 --- a/src/hooks/multicall.ts +++ b/src/hooks/multicall.ts @@ -1,4 +1,4 @@ -import useActiveWeb3React from 'hooks/useActiveWeb3React' +import { useWeb3React } from '@web3-react/core' import useBlockNumber from 'hooks/useBlockNumber' import multicall from 'state/multicall' @@ -37,7 +37,7 @@ export function useSingleContractWithCallData( } function useCallContext() { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const latestBlock = useBlockNumber() return { chainId, latestBlock } } diff --git a/src/hooks/routing/useRouterTrade.ts b/src/hooks/routing/useRouterTrade.ts index af0e66849..a463c2ad5 100644 --- a/src/hooks/routing/useRouterTrade.ts +++ b/src/hooks/routing/useRouterTrade.ts @@ -4,8 +4,8 @@ import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' // Importing just the type, so smart-order-router is lazy-loaded // eslint-disable-next-line no-restricted-imports import type { ChainId } from '@uniswap/smart-order-router' +import { useWeb3React } from '@web3-react/core' import { useRouterArguments } from 'hooks/routing/useRouterArguments' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import useDebounce from 'hooks/useDebounce' import useIsValidBlock from 'hooks/useIsValidBlock' import useIsWindowVisible from 'hooks/useIsWindowVisible' @@ -36,7 +36,7 @@ export function useRouterTrade( state: TradeState trade: InterfaceTrade | undefined } { - const { chainId, library } = useActiveWeb3React() + const { chainId, provider } = useWeb3React() const autoRouterSupported = isAutoRouterSupportedChain(chainId) const isWindowVisible = useIsWindowVisible() // Debounce is used to prevent excessive requests to SOR, as it is data intensive. @@ -67,7 +67,7 @@ export function useRouterTrade( amount: debouncedAmountSpecified, tradeType, routerUrl, - provider: library as JsonRpcProvider, + provider: provider as JsonRpcProvider, }) const { isError, data, currentData } = useGetQuoteQuery(queryArgs ?? skipToken, { diff --git a/src/hooks/swap/useSendSwapTransaction.tsx b/src/hooks/swap/useSendSwapTransaction.tsx index 6fbb14337..9808838a9 100644 --- a/src/hooks/swap/useSendSwapTransaction.tsx +++ b/src/hooks/swap/useSendSwapTransaction.tsx @@ -40,12 +40,12 @@ interface FailedCall extends SwapCallEstimate { export default function useSendSwapTransaction( account: string | null | undefined, chainId: number | undefined, - library: JsonRpcProvider | undefined, + provider: JsonRpcProvider | undefined, trade: AnyTrade | undefined, // trade to execute, required swapCalls: SwapCall[] ): { callback: null | (() => Promise) } { return useMemo(() => { - if (!trade || !library || !account || !chainId) { + if (!trade || !provider || !account || !chainId) { return { callback: null } } return { @@ -64,7 +64,7 @@ export default function useSendSwapTransaction( value, } - return library + return provider .estimateGas(tx) .then((gasEstimate) => { return { @@ -75,7 +75,7 @@ export default function useSendSwapTransaction( .catch((gasError) => { console.debug('Gas estimate failed, trying eth_call to extract error', call) - return library + return provider .call(tx) .then((result) => { console.debug('Unexpected successful call after failed estimate gas', call, gasError, result) @@ -110,7 +110,7 @@ export default function useSendSwapTransaction( call: { address, calldata, value }, } = bestCallOption - return library + return provider .getSigner() .sendTransaction({ from: account, @@ -135,5 +135,5 @@ export default function useSendSwapTransaction( }) }, } - }, [account, chainId, library, swapCalls, trade]) + }, [account, chainId, provider, swapCalls, trade]) } diff --git a/src/hooks/swap/useSwapApproval.ts b/src/hooks/swap/useSwapApproval.ts index db80f382c..11e4fe4ed 100644 --- a/src/hooks/swap/useSwapApproval.ts +++ b/src/hooks/swap/useSwapApproval.ts @@ -2,8 +2,8 @@ import { Protocol, Trade } from '@uniswap/router-sdk' import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core' import { Pair, Route as V2Route, Trade as V2Trade } from '@uniswap/v2-sdk' import { Pool, Route as V3Route, Trade as V3Trade } from '@uniswap/v3-sdk' +import { useWeb3React } from '@web3-react/core' import { SWAP_ROUTER_ADDRESSES, V2_ROUTER_ADDRESS, V3_ROUTER_ADDRESS } from 'constants/addresses' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useERC20PermitFromTrade, UseERC20PermitState } from 'hooks/useERC20Permit' import useTransactionDeadline from 'hooks/useTransactionDeadline' import { useCallback, useMemo } from 'react' @@ -18,7 +18,7 @@ function useSwapApprovalStates( allowedSlippage: Percent, useIsPendingApproval: (token?: Token, spender?: string) => boolean ): { v2: ApprovalState; v3: ApprovalState; v2V3: ApprovalState } { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const amountToApprove = useMemo( () => (trade && trade.inputAmount.currency.isToken ? trade.maximumAmountIn(allowedSlippage) : undefined), @@ -42,7 +42,7 @@ export function useSwapRouterAddress( | Trade | undefined ) { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() return useMemo( () => chainId diff --git a/src/hooks/swap/useSwapCallback.tsx b/src/hooks/swap/useSwapCallback.tsx index 3de227da0..38d88b833 100644 --- a/src/hooks/swap/useSwapCallback.tsx +++ b/src/hooks/swap/useSwapCallback.tsx @@ -4,7 +4,7 @@ import { TransactionResponse } from '@ethersproject/providers' import { Trans } from '@lingui/macro' import { Percent } from '@uniswap/sdk-core' import { FeeOptions } from '@uniswap/v3-sdk' -import useActiveWeb3React from 'hooks/useActiveWeb3React' +import { useWeb3React } from '@web3-react/core' import useENS from 'hooks/useENS' import { SignatureData } from 'hooks/useERC20Permit' import { AnyTrade, useSwapCallArguments } from 'hooks/useSwapCallArguments' @@ -42,7 +42,7 @@ export function useSwapCallback({ deadline, feeOptions, }: UseSwapCallbackArgs): UseSwapCallbackReturns { - const { account, chainId, library } = useActiveWeb3React() + const { account, chainId, provider } = useWeb3React() const swapCalls = useSwapCallArguments( trade, @@ -52,13 +52,13 @@ export function useSwapCallback({ deadline, feeOptions ) - const { callback } = useSendSwapTransaction(account, chainId, library, trade, swapCalls) + const { callback } = useSendSwapTransaction(account, chainId, provider, trade, swapCalls) const { address: recipientAddress } = useENS(recipientAddressOrName) const recipient = recipientAddressOrName === null ? account : recipientAddress return useMemo(() => { - if (!trade || !library || !account || !chainId || !callback) { + if (!trade || !provider || !account || !chainId || !callback) { return { state: SwapCallbackState.INVALID, error: Missing dependencies } } if (!recipient) { @@ -73,5 +73,5 @@ export function useSwapCallback({ state: SwapCallbackState.VALID, callback: async () => callback(), } - }, [trade, library, account, chainId, callback, recipient, recipientAddressOrName]) + }, [trade, provider, account, chainId, callback, recipient, recipientAddressOrName]) } diff --git a/src/hooks/swap/useSwapInfo.tsx b/src/hooks/swap/useSwapInfo.tsx index fadbbc309..0619e7f6f 100644 --- a/src/hooks/swap/useSwapInfo.tsx +++ b/src/hooks/swap/useSwapInfo.tsx @@ -1,6 +1,6 @@ import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' +import { useWeb3React } from '@web3-react/core' import { INVALID_TRADE, useRouterTrade } from 'hooks/routing/useRouterTrade' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useCurrencyBalances } from 'hooks/useCurrencyBalance' import useSlippage, { DEFAULT_SLIPPAGE, Slippage } from 'hooks/useSlippage' import useUSDCPriceImpact, { PriceImpact } from 'hooks/useUSDCPriceImpact' @@ -58,7 +58,7 @@ function useComputeSwapInfo(routerUrl?: string): SwapInfo { [isExactIn, isWrapping, parsedAmount, trade.trade?.outputAmount] ) - const { account } = useActiveWeb3React() + const { account } = useWeb3React() const [balanceIn, balanceOut] = useCurrencyBalances( account, useMemo(() => [currencyIn, currencyOut], [currencyIn, currencyOut]) diff --git a/src/hooks/swap/useSyncConvenienceFee.ts b/src/hooks/swap/useSyncConvenienceFee.ts index 684796f99..3d5238a5e 100644 --- a/src/hooks/swap/useSyncConvenienceFee.ts +++ b/src/hooks/swap/useSyncConvenienceFee.ts @@ -1,5 +1,5 @@ import { Percent } from '@uniswap/sdk-core' -import useActiveWeb3React from 'hooks/useActiveWeb3React' +import { useWeb3React } from '@web3-react/core' import { useUpdateAtom } from 'jotai/utils' import { useEffect } from 'react' import { feeOptionsAtom } from 'state/swap' @@ -10,7 +10,7 @@ export interface FeeOptions { } export default function useSyncConvenienceFee({ convenienceFee, convenienceFeeRecipient }: FeeOptions) { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const updateFeeOptions = useUpdateAtom(feeOptionsAtom) useEffect(() => { diff --git a/src/hooks/swap/useSyncTokenDefaults.ts b/src/hooks/swap/useSyncTokenDefaults.ts index 2191ed749..f0450405f 100644 --- a/src/hooks/swap/useSyncTokenDefaults.ts +++ b/src/hooks/swap/useSyncTokenDefaults.ts @@ -1,6 +1,6 @@ import { Currency } from '@uniswap/sdk-core' +import { useWeb3React } from '@web3-react/core' import { nativeOnChain } from 'constants/tokens' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useToken } from 'hooks/useCurrency' import useNativeCurrency from 'hooks/useNativeCurrency' import { useUpdateAtom } from 'jotai/utils' @@ -47,7 +47,7 @@ export default function useSyncTokenDefaults({ defaultOutputAmount, }: TokenDefaults) { const updateSwap = useUpdateAtom(swapAtom) - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const onSupportedNetwork = useOnSupportedNetwork() const nativeCurrency = useNativeCurrency() const defaultOutputToken = useDefaultToken(defaultOutputTokenAddress, chainId) diff --git a/src/hooks/swap/useWrapCallback.tsx b/src/hooks/swap/useWrapCallback.tsx index d778ae746..16e1b3ca5 100644 --- a/src/hooks/swap/useWrapCallback.tsx +++ b/src/hooks/swap/useWrapCallback.tsx @@ -1,4 +1,5 @@ import { ContractTransaction } from '@ethersproject/contracts' +import { useWeb3React } from '@web3-react/core' import { WRAPPED_NATIVE_CURRENCY } from 'constants/tokens' import { useWETHContract } from 'hooks/useContract' import { useAtomValue } from 'jotai/utils' @@ -6,7 +7,6 @@ import { useMemo } from 'react' import { Field, swapAtom } from 'state/swap' import tryParseCurrencyAmount from 'utils/tryParseCurrencyAmount' -import useActiveWeb3React from '../useActiveWeb3React' import useCurrencyBalance from '../useCurrencyBalance' export enum WrapType { @@ -20,7 +20,7 @@ interface UseWrapCallbackReturns { } export default function useWrapCallback(): UseWrapCallbackReturns { - const { account, chainId } = useActiveWeb3React() + const { account, chainId } = useWeb3React() const wrappedNativeCurrencyContract = useWETHContract() const { amount, [Field.INPUT]: inputCurrency, [Field.OUTPUT]: outputCurrency } = useAtomValue(swapAtom) diff --git a/src/hooks/transactions/index.tsx b/src/hooks/transactions/index.tsx index e041ef910..6f6e0ae98 100644 --- a/src/hooks/transactions/index.tsx +++ b/src/hooks/transactions/index.tsx @@ -1,5 +1,5 @@ import { Token } from '@uniswap/sdk-core' -import useActiveWeb3React from 'hooks/useActiveWeb3React' +import { useWeb3React } from '@web3-react/core' import { useAtomValue, useUpdateAtom } from 'jotai/utils' import ms from 'ms.macro' import { useCallback, useEffect, useRef } from 'react' @@ -14,13 +14,13 @@ function isTransactionRecent(transaction: Transaction) { } export function usePendingTransactions() { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const txs = useAtomValue(transactionsAtom) return (chainId ? txs[chainId] : null) ?? {} } export function useAddTransaction() { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const blockNumber = useBlockNumber() const updateTxs = useUpdateAtom(transactionsAtom) @@ -42,7 +42,7 @@ export function useAddTransaction() { /** Returns the hash of a pending approval transaction, if it exists. */ export function usePendingApproval(token?: Token, spender?: string): string | undefined { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const txs = useAtomValue(transactionsAtom) if (!chainId || !token || !spender) return undefined diff --git a/src/hooks/transactions/updater.tsx b/src/hooks/transactions/updater.tsx index e1f1d0840..b6865649e 100644 --- a/src/hooks/transactions/updater.tsx +++ b/src/hooks/transactions/updater.tsx @@ -1,6 +1,6 @@ import { TransactionReceipt } from '@ethersproject/abstract-provider' +import { useWeb3React } from '@web3-react/core' import { SupportedChainId } from 'constants/chains' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import useBlockNumber, { useFastForwardBlockNumber } from 'hooks/useBlockNumber' import ms from 'ms.macro' import { useCallback, useEffect } from 'react' @@ -45,18 +45,18 @@ interface UpdaterProps { } export default function Updater({ pendingTransactions, onCheck, onReceipt }: UpdaterProps): null { - const { chainId, library } = useActiveWeb3React() + const { chainId, provider } = useWeb3React() const lastBlockNumber = useBlockNumber() const fastForwardBlockNumber = useFastForwardBlockNumber() const getReceipt = useCallback( (hash: string) => { - if (!library || !chainId) throw new Error('No library or chainId') + if (!provider || !chainId) throw new Error('No library or chainId') const retryOptions = RETRY_OPTIONS_BY_CHAIN_ID[chainId] ?? DEFAULT_RETRY_OPTIONS return retry( () => - library.getTransactionReceipt(hash).then((receipt) => { + provider.getTransactionReceipt(hash).then((receipt) => { if (receipt === null) { console.debug(`Retrying tranasaction receipt for ${hash}`) throw new RetryableError() @@ -66,11 +66,11 @@ export default function Updater({ pendingTransactions, onCheck, onReceipt }: Upd retryOptions ) }, - [chainId, library] + [chainId, provider] ) useEffect(() => { - if (!chainId || !library || !lastBlockNumber) return + if (!chainId || !provider || !lastBlockNumber) return const cancels = Object.keys(pendingTransactions) .filter((hash) => shouldCheck(lastBlockNumber, pendingTransactions[hash])) @@ -95,7 +95,7 @@ export default function Updater({ pendingTransactions, onCheck, onReceipt }: Upd return () => { cancels.forEach((cancel) => cancel()) } - }, [chainId, library, lastBlockNumber, getReceipt, fastForwardBlockNumber, onReceipt, onCheck, pendingTransactions]) + }, [chainId, provider, lastBlockNumber, getReceipt, fastForwardBlockNumber, onReceipt, onCheck, pendingTransactions]) return null } diff --git a/src/hooks/useActiveWeb3React.tsx b/src/hooks/useActiveWeb3React.tsx deleted file mode 100644 index e5d6d6d19..000000000 --- a/src/hooks/useActiveWeb3React.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { ExternalProvider, JsonRpcProvider, Web3Provider } from '@ethersproject/providers' -import { initializeConnector, Web3ReactHooks } from '@web3-react/core' -import { EIP1193 } from '@web3-react/eip1193' -import { EMPTY } from '@web3-react/empty' -import { Connector, Provider as Eip1193Provider } from '@web3-react/types' -import { Url } from '@web3-react/url' -import { createContext, PropsWithChildren, useContext, useEffect, useMemo } from 'react' -import JsonRpcConnector from 'utils/JsonRpcConnector' - -type Web3ContextType = { - connector: Connector - library?: (JsonRpcProvider & { provider?: ExternalProvider }) | Web3Provider - chainId?: ReturnType - accounts?: ReturnType - account?: ReturnType - active?: ReturnType - activating?: ReturnType - error?: ReturnType -} - -const [EMPTY_CONNECTOR, EMPTY_HOOKS] = initializeConnector(() => EMPTY) -const EMPTY_STATE = { connector: EMPTY_CONNECTOR, hooks: EMPTY_HOOKS } -const EMPTY_CONTEXT: Web3ContextType = { connector: EMPTY } -const Web3Context = createContext(EMPTY_CONTEXT) - -export default function useActiveWeb3React() { - return useContext(Web3Context) -} - -interface ActiveWeb3ProviderProps { - jsonRpcEndpoint?: string | JsonRpcProvider - provider?: Eip1193Provider | JsonRpcProvider -} - -export function ActiveWeb3Provider({ - jsonRpcEndpoint, - provider, - children, -}: PropsWithChildren) { - const network = useMemo(() => { - if (jsonRpcEndpoint) { - let connector, hooks - if (JsonRpcProvider.isProvider(jsonRpcEndpoint)) { - ;[connector, hooks] = initializeConnector((actions) => new JsonRpcConnector(actions, jsonRpcEndpoint)) - } else { - ;[connector, hooks] = initializeConnector((actions) => new Url(actions, jsonRpcEndpoint)) - } - connector.activate() - return { connector, hooks } - } - return EMPTY_STATE - }, [jsonRpcEndpoint]) - - const wallet = useMemo(() => { - if (provider) { - let connector, hooks - if (JsonRpcProvider.isProvider(provider)) { - ;[connector, hooks] = initializeConnector((actions) => new JsonRpcConnector(actions, provider)) - } else if (JsonRpcProvider.isProvider((provider as any).provider)) { - throw new Error('Eip1193Bridge is experimental: pass your ethers Provider directly') - } else { - ;[connector, hooks] = initializeConnector((actions) => new EIP1193(actions, provider)) - } - connector.activate() - return { connector, hooks } - } - return EMPTY_STATE - }, [provider]) - - const { connector, hooks } = wallet.hooks.useIsActive() || network === EMPTY_STATE ? wallet : network - const accounts = hooks.useAccounts() - const account = hooks.useAccount() - const activating = hooks.useIsActivating() - const active = hooks.useIsActive() - const chainId = hooks.useChainId() - const error = hooks.useError() - const library = hooks.useProvider() - const web3 = useMemo(() => { - if (connector === EMPTY || !(active || activating)) { - return EMPTY_CONTEXT - } - return { connector, library, chainId, accounts, account, active, activating, error } - }, [account, accounts, activating, active, chainId, connector, error, library]) - - // Log web3 errors to facilitate debugging. - useEffect(() => { - if (error) { - console.error('web3 error:', error) - } - }, [error]) - - return {children} -} diff --git a/src/hooks/useAllV3Routes.ts b/src/hooks/useAllV3Routes.ts index 01f109307..641b3b5d1 100644 --- a/src/hooks/useAllV3Routes.ts +++ b/src/hooks/useAllV3Routes.ts @@ -1,6 +1,6 @@ import { Currency } from '@uniswap/sdk-core' import { Pool, Route } from '@uniswap/v3-sdk' -import useActiveWeb3React from 'hooks/useActiveWeb3React' +import { useWeb3React } from '@web3-react/core' import { useMemo } from 'react' import { useV3SwapPools } from './useV3SwapPools' @@ -63,7 +63,7 @@ export function useAllV3Routes( currencyIn?: Currency, currencyOut?: Currency ): { loading: boolean; routes: Route[] } { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const { pools, loading: poolsLoading } = useV3SwapPools(currencyIn, currencyOut) return useMemo(() => { diff --git a/src/hooks/useApproval.ts b/src/hooks/useApproval.ts index 30e3394a6..96eebdb20 100644 --- a/src/hooks/useApproval.ts +++ b/src/hooks/useApproval.ts @@ -1,7 +1,7 @@ import { MaxUint256 } from '@ethersproject/constants' import { TransactionResponse } from '@ethersproject/providers' import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' -import useActiveWeb3React from 'hooks/useActiveWeb3React' +import { useWeb3React } from '@web3-react/core' import { useTokenContract } from 'hooks/useContract' import { useTokenAllowance } from 'hooks/useTokenAllowance' import { useCallback, useMemo } from 'react' @@ -19,7 +19,7 @@ export function useApprovalStateForSpender( spender: string | undefined, useIsPendingApproval: (token?: Token, spender?: string) => boolean ): ApprovalState { - const { account } = useActiveWeb3React() + const { account } = useWeb3React() const token = amountToApprove?.currency?.isToken ? amountToApprove.currency : undefined const currentAllowance = useTokenAllowance(token, account ?? undefined, spender) @@ -48,7 +48,7 @@ export function useApproval( ApprovalState, () => Promise<{ response: TransactionResponse; tokenAddress: string; spenderAddress: string } | undefined> ] { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const token = amountToApprove?.currency?.isToken ? amountToApprove.currency : undefined // check the current approval status diff --git a/src/hooks/useArgentWalletContract.ts b/src/hooks/useArgentWalletContract.ts index 8cd178106..c407b120f 100644 --- a/src/hooks/useArgentWalletContract.ts +++ b/src/hooks/useArgentWalletContract.ts @@ -1,12 +1,12 @@ +import { useWeb3React } from '@web3-react/core' import ArgentWalletContractABI from 'abis/argent-wallet-contract.json' import { ArgentWalletContract } from 'abis/types' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useContract } from './useContract' import useIsArgentWallet from './useIsArgentWallet' export function useArgentWalletContract(): ArgentWalletContract | null { - const { account } = useActiveWeb3React() + const { account } = useWeb3React() const isArgentWallet = useIsArgentWallet() return useContract( isArgentWallet ? account ?? undefined : undefined, diff --git a/src/hooks/useAutoSlippageTolerance.ts b/src/hooks/useAutoSlippageTolerance.ts index 06e5e78ef..3baf3cf1d 100644 --- a/src/hooks/useAutoSlippageTolerance.ts +++ b/src/hooks/useAutoSlippageTolerance.ts @@ -1,8 +1,8 @@ import { Trade } from '@uniswap/router-sdk' import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core' +import { useWeb3React } from '@web3-react/core' import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains' import { L2_CHAIN_IDS } from 'constants/chains' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import useNativeCurrency from 'hooks/useNativeCurrency' import JSBI from 'jsbi' import { useMemo } from 'react' @@ -35,7 +35,7 @@ const MAX_AUTO_SLIPPAGE_TOLERANCE = new Percent(25, 100) // 25% export default function useAutoSlippageTolerance( trade: InterfaceTrade | undefined ): Percent { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const onL2 = chainId && L2_CHAIN_IDS.includes(chainId) const outputDollarValue = useUSDCValue(trade?.outputAmount) const nativeGasPrice = useGasPrice() diff --git a/src/hooks/useBlockNumber.tsx b/src/hooks/useBlockNumber.tsx index 434d7e248..1e0c533a4 100644 --- a/src/hooks/useBlockNumber.tsx +++ b/src/hooks/useBlockNumber.tsx @@ -1,4 +1,4 @@ -import useActiveWeb3React from 'hooks/useActiveWeb3React' +import { useWeb3React } from '@web3-react/core' import useIsWindowVisible from 'hooks/useIsWindowVisible' import { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState } from 'react' @@ -29,7 +29,7 @@ export function useFastForwardBlockNumber(): (block: number) => void { } export function BlockNumberProvider({ children }: { children: ReactNode }) { - const { chainId: activeChainId, library } = useActiveWeb3React() + const { chainId: activeChainId, provider } = useWeb3React() const [{ chainId, block }, setChainBlock] = useState<{ chainId?: number; block?: number }>({ chainId: activeChainId }) const onBlock = useCallback( @@ -48,29 +48,29 @@ export function BlockNumberProvider({ children }: { children: ReactNode }) { const windowVisible = useIsWindowVisible() useEffect(() => { - if (library && activeChainId && windowVisible) { + if (provider && activeChainId && windowVisible) { // If chainId hasn't changed, don't clear the block. This prevents re-fetching still valid data. setChainBlock((chainBlock) => (chainBlock.chainId === activeChainId ? chainBlock : { chainId: activeChainId })) let stale = false - library + provider .getBlockNumber() .then((block) => { if (stale) return onBlock(block) }) - .catch((error) => { + .catch((error: Error) => { console.error(`Failed to get block number for chainId ${activeChainId}`, error) }) - library.on('block', onBlock) + provider.on('block', onBlock) return () => { stale = true - library.off('block', onBlock) + provider.off('block', onBlock) } } return undefined - }, [activeChainId, library, onBlock, setChainBlock, windowVisible]) + }, [activeChainId, provider, onBlock, setChainBlock, windowVisible]) const value = useMemo( () => ({ diff --git a/src/hooks/useContract.ts b/src/hooks/useContract.ts index 7d776c597..586d454e8 100644 --- a/src/hooks/useContract.ts +++ b/src/hooks/useContract.ts @@ -6,6 +6,7 @@ import TickLensJson from '@uniswap/v3-periphery/artifacts/contracts/lens/TickLen import UniswapInterfaceMulticallJson from '@uniswap/v3-periphery/artifacts/contracts/lens/UniswapInterfaceMulticall.sol/UniswapInterfaceMulticall.json' import NonfungiblePositionManagerJson from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json' import V3MigratorJson from '@uniswap/v3-periphery/artifacts/contracts/V3Migrator.sol/V3Migrator.json' +import { useWeb3React } from '@web3-react/core' import ARGENT_WALLET_DETECTOR_ABI from 'abis/argent-wallet-detector.json' import EIP_2612 from 'abis/eip_2612.json' import ENS_PUBLIC_RESOLVER_ABI from 'abis/ens-public-resolver.json' @@ -27,7 +28,6 @@ import { V3_MIGRATOR_ADDRESSES, } from 'constants/addresses' import { WRAPPED_NATIVE_CURRENCY } from 'constants/tokens' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useMemo } from 'react' import { NonfungiblePositionManager, Quoter, TickLens, UniswapInterfaceMulticall } from 'types/v3' import { V3Migrator } from 'types/v3/V3Migrator' @@ -47,21 +47,21 @@ export function useContract( ABI: any, withSignerIfPossible = true ): T | null { - const { library, account, chainId } = useActiveWeb3React() + const { provider, account, chainId } = useWeb3React() return useMemo(() => { - if (!addressOrAddressMap || !ABI || !library || !chainId) return null + if (!addressOrAddressMap || !ABI || !provider || !chainId) return null let address: string | undefined if (typeof addressOrAddressMap === 'string') address = addressOrAddressMap else address = addressOrAddressMap[chainId] if (!address) return null try { - return getContract(address, ABI, library, withSignerIfPossible && account ? account : undefined) + return getContract(address, ABI, provider, withSignerIfPossible && account ? account : undefined) } catch (error) { console.error('Failed to get contract', error) return null } - }, [addressOrAddressMap, ABI, library, chainId, withSignerIfPossible, account]) as T + }, [addressOrAddressMap, ABI, provider, chainId, withSignerIfPossible, account]) as T } export function useV2MigratorContract() { @@ -73,7 +73,7 @@ export function useTokenContract(tokenAddress?: string, withSignerIfPossible?: b } export function useWETHContract(withSignerIfPossible?: boolean) { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() return useContract( chainId ? WRAPPED_NATIVE_CURRENCY[chainId]?.address : undefined, WETH_ABI, @@ -134,7 +134,7 @@ export function useV3Quoter() { } export function useTickLens(): TickLens | null { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const address = chainId ? TICK_LENS_ADDRESSES[chainId] : undefined return useContract(address, TickLensABI) as TickLens | null } diff --git a/src/hooks/useCurrency.ts b/src/hooks/useCurrency.ts index 2012a1dac..7b307eccf 100644 --- a/src/hooks/useCurrency.ts +++ b/src/hooks/useCurrency.ts @@ -1,9 +1,9 @@ import { arrayify } from '@ethersproject/bytes' import { parseBytes32String } from '@ethersproject/strings' import { Currency, Token } from '@uniswap/sdk-core' +import { useWeb3React } from '@web3-react/core' import { TOKEN_SHORTHANDS } from 'constants/tokens' import { NEVER_RELOAD, useSingleCallResult } from 'hooks/multicall' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useBytes32TokenContract, useTokenContract } from 'hooks/useContract' import useNativeCurrency from 'hooks/useNativeCurrency' import { useMemo } from 'react' @@ -30,7 +30,7 @@ function parseStringOrBytes32(str: string | undefined, bytes32: string | undefin * Returns undefined if tokenAddress is invalid or token does not exist. */ export function useTokenFromNetwork(tokenAddress: string | null | undefined): Token | null | undefined { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const formattedAddress = isAddress(tokenAddress) @@ -102,7 +102,7 @@ export function useToken(tokenAddress?: string | null): Token | null | undefined */ export function useCurrencyFromMap(tokens: TokenMap, currencyId?: string | null): Currency | null | undefined { const nativeCurrency = useNativeCurrency() - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const isNative = Boolean(nativeCurrency && currencyId?.toUpperCase() === 'ETH') const shorthandMatchAddress = useMemo(() => { const chain = supportedChainId(chainId) diff --git a/src/hooks/useCurrencyBalance.ts b/src/hooks/useCurrencyBalance.ts index 401f3553c..c06b8a2ce 100644 --- a/src/hooks/useCurrencyBalance.ts +++ b/src/hooks/useCurrencyBalance.ts @@ -1,10 +1,10 @@ import { Interface } from '@ethersproject/abi' import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' +import { useWeb3React } from '@web3-react/core' import ERC20ABI from 'abis/erc20.json' import { Erc20Interface } from 'abis/types/Erc20' import { nativeOnChain } from 'constants/tokens' import { useMultipleContractSingleData, useSingleContractMultipleData } from 'hooks/multicall' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useInterfaceMulticall } from 'hooks/useContract' import JSBI from 'jsbi' import { useMemo } from 'react' @@ -16,7 +16,7 @@ import { isAddress } from 'utils' export function useNativeCurrencyBalances(uncheckedAddresses?: (string | undefined)[]): { [address: string]: CurrencyAmount | undefined } { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const multicallContract = useInterfaceMulticall() const validAddressInputs: [string][] = useMemo( diff --git a/src/hooks/useERC20Permit.ts b/src/hooks/useERC20Permit.ts index 2a7608bcc..459c127f8 100644 --- a/src/hooks/useERC20Permit.ts +++ b/src/hooks/useERC20Permit.ts @@ -4,10 +4,10 @@ import { Trade } from '@uniswap/router-sdk' import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core' import { Trade as V2Trade } from '@uniswap/v2-sdk' import { Trade as V3Trade } from '@uniswap/v3-sdk' +import { useWeb3React } from '@web3-react/core' import { SWAP_ROUTER_ADDRESSES, V3_ROUTER_ADDRESS } from 'constants/addresses' import { DAI, UNI, USDC_MAINNET } from 'constants/tokens' import { useSingleCallResult } from 'hooks/multicall' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import JSBI from 'jsbi' import { useMemo, useState } from 'react' @@ -126,7 +126,7 @@ export function useERC20Permit( state: UseERC20PermitState gatherPermitSignature: null | (() => Promise) } { - const { account, chainId, library } = useActiveWeb3React() + const { account, chainId, provider } = useWeb3React() const tokenAddress = currencyAmount?.currency?.isToken ? currencyAmount.currency.address : undefined const eip2612Contract = useEIP2612Contract(tokenAddress) const isArgentWallet = useIsArgentWallet() @@ -145,7 +145,7 @@ export function useERC20Permit( !account || !chainId || !transactionDeadline || - !library || + !provider || !tokenNonceState.valid || !tokenAddress || !spender || @@ -221,7 +221,7 @@ export function useERC20Permit( message, }) - return library + return provider .send('eth_signTypedData_v4', [account, data]) .then(splitSignature) .then((signature) => { @@ -248,7 +248,7 @@ export function useERC20Permit( chainId, isArgentWallet, transactionDeadline, - library, + provider, tokenNonceState.loading, tokenNonceState.valid, tokenNonceState.result, @@ -268,7 +268,7 @@ export function useERC20PermitFromTrade( allowedSlippage: Percent, transactionDeadline: BigNumber | undefined ) { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const swapRouterAddress = chainId ? // v2 router does not support trade instanceof V2Trade diff --git a/src/hooks/useIsArgentWallet.ts b/src/hooks/useIsArgentWallet.ts index 3c48f1e85..6a12a7ead 100644 --- a/src/hooks/useIsArgentWallet.ts +++ b/src/hooks/useIsArgentWallet.ts @@ -1,11 +1,11 @@ +import { useWeb3React } from '@web3-react/core' import { NEVER_RELOAD, useSingleCallResult } from 'hooks/multicall' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useMemo } from 'react' import { useArgentWalletDetectorContract } from './useContract' export default function useIsArgentWallet(): boolean { - const { account } = useActiveWeb3React() + const { account } = useWeb3React() const argentWalletDetector = useArgentWalletDetectorContract() const inputs = useMemo(() => [account ?? undefined], [account]) const call = useSingleCallResult(argentWalletDetector, 'isArgentWallet', inputs, NEVER_RELOAD) diff --git a/src/hooks/useIsValidBlock.ts b/src/hooks/useIsValidBlock.ts index 1ad259d41..589b2cd70 100644 --- a/src/hooks/useIsValidBlock.ts +++ b/src/hooks/useIsValidBlock.ts @@ -1,8 +1,8 @@ +import { useWeb3React } from '@web3-react/core' import { atomWithImmer } from 'jotai/immer' import { useAtomValue, useUpdateAtom } from 'jotai/utils' import { useCallback } from 'react' -import useActiveWeb3React from './useActiveWeb3React' import useBlockNumber from './useBlockNumber' // The oldest block (per chain) to be considered valid. @@ -11,7 +11,7 @@ const oldestBlockMapAtom = atomWithImmer<{ [chainId: number]: number }>({}) const DEFAULT_MAX_BLOCK_AGE = 10 export function useSetOldestValidBlock(): (block: number) => void { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const updateValidBlock = useUpdateAtom(oldestBlockMapAtom) return useCallback( (block: number) => { @@ -25,7 +25,7 @@ export function useSetOldestValidBlock(): (block: number) => void { } export function useGetIsValidBlock(maxBlockAge = DEFAULT_MAX_BLOCK_AGE): (block: number) => boolean { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const currentBlock = useBlockNumber() const oldestBlockMap = useAtomValue(oldestBlockMapAtom) const oldestBlock = chainId ? oldestBlockMap[chainId] : 0 diff --git a/src/hooks/useNativeCurrency.ts b/src/hooks/useNativeCurrency.ts index 6aee623b7..29c8eb09a 100644 --- a/src/hooks/useNativeCurrency.ts +++ b/src/hooks/useNativeCurrency.ts @@ -1,11 +1,11 @@ import { NativeCurrency } from '@uniswap/sdk-core' +import { useWeb3React } from '@web3-react/core' import { SupportedChainId } from 'constants/chains' import { nativeOnChain } from 'constants/tokens' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useMemo } from 'react' export default function useNativeCurrency(): NativeCurrency { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() return useMemo( () => chainId diff --git a/src/hooks/useOnSupportedNetwork.ts b/src/hooks/useOnSupportedNetwork.ts index 6a5039f7d..aeec47377 100644 --- a/src/hooks/useOnSupportedNetwork.ts +++ b/src/hooks/useOnSupportedNetwork.ts @@ -1,10 +1,9 @@ +import { useWeb3React } from '@web3-react/core' import { ALL_SUPPORTED_CHAIN_IDS } from 'constants/chains' import { useMemo } from 'react' -import useActiveWeb3React from './useActiveWeb3React' - function useOnSupportedNetwork() { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() return useMemo(() => Boolean(chainId && ALL_SUPPORTED_CHAIN_IDS.includes(chainId)), [chainId]) } diff --git a/src/hooks/usePoll.ts b/src/hooks/usePoll.ts index 448fb8e97..f0761cefb 100644 --- a/src/hooks/usePoll.ts +++ b/src/hooks/usePoll.ts @@ -45,7 +45,7 @@ export default function usePoll( if (isStale && entry?.result !== undefined ? isStale(entry.result) : false) { poll() // stale results should be refetched immediately } else if (entry.ttl && entry.ttl + keepUnusedDataFor > Date.now()) { - timeout = setTimeout(poll, Math.max(0, entry.ttl - Date.now())) + timeout = window.setTimeout(poll, Math.max(0, entry.ttl - Date.now())) } } } else { @@ -60,7 +60,7 @@ export default function usePoll( } async function poll(ttl = Date.now() + pollingInterval) { - timeout = setTimeout(poll, pollingInterval) // queue the next poll + timeout = window.setTimeout(poll, pollingInterval) // queue the next poll cache.set(key, { ttl: null, ...cache.get(key) }) // mark the entry as a pending fetch // Always set the result in the cache, but only set it as data if the key is still being queried. diff --git a/src/hooks/usePools.ts b/src/hooks/usePools.ts index 5cfba4992..c3e4f78f3 100644 --- a/src/hooks/usePools.ts +++ b/src/hooks/usePools.ts @@ -3,9 +3,9 @@ import { BigintIsh, Currency, Token } from '@uniswap/sdk-core' import { abi as IUniswapV3PoolStateABI } from '@uniswap/v3-core/artifacts/contracts/interfaces/pool/IUniswapV3PoolState.sol/IUniswapV3PoolState.json' import { computePoolAddress } from '@uniswap/v3-sdk' import { FeeAmount, Pool } from '@uniswap/v3-sdk' +import { useWeb3React } from '@web3-react/core' import { V3_CORE_FACTORY_ADDRESSES } from 'constants/addresses' import { useMultipleContractSingleData } from 'hooks/multicall' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import JSBI from 'jsbi' import { useMemo } from 'react' import { IUniswapV3PoolStateInterface } from 'types/v3/IUniswapV3PoolState' @@ -85,7 +85,7 @@ export enum PoolState { export function usePools( poolKeys: [Currency | undefined, Currency | undefined, FeeAmount | undefined][] ): [PoolState, Pool | null][] { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const poolTokens: ([Token, Token, FeeAmount] | undefined)[] = useMemo(() => { if (!chainId) return new Array(poolKeys.length) diff --git a/src/hooks/useStablecoinAmountFromFiatValue.ts b/src/hooks/useStablecoinAmountFromFiatValue.ts index efe0cb3f9..182d91b2f 100644 --- a/src/hooks/useStablecoinAmountFromFiatValue.ts +++ b/src/hooks/useStablecoinAmountFromFiatValue.ts @@ -1,7 +1,7 @@ import { CurrencyAmount, Token } from '@uniswap/sdk-core' +import { useWeb3React } from '@web3-react/core' import { SupportedChainId } from 'constants/chains' import { DAI_OPTIMISM, USDC_ARBITRUM, USDC_MAINNET, USDC_POLYGON } from 'constants/tokens' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useMemo } from 'react' import tryParseCurrencyAmount from 'utils/tryParseCurrencyAmount' @@ -20,7 +20,7 @@ export const STABLECOIN_AMOUNT_OUT: { [chainId: number]: CurrencyAmount } * @returns CurrencyAmount where currency is stablecoin on active chain */ export function useStablecoinAmountFromFiatValue(fiatValue: string | null | undefined) { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const stablecoin = chainId ? STABLECOIN_AMOUNT_OUT[chainId]?.currency : undefined return useMemo(() => { diff --git a/src/hooks/useSwapCallArguments.tsx b/src/hooks/useSwapCallArguments.tsx index efe045889..ce51bb05e 100644 --- a/src/hooks/useSwapCallArguments.tsx +++ b/src/hooks/useSwapCallArguments.tsx @@ -3,8 +3,8 @@ import { SwapRouter, Trade } from '@uniswap/router-sdk' import { Currency, Percent, TradeType } from '@uniswap/sdk-core' import { Router as V2SwapRouter, Trade as V2Trade } from '@uniswap/v2-sdk' import { FeeOptions, SwapRouter as V3SwapRouter, Trade as V3Trade } from '@uniswap/v3-sdk' +import { useWeb3React } from '@web3-react/core' import { SWAP_ROUTER_ADDRESSES, V3_ROUTER_ADDRESS } from 'constants/addresses' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useMemo } from 'react' import approveAmountCalldata from 'utils/approveAmountCalldata' @@ -39,7 +39,7 @@ export function useSwapCallArguments( deadline: BigNumber | undefined, feeOptions: FeeOptions | undefined ): SwapCall[] { - const { account, chainId, library } = useActiveWeb3React() + const { account, chainId, provider } = useWeb3React() const { address: recipientAddress } = useENS(recipientAddressOrName) const recipient = recipientAddressOrName === null ? account : recipientAddress @@ -47,7 +47,7 @@ export function useSwapCallArguments( const argentWalletContract = useArgentWalletContract() return useMemo(() => { - if (!trade || !recipient || !library || !account || !chainId || !deadline) return [] + if (!trade || !recipient || !provider || !account || !chainId || !deadline) return [] if (trade instanceof V2Trade) { if (!routerContract) return [] @@ -175,7 +175,7 @@ export function useSwapCallArguments( chainId, deadline, feeOptions, - library, + provider, recipient, routerContract, signatureData, diff --git a/src/hooks/useTokenList/index.tsx b/src/hooks/useTokenList/index.tsx index 70a412b8d..deb654885 100644 --- a/src/hooks/useTokenList/index.tsx +++ b/src/hooks/useTokenList/index.tsx @@ -1,6 +1,6 @@ import { Token } from '@uniswap/sdk-core' import { TokenInfo, TokenList } from '@uniswap/token-lists' -import useActiveWeb3React from 'hooks/useActiveWeb3React' +import { useWeb3React } from '@web3-react/core' import { createContext, PropsWithChildren, useCallback, useContext, useEffect, useMemo, useState } from 'react' import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo' import resolveENSContentHash from 'utils/resolveENSContentHash' @@ -29,7 +29,7 @@ export function useIsTokenListLoaded() { } export default function useTokenList(): WrappedTokenInfo[] { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const chainTokenMap = useChainTokenMapContext() const tokenMap = chainId && chainTokenMap?.[chainId] return useMemo(() => { @@ -41,7 +41,7 @@ export default function useTokenList(): WrappedTokenInfo[] { export type TokenMap = { [address: string]: Token } export function useTokenMap(): TokenMap { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const chainTokenMap = useChainTokenMapContext() const tokenMap = chainId && chainTokenMap?.[chainId] return useMemo(() => { @@ -65,15 +65,15 @@ export function TokenListProvider({ useEffect(() => setChainTokenMap(undefined), [list]) - const { chainId, library } = useActiveWeb3React() + const { chainId, provider } = useWeb3React() const resolver = useCallback( (ensName: string) => { - if (library && chainId === 1) { - return resolveENSContentHash(ensName, library) + if (provider && chainId === 1) { + return resolveENSContentHash(ensName, provider) } throw new Error('Could not construct mainnet ENS resolver') }, - [chainId, library] + [chainId, provider] ) useEffect(() => { diff --git a/src/hooks/useTokenList/useQueryTokens.ts b/src/hooks/useTokenList/useQueryTokens.ts index 08375d20c..277261f11 100644 --- a/src/hooks/useTokenList/useQueryTokens.ts +++ b/src/hooks/useTokenList/useQueryTokens.ts @@ -1,5 +1,5 @@ +import { useWeb3React } from '@web3-react/core' import { nativeOnChain } from 'constants/tokens' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useTokenBalances } from 'hooks/useCurrencyBalance' import useDebounce from 'hooks/useDebounce' import { useMemo } from 'react' @@ -9,7 +9,7 @@ import { getTokenFilter } from './filtering' import { tokenComparator, useSortTokensByQuery } from './sorting' export function useQueryTokens(query: string, tokens: WrappedTokenInfo[]) { - const { chainId, account } = useActiveWeb3React() + const { chainId, account } = useWeb3React() const balances = useTokenBalances(account, tokens) const sortedTokens = useMemo( // Create a new array because sort is in-place and returns a referentially equivalent array. diff --git a/src/hooks/useTransactionDeadline.ts b/src/hooks/useTransactionDeadline.ts index 78589672b..ee05d4376 100644 --- a/src/hooks/useTransactionDeadline.ts +++ b/src/hooks/useTransactionDeadline.ts @@ -1,4 +1,5 @@ import { BigNumber } from '@ethersproject/bignumber' +import { useWeb3React } from '@web3-react/core' import { L2_CHAIN_IDS } from 'constants/chains' import { DEFAULT_DEADLINE_FROM_NOW, L2_DEADLINE_FROM_NOW } from 'constants/misc' import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp' @@ -6,11 +7,9 @@ import { useAtom } from 'jotai' import { useMemo } from 'react' import { transactionTtlAtom } from 'state/settings' -import useActiveWeb3React from './useActiveWeb3React' - /** Returns the default transaction TTL for the chain, in minutes. */ export function useDefaultTransactionTtl(): number { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() if (chainId && L2_CHAIN_IDS.includes(chainId)) return L2_DEADLINE_FROM_NOW / 60 return DEFAULT_DEADLINE_FROM_NOW / 60 } diff --git a/src/hooks/useV3SwapPools.ts b/src/hooks/useV3SwapPools.ts index 290e968c7..c7c13ff14 100644 --- a/src/hooks/useV3SwapPools.ts +++ b/src/hooks/useV3SwapPools.ts @@ -1,7 +1,7 @@ import { Currency, Token } from '@uniswap/sdk-core' import { FeeAmount, Pool } from '@uniswap/v3-sdk' +import { useWeb3React } from '@web3-react/core' import { SupportedChainId } from 'constants/chains' -import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useMemo } from 'react' import { useAllCurrencyCombinations } from './useAllCurrencyCombinations' @@ -19,7 +19,7 @@ export function useV3SwapPools( pools: Pool[] loading: boolean } { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const allCurrencyCombinations = useAllCurrencyCombinations(currencyIn, currencyOut) diff --git a/src/icons/identicon.tsx b/src/icons/identicon.tsx new file mode 100644 index 000000000..1975022a4 --- /dev/null +++ b/src/icons/identicon.tsx @@ -0,0 +1,38 @@ +import { useWeb3React } from '@web3-react/core' +import IdenticonGradient0 from 'assets/images/identicons/IdenticonGradient-0.png' +import IdenticonGradient1 from 'assets/images/identicons/IdenticonGradient-1.png' +import IdenticonGradient2 from 'assets/images/identicons/IdenticonGradient-2.png' +import IdenticonGradient3 from 'assets/images/identicons/IdenticonGradient-3.png' +import IdenticonGradient4 from 'assets/images/identicons/IdenticonGradient-4.png' +import IdenticonGradient5 from 'assets/images/identicons/IdenticonGradient-5.png' +import IdenticonGradient6 from 'assets/images/identicons/IdenticonGradient-6.png' +import IdenticonGradient7 from 'assets/images/identicons/IdenticonGradient-7.png' +import IdenticonGradient8 from 'assets/images/identicons/IdenticonGradient-8.png' +import IdenticonGradient9 from 'assets/images/identicons/IdenticonGradient-9.png' +import { useMemo } from 'react' + +const gradients = [ + IdenticonGradient0, + IdenticonGradient1, + IdenticonGradient2, + IdenticonGradient3, + IdenticonGradient4, + IdenticonGradient5, + IdenticonGradient6, + IdenticonGradient7, + IdenticonGradient8, + IdenticonGradient9, +] + +function getGradientIconSrc(account: string) { + const num = parseInt(account.slice(2, 10), 16) + const i = num % 10 + return gradients[i] +} + +export default function IdenticonIcon() { + const { account } = useWeb3React() + const iconSrc = useMemo(() => account && getGradientIconSrc(account), [account]) + + return account icon +} diff --git a/src/icons/index.tsx b/src/icons/index.tsx index 698d7f433..0d67b641a 100644 --- a/src/icons/index.tsx +++ b/src/icons/index.tsx @@ -5,6 +5,7 @@ import { ReactComponent as InlineSpinnerIcon } from 'assets/svg/inline_spinner.s import { ReactComponent as LogoIcon } from 'assets/svg/logo.svg' import { ReactComponent as SpinnerIcon } from 'assets/svg/spinner.svg' import { ReactComponent as WalletIcon } from 'assets/svg/wallet.svg' +import { ReactComponent as WalletDisconnectIcon } from 'assets/svg/wallet_disconnect.svg' import { loadingCss } from 'css/loading' import { FunctionComponent, SVGProps } from 'react' /* eslint-disable no-restricted-imports */ @@ -30,6 +31,9 @@ import { } from 'react-feather' import styled, { css, keyframes } from 'styled-components/macro' import { Color } from 'theme' + +import IdenticonIcon from './identicon' + /* eslint-enable no-restricted-imports */ type SVGIcon = FunctionComponent> @@ -86,6 +90,7 @@ export const ChevronLeft = icon(ChevronLeftIcon) export const ChevronRight = icon(ChevronRightIcon) export const Clock = icon(ClockIcon) export const HelpCircle = icon(HelpCircleIcon) +export const Identicon = icon(IdenticonIcon) export const Info = icon(InfoIcon) export const Link = icon(LinkIcon) export const AutoRouter = icon(RouterIcon) @@ -124,6 +129,11 @@ export const Logo = styled(icon(LogoIcon))` stroke: none; ` +export const WalletDisconnect = styled(icon(WalletDisconnectIcon))<{ color?: Color }>` + fill: currentColor; + stroke: none; +` + const rotate = keyframes` from { transform: rotate(0deg); diff --git a/src/index.tsx b/src/index.tsx index b7585d1e3..6d7acc495 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,3 +1,5 @@ +import 'polyfills' + import Swap, { SwapProps } from 'components/Swap' import Widget, { WidgetProps } from 'components/Widget' export type { Provider as EthersProvider } from '@ethersproject/abstract-provider' diff --git a/src/polyfills.ts b/src/polyfills.ts new file mode 100644 index 000000000..25d9a8d80 --- /dev/null +++ b/src/polyfills.ts @@ -0,0 +1,6 @@ +import { Buffer } from 'buffer' + +// WalletConnect relies on Buffer, so it must be polyfilled. +if (!('Buffer' in window)) { + window.Buffer = Buffer +} diff --git a/src/state/multicall.tsx b/src/state/multicall.tsx index 98bee0b68..c6c60436b 100644 --- a/src/state/multicall.tsx +++ b/src/state/multicall.tsx @@ -1,5 +1,5 @@ import { createMulticall } from '@uniswap/redux-multicall' -import useActiveWeb3React from 'hooks/useActiveWeb3React' +import { useWeb3React } from '@web3-react/core' import useBlockNumber from 'hooks/useBlockNumber' import { useInterfaceMulticall } from 'hooks/useContract' @@ -8,7 +8,7 @@ const multicall = createMulticall() export default multicall export function MulticallUpdater() { - const { chainId } = useActiveWeb3React() + const { chainId } = useWeb3React() const latestBlockNumber = useBlockNumber() const contract = useInterfaceMulticall() return diff --git a/src/state/wallet.ts b/src/state/wallet.ts new file mode 100644 index 000000000..eca80b501 --- /dev/null +++ b/src/state/wallet.ts @@ -0,0 +1,4 @@ +import { atom } from 'jotai' + +// If set, allows integrator to add behavior when 'Connect wallet to swap' button is clicked +export const onConnectWalletClickAtom = atom<(() => void | Promise) | undefined>(undefined) diff --git a/src/utils/JsonRpcConnector.ts b/src/utils/JsonRpcConnector.ts index 2826d8734..7cb2161c5 100644 --- a/src/utils/JsonRpcConnector.ts +++ b/src/utils/JsonRpcConnector.ts @@ -13,7 +13,8 @@ export default class JsonRpcConnector extends Connector { this.actions.update({ chainId: parseChainId(chainId) }) }) .on('disconnect', (error: ProviderRpcError): void => { - this.actions.reportError(error) + this.onError?.(error) + this.actions.resetState() }) .on('chainChanged', (chainId: string): void => { this.actions.update({ chainId: parseChainId(chainId) }) @@ -33,7 +34,8 @@ export default class JsonRpcConnector extends Connector { ]) this.actions.update({ chainId, accounts }) } catch (e) { - this.actions.reportError(e) + this.actions.resetState() + throw e } } } diff --git a/src/utils/index.ts b/src/utils/index.ts index 7d3e12025..04376858f 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -25,22 +25,22 @@ export function shortenAddress(address: string, chars = 4): string { } // account is not optional -function getSigner(library: JsonRpcProvider, account: string): JsonRpcSigner { - return library.getSigner(account).connectUnchecked() +function getSigner(provider: JsonRpcProvider, account: string): JsonRpcSigner { + return provider.getSigner(account).connectUnchecked() } // account is optional -function getProviderOrSigner(library: JsonRpcProvider, account?: string): JsonRpcProvider | JsonRpcSigner { - return account ? getSigner(library, account) : library +function getProviderOrSigner(provider: JsonRpcProvider, account?: string): JsonRpcProvider | JsonRpcSigner { + return account ? getSigner(provider, account) : provider } // account is optional -export function getContract(address: string, ABI: any, library: JsonRpcProvider, account?: string): Contract { +export function getContract(address: string, ABI: any, provider: JsonRpcProvider, account?: string): Contract { if (!isAddress(address) || address === AddressZero) { throw Error(`Invalid 'address' parameter '${address}'.`) } - return new Contract(address, ABI, getProviderOrSigner(library, account) as any) + return new Contract(address, ABI, getProviderOrSigner(provider, account) as any) } export function escapeRegExp(string: string): string { diff --git a/yarn.lock b/yarn.lock index 17ce0add4..d637fdd93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3309,10 +3309,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== -"@types/node@^13.13.5": - version "13.13.52" - resolved "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz" - integrity sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ== +"@types/node@^18.6.3": + version "18.6.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.6.3.tgz#4e4a95b6fe44014563ceb514b2598b3e623d1c98" + integrity sha512-6qKpDtoaYLM+5+AFChLhHermMQxc3TOEFIDzrZLPRGHPrLEwqFkkT5Kx3ju05g6X7uDPazz3jHbKPX0KzCjntg== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -3341,6 +3341,13 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== +"@types/qrcode@^1.4.2": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.4.2.tgz#7d7142d6fa9921f195db342ed08b539181546c74" + integrity sha512-7uNT9L4WQTNJejHTSTdaJhfBSCN73xtXaHFyBJ8TSwiLhe4PRuTue7Iph0s2nG9R/ifUaSnGhLUOZavlBEqDWQ== + dependencies: + "@types/node" "*" + "@types/qs@^6.9.2": version "6.9.7" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" @@ -4044,69 +4051,77 @@ dependencies: "@walletconnect/window-getters" "^1.0.0" -"@web3-react/core@8.0.30-beta.0": - version "8.0.30-beta.0" - resolved "https://registry.yarnpkg.com/@web3-react/core/-/core-8.0.30-beta.0.tgz#744c8a47a59eb75ff8f3e407c2190a90762a9905" - integrity sha512-Z6PTxs1xi7kO8fh/pjz29aY1rGyM6Bvoy909sSdVl2BRBMvo+XsezF6JEsAi/XbMrmtOEqzmaTSuokU0KusePw== +"@web3-react/core@8.0.35-beta.0": + version "8.0.35-beta.0" + resolved "https://registry.yarnpkg.com/@web3-react/core/-/core-8.0.35-beta.0.tgz#8667483bdbc283fc8377d7f56faba1ec2fcdb095" + integrity sha512-vkEL2Vafu57lTA9T/cd3DNkZoDZ3G/JDUgxgjHqKLQVF4bPucrkeErqIHutAJ4suIi4bLOD0dFPMpFs+Bq7RgA== dependencies: - "@web3-react/store" "^8.0.22-beta.0" - "@web3-react/types" "^8.0.17-beta.0" + "@web3-react/store" "^8.0.25-beta.0" + "@web3-react/types" "^8.0.20-beta.0" zustand "^4.0.0-rc.0" optionalDependencies: "@ethersproject/providers" "^5" -"@web3-react/eip1193@8.0.23-beta.0": - version "8.0.23-beta.0" - resolved "https://registry.yarnpkg.com/@web3-react/eip1193/-/eip1193-8.0.23-beta.0.tgz#5a38665f4a81b972b2f2ca2f70de99067551dbe8" - integrity sha512-q8jGkMo3HKcuptYw+IdEDECbSUnt4ueuk1lP4nRi4dA0vKUaeX5vjTstjs6uGD97JC1CN3jN7qtEHB+jXfGyvg== +"@web3-react/eip1193@8.0.26-beta.0": + version "8.0.26-beta.0" + resolved "https://registry.yarnpkg.com/@web3-react/eip1193/-/eip1193-8.0.26-beta.0.tgz#2e36423bab637a1fbc57b36d4f0f38ac531fd541" + integrity sha512-n/2ajjABcP8DktZfXxSHrxtPIxeSRJ9tsgfmd9XPEIW9FO7xLzLrF+nloTSghtZ8H+ZKKcswlKjYq4rbX/oqHg== dependencies: - "@web3-react/types" "^8.0.17-beta.0" + "@web3-react/types" "^8.0.20-beta.0" -"@web3-react/empty@8.0.17-beta.0": - version "8.0.17-beta.0" - resolved "https://registry.yarnpkg.com/@web3-react/empty/-/empty-8.0.17-beta.0.tgz#992a7cf4baa58bd9094536dffcd62ac14f57b305" - integrity sha512-1aKYWE7E5EUVol7M1G5kEiiVXG0e+9g4ehFk8O/3zKsjEWRBIP6xwT12Q/9H6AwrDLc2hNJmKe0BW7F8mDd6KQ== +"@web3-react/empty@8.0.20-beta.0": + version "8.0.20-beta.0" + resolved "https://registry.yarnpkg.com/@web3-react/empty/-/empty-8.0.20-beta.0.tgz#f8e2a6414ba49c7da3937776c213eb4c8ff6e2c7" + integrity sha512-hde1Wq7w03cal6hD0E+seVg6ZFMWKcaSZ0S5UwJi5CDxhF79oL8QuzpvTBslohRkWqzqlvl52wCSVaY+l2+F8Q== dependencies: - "@web3-react/types" "^8.0.17-beta.0" + "@web3-react/types" "^8.0.20-beta.0" -"@web3-react/metamask@8.0.24-beta.0": - version "8.0.24-beta.0" - resolved "https://registry.yarnpkg.com/@web3-react/metamask/-/metamask-8.0.24-beta.0.tgz#047bfe321f4064bd3dd0443c06d2497373e6d511" - integrity sha512-kCzWkGiha/7dn73DKB6LkSAsp+j6SdVnBg7Dp9EyInes2npwcIVl7PABQMop0g+7Drv4bpiJBwMIetcVHkr9sQ== +"@web3-react/metamask@8.0.28-beta.0": + version "8.0.28-beta.0" + resolved "https://registry.yarnpkg.com/@web3-react/metamask/-/metamask-8.0.28-beta.0.tgz#f7e9e0de446727a961745cbec75c8cbf6c961388" + integrity sha512-IXuVyj6vhRAhfGQ/sN4qgET8EEdqX844pB4kCDG9kjPD3LLm5kq47ykBosgTr9YCUtdHAXN0UaUimD0TbLMRFg== dependencies: "@metamask/detect-provider" "^1.2.0" - "@web3-react/types" "^8.0.17-beta.0" + "@web3-react/types" "^8.0.20-beta.0" -"@web3-react/store@^8.0.22-beta.0": - version "8.0.22-beta.0" - resolved "https://registry.yarnpkg.com/@web3-react/store/-/store-8.0.22-beta.0.tgz#c4a56edb5d89f14bd6b1d53910a793c2bf854fb9" - integrity sha512-8POKnRntMA+8aUCGwMcPV32PM9A+CGEu1Eha7PKU5uaMrNMwf6eIufpm0pa9FxDZtk5n8lFCfGed8DApwiUvjw== +"@web3-react/network@8.0.27-beta.0": + version "8.0.27-beta.0" + resolved "https://registry.yarnpkg.com/@web3-react/network/-/network-8.0.27-beta.0.tgz#7cb522b02efc9d0f877ac285f350810fbf322292" + integrity sha512-kLHilUpLkDejx0C5Rr57puQSEVA+BQmT58xN6D/elphcZpVHAIkbh/MCYm0XrnLmqq0uOjw+jDhEYBBn80ncHQ== + dependencies: + "@ethersproject/providers" "^5" + "@web3-react/types" "^8.0.20-beta.0" + +"@web3-react/store@^8.0.25-beta.0": + version "8.0.25-beta.0" + resolved "https://registry.yarnpkg.com/@web3-react/store/-/store-8.0.25-beta.0.tgz#853a029a9f82d8950e306adb455ba308f908c8f4" + integrity sha512-YHrZ42EHiQ9UAJq8Y/pHsyXu6GhzbCSHaQJ9U0wCDhbPoQ9QjOj0Pwcaa/VLXljuynK/7ERP942nI1IzArt9Sg== dependencies: "@ethersproject/address" "^5" - "@web3-react/types" "^8.0.17-beta.0" + "@web3-react/types" "^8.0.20-beta.0" zustand "^4.0.0-rc.0" -"@web3-react/types@8.0.17-beta.0", "@web3-react/types@^8.0.17-beta.0": - version "8.0.17-beta.0" - resolved "https://registry.yarnpkg.com/@web3-react/types/-/types-8.0.17-beta.0.tgz#a7a2898065a67a0e574572f49a73d39879e75676" - integrity sha512-5zh+BrykylyN29P18/rNPFVVOI4Go/jYLwl5a/55eb5X1wgCfPU62ZBiVeKIOWBCwJ558uie31p909xJjeE2ig== +"@web3-react/types@8.0.20-beta.0", "@web3-react/types@^8.0.20-beta.0": + version "8.0.20-beta.0" + resolved "https://registry.yarnpkg.com/@web3-react/types/-/types-8.0.20-beta.0.tgz#6b4509bef8c5c7eb866e49295880c865c20fb565" + integrity sha512-qOZYMyUmsm3Um6t6Pg3OgnE86ufhWZpB5/VxsooB8cdpXc/C/f8KMyYSeM63GoKSMScOKwfqV6yODFL7g/Qc8g== dependencies: zustand "^4.0.0-rc.0" -"@web3-react/url@8.0.22-beta.0": - version "8.0.22-beta.0" - resolved "https://registry.yarnpkg.com/@web3-react/url/-/url-8.0.22-beta.0.tgz#721425fe9271fa5bcafe697233f060eb47bb82bb" - integrity sha512-ru8KybaxmqGQWacbsU1K2v3hgW0CWXBbn0hSkOCMMtl16h7hdVQ3lFuuzBXjErWH5bZj3/AuzAOb468y4/9yLw== +"@web3-react/url@8.0.25-beta.0": + version "8.0.25-beta.0" + resolved "https://registry.yarnpkg.com/@web3-react/url/-/url-8.0.25-beta.0.tgz#68e464d2e78b89496e50e9e4a28e91281c4d53d6" + integrity sha512-cjFfAFjsWF5vqJ7TG79HT72jNNWlcS9bqbNK2jvu25zej62zMpPvy4iyYiV7zy2SLbAQTdsgvIMYAdxRbnzlWg== dependencies: "@ethersproject/providers" "^5" - "@web3-react/types" "^8.0.17-beta.0" + "@web3-react/types" "^8.0.20-beta.0" -"@web3-react/walletconnect@8.0.31-beta.0": - version "8.0.31-beta.0" - resolved "https://registry.yarnpkg.com/@web3-react/walletconnect/-/walletconnect-8.0.31-beta.0.tgz#d2fa2b998364a9390ddeba954f200b130d63e84b" - integrity sha512-j3Mdf4t3xSjs1JreLH9GQInS2zvLj5zMn9bwrq1xvgdx0ETAyh00gNTB+WoYKtjWBHmoXxTQ7YXwKfB6C/5Dsw== +"@web3-react/walletconnect@8.0.35-beta.0": + version "8.0.35-beta.0" + resolved "https://registry.yarnpkg.com/@web3-react/walletconnect/-/walletconnect-8.0.35-beta.0.tgz#49c6c77447d58bfb295f28fa87c8fbfeec95cff5" + integrity sha512-fUrqcnwAr5oecZ6VUE/7+RSVURrohbAgWMLKYxd8Zo47AtTPzgJ1t5Lydh/EX4xJPLhfK1LqX5YgMwiys3DvhQ== dependencies: - "@web3-react/types" "^8.0.17-beta.0" + "@web3-react/types" "^8.0.20-beta.0" eventemitter3 "^4.0.7" "@webassemblyjs/ast@1.11.1": @@ -5505,6 +5520,15 @@ cliui@^5.0.0: strip-ansi "^5.2.0" wrap-ansi "^5.1.0" +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -6383,6 +6407,11 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== +encode-utf8@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" + integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -11103,6 +11132,11 @@ pngjs@^3.0.0, pngjs@^3.3.0, pngjs@^3.3.3: resolved "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz" integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== +pngjs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" + integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== + pofile@^1.1.0: version "1.1.1" resolved "https://registry.npmjs.org/pofile/-/pofile-1.1.1.tgz" @@ -11328,6 +11362,16 @@ qrcode@1.4.4: pngjs "^3.3.0" yargs "^13.2.4" +qrcode@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.0.tgz#95abb8a91fdafd86f8190f2836abbfc500c72d1b" + integrity sha512-9MgRpgVc+/+47dFvQeD6U2s0Z92EsKzcHogtum4QB+UNd025WOJSHvn/hjk9xmzj7Stj95CyUAs31mrjxliEsQ== + dependencies: + dijkstrajs "^1.0.1" + encode-utf8 "^1.0.3" + pngjs "^5.0.0" + yargs "^15.3.1" + qs@6.7.0: version "6.7.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" @@ -13674,6 +13718,15 @@ wrap-ansi@^5.1.0: string-width "^3.0.0" strip-ansi "^5.0.0" +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -13814,6 +13867,14 @@ yargs-parser@^13.1.2: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" @@ -13858,6 +13919,23 @@ yargs@^13.2.4: y18n "^4.0.0" yargs-parser "^13.1.2" +yargs@^15.3.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + yeast@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"