Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WalletConnect integration #23

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be replaced with official project id.

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
67 changes: 62 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,49 @@ 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,
},
},
featuredWalletIds: [
'c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96', // Metamask
'163d2cf19babf05eb8962e9748f9ebe613ed52ebf9c8107c9a0f104bfcf161b3', // Brave
'a797aa35c0fadbfc1a53e7f675162ed5226968b44a19ee3d24385c64d1d3c393' // Phantom
],
/*includeWalletIds: [
'c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96', // Metamask
'163d2cf19babf05eb8962e9748f9ebe613ed52ebf9c8107c9a0f104bfcf161b3', // Brave
'a797aa35c0fadbfc1a53e7f675162ed5226968b44a19ee3d24385c64d1d3c393' // Phantom
]*/
})

const router = createHashRouter([
{
Expand All @@ -32,8 +75,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