Wrap up eth_sign and personal_sign examples

This commit is contained in:
Ilja 2022-02-14 15:38:09 +02:00
parent de05b8c82c
commit e2ac17957e
14 changed files with 183 additions and 73 deletions

View File

@ -19,7 +19,8 @@
"react-qr-reader-es6": "2.2.1-2",
"framer-motion": "6.2.6",
"ethers": "5.5.4",
"valtio": "1.3.0"
"valtio": "1.3.0",
"@json-rpc-tools/utils": "1.7.6"
},
"devDependencies": {
"@walletconnect/types": "2.0.0-beta.22",

View File

@ -1,5 +1,6 @@
import { truncate } from '@/utils/HelperUtil'
import { Avatar, Card, Text } from '@nextui-org/react'
import Link from 'next/link'
interface Props {
name: string
@ -10,6 +11,7 @@ interface Props {
export default function AccountCard({ name, logo, rgb, address }: Props) {
return (
<Link href={`/sessions?address=${address}`} passHref>
<Card
bordered
clickable
@ -41,5 +43,6 @@ export default function AccountCard({ name, logo, rgb, address }: Props) {
</div>
</Card.Body>
</Card>
</Link>
)
}

View File

@ -1,6 +1,6 @@
import ModalStore from '@/store/ModalStore'
import SessionProposalModal from '@/views/SessionProposalModal'
import SessionRequestModal from '@/views/SessionRequestModal'
import SessionRequestModal from '@/views/SessionSignModal'
import { Modal as NextModal } from '@nextui-org/react'
import { useSnapshot } from 'valtio'
@ -10,7 +10,7 @@ export default function Modal() {
return (
<NextModal blur open={open} style={{ border: '1px solid rgba(139, 139, 139, 0.4)' }}>
{view === 'SessionProposalModal' && <SessionProposalModal />}
{view === 'SessionRequestModal' && <SessionRequestModal />}
{view === 'SessionSignModal' && <SessionRequestModal />}
</NextModal>
)
}

View File

@ -3,20 +3,20 @@
* @url https://chainlist.org
*/
/**
* Types
*/
export type TChain = keyof typeof MAINNET_CHAINS
/**
* Utilities
*/
export const LOGO_BASE_URL = 'https://blockchain-api.xyz/logos/'
const LOGO_BASE_URL = 'https://blockchain-api.xyz/logos/'
/**
* Types
*/
export type TEIP155Chain = keyof typeof EIP155_CHAINS
/**
* Chains
*/
export const MAINNET_CHAINS = {
export const EIP155_CHAINS = {
'eip155:1': {
chainId: 1,
name: 'Ethereum',
@ -46,11 +46,12 @@ export const MAINNET_CHAINS = {
/**
* Methods
*/
export const SIGNING_METHODS = {
export const EIP155_SIGNING_METHODS = {
PERSONAL_SIGN: 'personal_sign',
SEND_TRANSACTION: 'eth_sendTransaction',
SIGN: 'eth_sign',
SIGN_TRANSACTION: 'eth_signTransaction',
SIGN_TYPED_DATA: 'eth_signTypedData',
SIGN_TYPED_DATA_V4: 'eth_signTypedData_v4'
ETH_SIGN: 'eth_sign',
ETH_SIGN_TRANSACTION: 'eth_signTransaction',
ETH_SIGN_TYPED_DATA: 'eth_signTypedData',
ETH_SIGN_TYPED_DATA_V4: 'eth_signTypedData_v4',
ETH_SIGN_RAW_TRANSACTION: 'eth_sendRawTransaction',
ETH_SEND_TRANSACTION: 'eth_sendTransaction'
}

View File

@ -1,3 +1,4 @@
import { EIP155_SIGNING_METHODS } from '@/data/EIP155Data'
import ModalStore from '@/store/ModalStore'
import { walletConnectClient } from '@/utils/WalletConnectUtil'
import { CLIENT_EVENTS } from '@walletconnect/client'
@ -5,28 +6,31 @@ import { SessionTypes } from '@walletconnect/types'
import { useCallback, useEffect } from 'react'
export default function useWalletConnectEventsManager(initialized: boolean) {
// 1. Open session proposal modal for confirmation / rejection
const onSessionProposal = useCallback((proposal: SessionTypes.Proposal) => {
ModalStore.open('SessionProposalModal', { proposal })
}, [])
const onSessionCreated = useCallback((created: SessionTypes.Created) => {
// TODO show successful connection feedback here
}, [])
// 2. Open session created modal to show success feedback
const onSessionCreated = useCallback((created: SessionTypes.Created) => {}, [])
const onSessionRequest = useCallback(async (request: SessionTypes.RequestEvent) => {
const requestSession = await walletConnectClient.session.get(request.topic)
ModalStore.open('SessionRequestModal', { request, requestSession })
// 3. Open rpc request handling modal based on method that was used
const onSessionRequest = useCallback(async (requestEvent: SessionTypes.RequestEvent) => {
const { topic, request } = requestEvent
const { method } = request
const requestSession = await walletConnectClient.session.get(topic)
if ([EIP155_SIGNING_METHODS.ETH_SIGN, EIP155_SIGNING_METHODS.PERSONAL_SIGN].includes(method)) {
ModalStore.open('SessionSignModal', { requestEvent, requestSession })
}
}, [])
useEffect(() => {
if (initialized) {
// 1. Open session proposal modal for confirmation / rejection
walletConnectClient.on(CLIENT_EVENTS.session.proposal, onSessionProposal)
// 2. Open session created modal to show success feedback
walletConnectClient.on(CLIENT_EVENTS.session.created, onSessionCreated)
// 3. Open rpc request handling modal
walletConnectClient.on(CLIENT_EVENTS.session.request, onSessionRequest)
}
}, [initialized, onSessionProposal, onSessionCreated, onSessionRequest])

View File

@ -2,11 +2,11 @@ import Layout from '@/components/Layout'
import Modal from '@/components/Modal'
import useInitialization from '@/hooks/useInitialization'
import useWalletConnectEventsManager from '@/hooks/useWalletConnectEventsManager'
import '@/styles/main.css'
import { darkTheme, lightTheme } from '@/utils/ThemeUtil'
import { NextUIProvider } from '@nextui-org/react'
import { ThemeProvider } from 'next-themes'
import { AppProps } from 'next/app'
import '../../public/main.css'
export default function App({ Component, pageProps }: AppProps) {
// Step 1 - Initialize wallets and wallet connect client

View File

@ -1,6 +1,6 @@
import AccountCard from '@/components/AccountCard'
import PageHeader from '@/components/PageHeader'
import { MAINNET_CHAINS } from '@/data/EIP155Data'
import { EIP155_CHAINS } from '@/data/EIP155Data'
import { wallet } from '@/utils/WalletUtil'
import { Fragment } from 'react'
@ -8,7 +8,7 @@ export default function HomePage() {
return (
<Fragment>
<PageHeader>Accounts</PageHeader>
{Object.values(MAINNET_CHAINS).map(({ name, logo, rgb }) => (
{Object.values(EIP155_CHAINS).map(({ name, logo, rgb }) => (
<AccountCard key={name} name={name} logo={logo} rgb={rgb} address={wallet.address} />
))}
</Fragment>

View File

@ -0,0 +1,20 @@
import PageHeader from '@/components/PageHeader'
import { truncate } from '@/utils/HelperUtil'
import { walletConnectClient } from '@/utils/WalletConnectUtil'
import { useRouter } from 'next/router'
import { Fragment, useEffect } from 'react'
export default function SessionsPage() {
const { query } = useRouter()
const address = (query?.address as string) ?? 'Unknown'
useEffect(() => {
console.log(walletConnectClient.session.values)
}, [])
return (
<Fragment>
<PageHeader>{truncate(address, 15)}</PageHeader>
</Fragment>
)
}

View File

@ -7,13 +7,13 @@ import { proxy } from 'valtio'
interface ModalData {
proposal?: SessionTypes.Proposal
created?: SessionTypes.Created
request?: SessionTypes.RequestEvent
requestEvent?: SessionTypes.RequestEvent
requestSession?: SessionTypes.Settled
}
interface State {
open: boolean
view?: 'SessionProposalModal' | 'SessionRequestModal'
view?: 'SessionProposalModal' | 'SessionSignModal'
data?: ModalData
}

View File

@ -0,0 +1,28 @@
import { EIP155_SIGNING_METHODS } from '@/data/EIP155Data'
import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'
import { RequestEvent } from '@walletconnect/types'
import { ERROR } from '@walletconnect/utils'
import { Wallet } from 'ethers'
export async function approveEIP155Request(request: RequestEvent['request'], wallet: Wallet) {
const { method, params, id } = request
switch (method) {
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
const personalSignResult = await wallet.signMessage(params[0])
return formatJsonRpcResult(id, personalSignResult)
case EIP155_SIGNING_METHODS.ETH_SIGN:
const ethSignResult = await wallet.signMessage(params[1])
return formatJsonRpcResult(id, ethSignResult)
default:
throw new Error(ERROR.UNKNOWN_JSONRPC_METHOD.format().message)
}
}
export function rejectEIP155Request(request: RequestEvent['request']) {
const { id } = request
return formatJsonRpcError(id, ERROR.JSONRPC_REQUEST_METHOD_REJECTED.format().message)
}

View File

@ -1,4 +1,4 @@
import { MAINNET_CHAINS, TChain } from '@/data/EIP155Data'
import { EIP155_CHAINS, TEIP155Chain } from '@/data/EIP155Data'
import ModalStore from '@/store/ModalStore'
import { walletConnectClient } from '@/utils/WalletConnectUtil'
import { wallet } from '@/utils/WalletUtil'
@ -66,7 +66,9 @@ export default function SessionProposalModal() {
<Col>
<Text h5>Blockchains</Text>
<Text color="$gray400">
{chains.map(chain => MAINNET_CHAINS[chain as TChain]?.name ?? chain).join(', ')}
{chains
.map(chain => EIP155_CHAINS[chain as TEIP155Chain]?.name ?? chain)
.join(', ')}
</Text>
</Col>
</Row>

View File

@ -1,37 +1,57 @@
import { MAINNET_CHAINS, TChain } from '@/data/EIP155Data'
import { EIP155_CHAINS, EIP155_SIGNING_METHODS, TEIP155Chain } from '@/data/EIP155Data'
import ModalStore from '@/store/ModalStore'
import { getSignMessage } from '@/utils/HelperUtil'
import { approveEIP155Request, rejectEIP155Request } from '@/utils/RequestHandlerUtil'
import { walletConnectClient } from '@/utils/WalletConnectUtil'
import { wallet } from '@/utils/WalletUtil'
import { Avatar, Button, Col, Container, Divider, Link, Modal, Row, Text } from '@nextui-org/react'
import { utils } from 'ethers'
import { Fragment } from 'react'
export default function SessionRequestModal() {
export default function SessionSignModal() {
// Get request and wallet data from store
const request = ModalStore.state.data?.request
const requestEvent = ModalStore.state.data?.requestEvent
const requestSession = ModalStore.state.data?.requestSession
// Ensure request and wallet are defined
if (!request || !requestSession) {
if (!requestEvent || !requestSession) {
return <Text>Missing request data</Text>
}
// Get required request data
const { chainId } = request
const { method, params } = request.request
const { chainId } = requestEvent
const { method, params } = requestEvent.request
const { protocol } = requestSession.relay
const { name, icons, url } = requestSession.peer.metadata
// Get message as utf string
let message = method === EIP155_SIGNING_METHODS.PERSONAL_SIGN ? params[0] : params[1]
if (utils.isHexString(message)) {
message = utils.toUtf8String(message)
}
// Handle approve action (logic varies based on request method)
async function onApprove() {
// Handle sign requests
if (['eth_sign', 'personal_sign'].includes(method)) {
const message = getSignMessage(params, wallet.address)
const signedMessage = wallet.signMessage(message)
if (requestEvent) {
const response = await approveEIP155Request(requestEvent.request, wallet)
await walletConnectClient.respond({
topic: requestEvent.topic,
response
})
ModalStore.close()
}
}
// Handle reject action
async function onReject() {}
async function onReject() {
if (requestEvent) {
const response = rejectEIP155Request(requestEvent.request)
await walletConnectClient.respond({
topic: requestEvent.topic,
response
})
ModalStore.close()
}
}
return (
<Fragment>
@ -56,7 +76,18 @@ export default function SessionRequestModal() {
<Row>
<Col>
<Text h5>Blockchain</Text>
<Text color="$gray400">{MAINNET_CHAINS[chainId as TChain]?.name ?? chainId}</Text>
<Text color="$gray400">
{EIP155_CHAINS[chainId as TEIP155Chain]?.name ?? chainId}
</Text>
</Col>
</Row>
<Divider y={2} />
<Row>
<Col>
<Text h5>Message</Text>
<Text color="$gray400">{message}</Text>
</Col>
</Row>

View File

@ -417,6 +417,21 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
"@json-rpc-tools/types@^1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@json-rpc-tools/types/-/types-1.7.6.tgz#5abd5fde01364a130c46093b501715bcce5bdc0e"
integrity sha512-nDSqmyRNEqEK9TZHtM15uNnDljczhCUdBmRhpNZ95bIPKEDQ+nTDmGMFd2lLin3upc5h2VVVd9tkTDdbXUhDIQ==
dependencies:
keyvaluestorage-interface "^1.0.0"
"@json-rpc-tools/utils@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@json-rpc-tools/utils/-/utils-1.7.6.tgz#67f04987dbaa2e7adb6adff1575367b75a9a9ba1"
integrity sha512-HjA8x/U/Q78HRRe19yh8HVKoZ+Iaoo3YZjakJYxR+rw52NHo6jM+VE9b8+7ygkCFXl/EHID5wh/MkXaE/jGyYw==
dependencies:
"@json-rpc-tools/types" "^1.7.6"
"@pedrouid/environment" "^1.0.1"
"@next/env@12.0.10":
version "12.0.10"
resolved "https://registry.yarnpkg.com/@next/env/-/env-12.0.10.tgz#561640fd62279218ccd2798ae907bae8d94a7730"
@ -517,6 +532,11 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@pedrouid/environment@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@pedrouid/environment/-/environment-1.0.1.tgz#858f0f8a057340e0b250398b75ead77d6f4342ec"
integrity sha512-HaW78NszGzRZd9SeoI3JD11JqY+lubnaOx7Pewj5pfjqWXOEATpeKIFb9Z4t2WBUK2iryiXX3lzWwmYWgUL0Ug==
"@react-aria/focus@3.5.0":
version "3.5.0"
resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.5.0.tgz#02b85f97d6114af1eccc0902ce40723b626cb7f9"