Skip to content

Commit

Permalink
Add WalletConnect integration
Browse files Browse the repository at this point in the history
  • Loading branch information
lubej committed Jan 10, 2024
1 parent f4c80a6 commit 60a3b8e
Show file tree
Hide file tree
Showing 21 changed files with 2,741 additions and 328 deletions.
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_WALLET_CONNECT_PROJECT_ID=92fd8c36de7a5e1c7b4f355e9f10268c
1 change: 1 addition & 0 deletions .env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_WALLET_CONNECT_PROJECT_ID=92fd8c36de7a5e1c7b4f355e9f10268c
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@metamask/detect-provider": "^2.0.0",
"@metamask/jazzicon": "^2.0.0",
"@oasisprotocol/sapphire-paratime": "^1.1.4",
"@web3modal/ethers5": "^3.5.5",
"buffer": "^6.0.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
57 changes: 52 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,39 @@ import { Web3ContextProvider } from './providers/Web3Provider'
import { ConnectWallet } from './pages/ConnectWallet'
import { WrapFormContextProvider } from './providers/WrapFormProvider'
import { Transaction } from './pages/Transaction'
import { WalletConnectContextProvider } from './providers/WalletConnectProvider'
import { createWeb3Modal, defaultConfig, useWeb3ModalTheme } from '@web3modal/ethers5/react'
import {
NETWORKS,
WALLET_CONNECT_METADATA,
WALLET_CONNECT_PROJECT_ID,
WALLET_CONNECT_SAPPHIRE_CHAIN,
WALLET_CONNECT_SAPPHIRE_TESTNET_CHAIN,
} from './constants/config'

const { chainId: sapphireChainId } = WALLET_CONNECT_SAPPHIRE_CHAIN
const { chainId: sapphireTestnetChainId } = WALLET_CONNECT_SAPPHIRE_TESTNET_CHAIN

// createWeb3Modal should be called outside any React component to avoid unwanted re-renders
createWeb3Modal({
ethersConfig: defaultConfig({
metadata: WALLET_CONNECT_METADATA,
defaultChainId: sapphireChainId,
enableEIP6963: true,
enableCoinbase: false,
enableInjected: false,
}),
chains: [WALLET_CONNECT_SAPPHIRE_CHAIN, WALLET_CONNECT_SAPPHIRE_TESTNET_CHAIN],
projectId: WALLET_CONNECT_PROJECT_ID,
tokens: {
[sapphireChainId]: {
address: NETWORKS[sapphireChainId].wRoseContractAddress,
},
[sapphireTestnetChainId]: {
address: NETWORKS[sapphireTestnetChainId].wRoseContractAddress,
},
},
})

const router = createHashRouter([
{
Expand All @@ -32,8 +65,22 @@ const router = createHashRouter([
},
])

export const App: FC = () => (
<Web3ContextProvider>
<RouterProvider router={router} />
</Web3ContextProvider>
)
export const App: FC = () => {
const { setThemeMode, setThemeVariables } = useWeb3ModalTheme()

setThemeMode('light')

setThemeVariables({
'--w3m-color-mix': '#3333c4',
'--w3m-color-mix-strength': 15,
'--w3m-accent': '#0092f6',
})

return (
<WalletConnectContextProvider>
<Web3ContextProvider>
<RouterProvider router={router} />
</Web3ContextProvider>
</WalletConnectContextProvider>
)
}
7 changes: 6 additions & 1 deletion src/components/Account/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@
border-radius: 12px;
background: var(--white);
padding: 0.7125rem;
text-decoration: none;
cursor: pointer;
}

.accountLink {
width: 100%;
display: flex;
text-decoration: none;
}

.accountDetails {
display: inline-flex;
flex-direction: row-reverse;
Expand Down
33 changes: 23 additions & 10 deletions src/components/Account/index.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,45 @@
import classes from './index.module.css'
import { FC } from 'react'
import { FC, MouseEventHandler } from 'react'
import { StringUtils } from '../../utils/string.utils'
import { JazzIcon } from '../JazzIcon'
import { useMediaQuery } from 'react-responsive'
import { useWeb3 } from '../../hooks/useWeb3'
import { useWalletConnect } from '../../hooks/useWalletConnect'

interface Props {
address: string
networkName: string
}

export const Account: FC<Props> = ({ address, networkName }) => {
const { switchAccount, switchNetwork } = useWalletConnect()
const isXlScreen = useMediaQuery({ query: '(min-width: 1000px)' })
const {
state: { explorerBaseUrl },
} = useWeb3()

const handleSwitchNetwork: MouseEventHandler = e => {
e.preventDefault()
e.stopPropagation()

switchNetwork()
}

const url = explorerBaseUrl ? StringUtils.getAccountUrl(explorerBaseUrl, address) : '#'

return (
<a href={url} className={classes.account} target="_blank" rel="nofollow noreferrer">
<JazzIcon size={isXlScreen ? 60 : 30} address={address} />
<p className={classes.accountDetails}>
<abbr title={address} className={classes.accountAddress}>
{StringUtils.truncateAddress(address)}
</abbr>
<span className={classes.network}>{networkName}</span>
</p>
</a>
<div className={classes.account}>
<JazzIcon onClick={switchAccount} size={isXlScreen ? 60 : 30} address={address} />
<a className={classes.accountLink} href={url} target="_blank" rel="nofollow noreferrer">
<p className={classes.accountDetails}>
<abbr title={address} className={classes.accountAddress}>
{StringUtils.truncateAddress(address)}
</abbr>
<span onClick={handleSwitchNetwork} className={classes.network}>
{networkName}
</span>
</p>
</a>
</div>
)
}
9 changes: 6 additions & 3 deletions src/components/JazzIcon/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import createJazzIcon from '@metamask/jazzicon'
import classes from './index.module.css'
import { FC, memo, useEffect, useRef } from 'react'
import { FC, memo, MouseEventHandler, useEffect, useRef } from 'react'
import { NumberUtils } from '../../utils/number.utils'

interface JazzIconProps {
address: string
size: number
onClick?: MouseEventHandler
}

const JazzIconCmp: FC<JazzIconProps> = ({ address, size }) => {
const JazzIconCmp: FC<JazzIconProps> = ({ address, size, onClick }) => {
const ref = useRef<HTMLDivElement | null>(null)

useEffect(() => {
Expand All @@ -20,7 +21,9 @@ const JazzIconCmp: FC<JazzIconProps> = ({ address, size }) => {
}
}, [size, ref, address])

return <div ref={ref} style={{ width: size, height: size }} className={classes.jazzIcon} />
return (
<div ref={ref} onClick={onClick} style={{ width: size, height: size }} className={classes.jazzIcon} />
)
}

export const JazzIcon = memo(JazzIconCmp)
8 changes: 6 additions & 2 deletions src/components/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import classes from './index.module.css'
import { Account } from '../Account'
import { LogoIcon } from '../icons/LogoIcon'
import { useWeb3 } from '../../hooks/useWeb3'
import { useWalletConnect } from '../../hooks/useWalletConnect'

export const Layout: FC<PropsWithChildren> = () => {
const {
state: { isConnected, account, networkName },
state: { isConnected, address },
} = useWalletConnect()
const {
state: { networkName },
} = useWeb3()
const { pathname } = useLocation()
const navigate = useNavigate()
Expand All @@ -29,7 +33,7 @@ export const Layout: FC<PropsWithChildren> = () => {

return (
<main className={classes.layout}>
{isConnected && account && <Account address={account} networkName={networkName ?? ''} />}
{isConnected && address && <Account address={address} networkName={networkName ?? ''} />}
<h2 className={classes.header}>
ROSE <LogoIcon /> wrapper
</h2>
Expand Down
25 changes: 24 additions & 1 deletion src/constants/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,27 @@ export const NETWORKS: Record<number, NetworkConfiguration> = {

export const MAX_GAS_LIMIT = 100000

export const METAMASK_HOME_PAGE = 'https://metamask.io/'
export const WALLET_CONNECT_PROJECT_ID = import.meta.env.VITE_WALLET_CONNECT_PROJECT_ID

export const WALLET_CONNECT_METADATA = {
name: 'ROSE (un)wrapper',
description:
'Quickly wrap your ROSE into wROSE and vice versa with the (un)wrap ROSE tool.',
url: 'http://wrose.oasis.io/',
icons: ['https://wrose.oasis.io/icon.svg'],
}
export const WALLET_CONNECT_SAPPHIRE_CHAIN = {
chainId: 23294,
name: 'Sapphire',
currency: 'ROSE',
explorerUrl: 'https://explorer.oasis.io/mainnet/sapphire',
rpcUrl: 'https://sapphire.oasis.io',
}

export const WALLET_CONNECT_SAPPHIRE_TESTNET_CHAIN = {
chainId: 23295,
name: 'Sapphire Testnet',
currency: 'ROSE',
explorerUrl: 'https://explorer.oasis.io/testnet/sapphire',
rpcUrl: 'https://testnet.sapphire.oasis.dev',
}
3 changes: 1 addition & 2 deletions src/hooks/useInterval.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { useEffect, useRef } from 'react'

export const useInterval = (cb: () => void, delay: number) => {
const cbRef = useRef(() => {
})
const cbRef = useRef(() => {})

useEffect(() => {
cbRef.current = cb
Expand Down
11 changes: 11 additions & 0 deletions src/hooks/useWalletConnect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useContext } from 'react'
import { WalletConnectContext } from '../providers/WalletConnectContext'

export const useWalletConnect = () => {
const value = useContext(WalletConnectContext)
if (value === undefined) {
throw new Error('[useWalletConnect] Component not wrapped within a Provider')
}

return value
}
81 changes: 21 additions & 60 deletions src/pages/ConnectWallet/index.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,17 @@
import { FC, useEffect, useState } from 'react'
import { FC, useState } from 'react'
import classes from './index.module.css'
import { Button } from '../../components/Button'
import { UnknownNetworkError } from '../../utils/errors'
import { Alert } from '../../components/Alert'
import { METAMASK_HOME_PAGE } from '../../constants/config'
import { useWeb3 } from '../../hooks/useWeb3'
import { useWalletConnect } from '../../hooks/useWalletConnect'

export const ConnectWallet: FC = () => {
const { connectWallet, switchNetwork, isMetaMaskInstalled } = useWeb3()
const { connectWallet, switchNetwork } = useWalletConnect()
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState('')
const [hasMetaMaskWallet, setHasMetaMaskWallet] = useState(true)
// TODO: Handle unknown network
const [isUnknownNetwork, setIsUnknownNetwork] = useState(false)

useEffect(() => {
const init = async () => {
setIsLoading(true)
setHasMetaMaskWallet(await isMetaMaskInstalled())
setIsLoading(false)
}

init()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [window.ethereum])

const handleConnectWallet = async () => {
setIsLoading(true)
try {
Expand Down Expand Up @@ -53,60 +41,33 @@ export const ConnectWallet: FC = () => {

return (
<>
{!hasMetaMaskWallet && (
{!isUnknownNetwork && (
<div>
<p className={classes.subHeader}>
Quickly wrap your ROSE into wROSE and vice versa with the (un)wrap ROSE tool.
<br />
MetaMask not detected, please install it.
Please connect your wallet to get started.
</p>

<a href={METAMASK_HOME_PAGE} target={'_blank'} rel={'noopener noreferrer'}>
<Button className={classes.installMetaMaskBtn} fullWidth disabled={isLoading}>
Install MetaMask
</Button>
</a>
<Button
variant="secondary"
onClick={() => setHasMetaMaskWallet(true)}
disabled={isLoading}
fullWidth
>
Skip
<Button onClick={handleConnectWallet} disabled={isLoading} fullWidth>
Connect wallet
</Button>
{error && <Alert variant="danger">{error}</Alert>}
</div>
)}
{hasMetaMaskWallet && (
<>
{!isUnknownNetwork && (
<div>
<p className={classes.subHeader}>
Quickly wrap your ROSE into wROSE and vice versa with the (un)wrap ROSE tool.
<br />
Please connect your wallet to get started.
</p>

<Button onClick={handleConnectWallet} disabled={isLoading} fullWidth>
Connect wallet
</Button>
{error && <Alert variant="danger">{error}</Alert>}
</div>
)}
{isUnknownNetwork && (
<div>
<p className={classes.subHeader}>
Quickly wrap your ROSE into wROSE and vice versa with the (un)wrap ROSE tool.
<br />
Please switch to another network to get started.
</p>
{isUnknownNetwork && (
<div>
<p className={classes.subHeader}>
Quickly wrap your ROSE into wROSE and vice versa with the (un)wrap ROSE tool.
<br />
Please switch to another network to get started.
</p>

<Button onClick={handleSwitchNetwork} disabled={isLoading} fullWidth>
Switch Network
</Button>
{error && <Alert variant="danger">{error}</Alert>}
</div>
)}
</>
<Button onClick={handleSwitchNetwork} disabled={isLoading} fullWidth>
Switch Network
</Button>
{error && <Alert variant="danger">{error}</Alert>}
</div>
)}
</>
)
Expand Down
Loading

0 comments on commit 60a3b8e

Please sign in to comment.