Parse and sign typed data
This commit is contained in:
parent
a9280ef124
commit
6d5c679ca7
@ -1,6 +1,7 @@
|
|||||||
import ModalStore from '@/store/ModalStore'
|
import ModalStore from '@/store/ModalStore'
|
||||||
import SessionProposalModal from '@/views/SessionProposalModal'
|
import SessionProposalModal from '@/views/SessionProposalModal'
|
||||||
import SessionRequestModal from '@/views/SessionSignModal'
|
import SessionRequestModal from '@/views/SessionSignModal'
|
||||||
|
import SessionSignTypedDataModal from '@/views/SessionSignTypedDataModal'
|
||||||
import { Modal as NextModal } from '@nextui-org/react'
|
import { Modal as NextModal } from '@nextui-org/react'
|
||||||
import { useSnapshot } from 'valtio'
|
import { useSnapshot } from 'valtio'
|
||||||
|
|
||||||
@ -11,6 +12,7 @@ export default function Modal() {
|
|||||||
<NextModal blur open={open} style={{ border: '1px solid rgba(139, 139, 139, 0.4)' }}>
|
<NextModal blur open={open} style={{ border: '1px solid rgba(139, 139, 139, 0.4)' }}>
|
||||||
{view === 'SessionProposalModal' && <SessionProposalModal />}
|
{view === 'SessionProposalModal' && <SessionProposalModal />}
|
||||||
{view === 'SessionSignModal' && <SessionRequestModal />}
|
{view === 'SessionSignModal' && <SessionRequestModal />}
|
||||||
|
{view === 'SessionSignTypedDataModal' && <SessionSignTypedDataModal />}
|
||||||
</NextModal>
|
</NextModal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ export type TEIP155Chain = keyof typeof EIP155_CHAINS
|
|||||||
/**
|
/**
|
||||||
* Chains
|
* Chains
|
||||||
*/
|
*/
|
||||||
export const EIP155_CHAINS = {
|
export const EIP155_MAINNET_CHAINS = {
|
||||||
'eip155:1': {
|
'eip155:1': {
|
||||||
chainId: 1,
|
chainId: 1,
|
||||||
name: 'Ethereum',
|
name: 'Ethereum',
|
||||||
@ -70,6 +70,8 @@ export const EIP155_TEST_CHAINS = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const EIP155_CHAINS = { ...EIP155_MAINNET_CHAINS, ...EIP155_TEST_CHAINS }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Methods
|
* Methods
|
||||||
*/
|
*/
|
||||||
@ -78,6 +80,7 @@ export const EIP155_SIGNING_METHODS = {
|
|||||||
ETH_SIGN: 'eth_sign',
|
ETH_SIGN: 'eth_sign',
|
||||||
ETH_SIGN_TRANSACTION: 'eth_signTransaction',
|
ETH_SIGN_TRANSACTION: 'eth_signTransaction',
|
||||||
ETH_SIGN_TYPED_DATA: 'eth_signTypedData',
|
ETH_SIGN_TYPED_DATA: 'eth_signTypedData',
|
||||||
|
ETH_SIGN_TYPED_DATA_V3: 'eth_signTypedData_v3',
|
||||||
ETH_SIGN_TYPED_DATA_V4: 'eth_signTypedData_v4',
|
ETH_SIGN_TYPED_DATA_V4: 'eth_signTypedData_v4',
|
||||||
ETH_SIGN_RAW_TRANSACTION: 'eth_sendRawTransaction',
|
ETH_SIGN_RAW_TRANSACTION: 'eth_sendRawTransaction',
|
||||||
ETH_SEND_TRANSACTION: 'eth_sendTransaction'
|
ETH_SEND_TRANSACTION: 'eth_sendTransaction'
|
||||||
|
@ -20,9 +20,21 @@ export default function useWalletConnectEventsManager(initialized: boolean) {
|
|||||||
const { method } = request
|
const { method } = request
|
||||||
const requestSession = await walletConnectClient.session.get(topic)
|
const requestSession = await walletConnectClient.session.get(topic)
|
||||||
|
|
||||||
|
// Hanle message signing requests of various formats
|
||||||
if ([EIP155_SIGNING_METHODS.ETH_SIGN, EIP155_SIGNING_METHODS.PERSONAL_SIGN].includes(method)) {
|
if ([EIP155_SIGNING_METHODS.ETH_SIGN, EIP155_SIGNING_METHODS.PERSONAL_SIGN].includes(method)) {
|
||||||
ModalStore.open('SessionSignModal', { requestEvent, requestSession })
|
ModalStore.open('SessionSignModal', { requestEvent, requestSession })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hanle data signing requests of various formats
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA,
|
||||||
|
EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V3,
|
||||||
|
EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V4
|
||||||
|
].includes(method)
|
||||||
|
) {
|
||||||
|
ModalStore.open('SessionSignTypedDataModal', { requestEvent, requestSession })
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import AccountCard from '@/components/AccountCard'
|
import AccountCard from '@/components/AccountCard'
|
||||||
import PageHeader from '@/components/PageHeader'
|
import PageHeader from '@/components/PageHeader'
|
||||||
import { EIP155_CHAINS, EIP155_TEST_CHAINS } from '@/data/EIP155Data'
|
import { EIP155_MAINNET_CHAINS, EIP155_TEST_CHAINS } from '@/data/EIP155Data'
|
||||||
import SettingsStore from '@/store/SettingsStore'
|
import SettingsStore from '@/store/SettingsStore'
|
||||||
import { wallet } from '@/utils/WalletUtil'
|
import { wallet } from '@/utils/WalletUtil'
|
||||||
import { Text } from '@nextui-org/react'
|
import { Text } from '@nextui-org/react'
|
||||||
@ -16,7 +16,7 @@ export default function HomePage() {
|
|||||||
<Text h4 css={{ marginBottom: '$5' }}>
|
<Text h4 css={{ marginBottom: '$5' }}>
|
||||||
Mainnets
|
Mainnets
|
||||||
</Text>
|
</Text>
|
||||||
{Object.values(EIP155_CHAINS).map(({ name, logo, rgb }) => (
|
{Object.values(EIP155_MAINNET_CHAINS).map(({ name, logo, rgb }) => (
|
||||||
<AccountCard key={name} name={name} logo={logo} rgb={rgb} address={wallet.address} />
|
<AccountCard key={name} name={name} logo={logo} rgb={rgb} address={wallet.address} />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ interface ModalData {
|
|||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
open: boolean
|
open: boolean
|
||||||
view?: 'SessionProposalModal' | 'SessionSignModal'
|
view?: 'SessionProposalModal' | 'SessionSignModal' | 'SessionSignTypedDataModal'
|
||||||
data?: ModalData
|
data?: ModalData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ interface State {
|
|||||||
* State
|
* State
|
||||||
*/
|
*/
|
||||||
const state = proxy<State>({
|
const state = proxy<State>({
|
||||||
testNets: false
|
testNets: Boolean(localStorage.getItem('TEST_NETS')) ?? false
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,6 +22,11 @@ const SettingsStore = {
|
|||||||
|
|
||||||
toggleTestNets() {
|
toggleTestNets() {
|
||||||
state.testNets = !state.testNets
|
state.testNets = !state.testNets
|
||||||
|
if (state.testNets) {
|
||||||
|
localStorage.setItem('TEST_NETS', 'YES')
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('TEST_NETS')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,3 +26,29 @@ export function convertHexToUtf8(value: string) {
|
|||||||
|
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets message from various signing request methods by filtering out
|
||||||
|
* a value that is not an address (thus is a message).
|
||||||
|
* If it is a hex string, it gets converted to utf8 string
|
||||||
|
*/
|
||||||
|
export function getSignParamsMessage(params: string[]) {
|
||||||
|
const message = params.filter(p => !utils.isAddress(p))[0]
|
||||||
|
|
||||||
|
return convertHexToUtf8(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets data from various signTypedData request methods by filtering out
|
||||||
|
* a value that is not an address (thus is data).
|
||||||
|
* If data is a string convert it to object
|
||||||
|
*/
|
||||||
|
export function getSignTypedDataParamsData(params: string[]) {
|
||||||
|
const data = params.filter(p => !utils.isAddress(p))[0]
|
||||||
|
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
return JSON.parse(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { EIP155_SIGNING_METHODS } from '@/data/EIP155Data'
|
import { EIP155_SIGNING_METHODS } from '@/data/EIP155Data'
|
||||||
import { convertHexToUtf8 } from '@/utils/HelperUtil'
|
import { getSignParamsMessage, getSignTypedDataParamsData } from '@/utils/HelperUtil'
|
||||||
import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'
|
import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'
|
||||||
import { RequestEvent } from '@walletconnect/types'
|
import { RequestEvent } from '@walletconnect/types'
|
||||||
import { ERROR } from '@walletconnect/utils'
|
import { ERROR } from '@walletconnect/utils'
|
||||||
@ -9,13 +9,28 @@ export async function approveEIP155Request(request: RequestEvent['request'], wal
|
|||||||
const { method, params, id } = request
|
const { method, params, id } = request
|
||||||
|
|
||||||
switch (method) {
|
switch (method) {
|
||||||
|
/**
|
||||||
|
* Handle message signing requests
|
||||||
|
*/
|
||||||
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
|
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
|
||||||
const personalSignResult = await wallet.signMessage(convertHexToUtf8(params[0]))
|
|
||||||
return formatJsonRpcResult(id, personalSignResult)
|
|
||||||
|
|
||||||
case EIP155_SIGNING_METHODS.ETH_SIGN:
|
case EIP155_SIGNING_METHODS.ETH_SIGN:
|
||||||
const ethSignResult = await wallet.signMessage(convertHexToUtf8(params[1]))
|
const message = getSignParamsMessage(params)
|
||||||
return formatJsonRpcResult(id, ethSignResult)
|
const signedMessage = await wallet.signMessage(message)
|
||||||
|
return formatJsonRpcResult(id, signedMessage)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle data signing requests
|
||||||
|
*/
|
||||||
|
case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA:
|
||||||
|
case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V3:
|
||||||
|
case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V4:
|
||||||
|
const { domain, types, message: data } = getSignTypedDataParamsData(params)
|
||||||
|
|
||||||
|
// https://github.com/ethers-io/ethers.js/issues/687#issuecomment-714069471
|
||||||
|
delete types.EIP712Domain
|
||||||
|
|
||||||
|
const signedData = await wallet._signTypedData(domain, types, data)
|
||||||
|
return formatJsonRpcResult(id, signedData)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(ERROR.UNKNOWN_JSONRPC_METHOD.format().message)
|
throw new Error(ERROR.UNKNOWN_JSONRPC_METHOD.format().message)
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
import { Wallet } from 'ethers'
|
import { Wallet } from 'ethers'
|
||||||
|
|
||||||
const STORAGE_KEY = 'WALLET_MNEMONIC'
|
|
||||||
|
|
||||||
export let wallet: Wallet
|
export let wallet: Wallet
|
||||||
|
|
||||||
export function createOrRestoreWallet() {
|
export function createOrRestoreWallet() {
|
||||||
const mnemonic = localStorage.getItem(STORAGE_KEY)
|
const mnemonic = localStorage.getItem('WALLET_MNEMONIC')
|
||||||
|
|
||||||
if (mnemonic) {
|
if (mnemonic) {
|
||||||
wallet = Wallet.fromMnemonic(mnemonic)
|
wallet = Wallet.fromMnemonic(mnemonic)
|
||||||
} else {
|
} else {
|
||||||
wallet = Wallet.createRandom()
|
wallet = Wallet.createRandom()
|
||||||
// Don't store mnemonic in local storage in a production project!
|
// Don't store mnemonic in local storage in a production project!
|
||||||
localStorage.setItem(STORAGE_KEY, wallet.mnemonic.phrase)
|
localStorage.setItem('WALLET_MNEMONIC', wallet.mnemonic.phrase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { EIP155_CHAINS, EIP155_SIGNING_METHODS, TEIP155Chain } from '@/data/EIP155Data'
|
import { EIP155_CHAINS, TEIP155Chain } from '@/data/EIP155Data'
|
||||||
import ModalStore from '@/store/ModalStore'
|
import ModalStore from '@/store/ModalStore'
|
||||||
import { convertHexToUtf8 } from '@/utils/HelperUtil'
|
import { getSignParamsMessage } from '@/utils/HelperUtil'
|
||||||
import { approveEIP155Request, rejectEIP155Request } from '@/utils/RequestHandlerUtil'
|
import { approveEIP155Request, rejectEIP155Request } from '@/utils/RequestHandlerUtil'
|
||||||
import { walletConnectClient } from '@/utils/WalletConnectUtil'
|
import { walletConnectClient } from '@/utils/WalletConnectUtil'
|
||||||
import { wallet } from '@/utils/WalletUtil'
|
import { wallet } from '@/utils/WalletUtil'
|
||||||
@ -24,8 +24,7 @@ export default function SessionSignModal() {
|
|||||||
const { name, icons, url } = requestSession.peer.metadata
|
const { name, icons, url } = requestSession.peer.metadata
|
||||||
|
|
||||||
// Get message, convert it to UTF8 string if it is valid hex
|
// Get message, convert it to UTF8 string if it is valid hex
|
||||||
let message = method === EIP155_SIGNING_METHODS.PERSONAL_SIGN ? params[0] : params[1]
|
const message = getSignParamsMessage(params)
|
||||||
message = convertHexToUtf8(message)
|
|
||||||
|
|
||||||
// Handle approve action (logic varies based on request method)
|
// Handle approve action (logic varies based on request method)
|
||||||
async function onApprove() {
|
async function onApprove() {
|
||||||
@ -54,7 +53,7 @@ export default function SessionSignModal() {
|
|||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Modal.Header>
|
<Modal.Header>
|
||||||
<Text h3>Request</Text>
|
<Text h3>Sign Message</Text>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
|
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
|
121
wallets/react-wallet-v2/src/views/SessionSignTypedDataModal.tsx
Normal file
121
wallets/react-wallet-v2/src/views/SessionSignTypedDataModal.tsx
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import { EIP155_CHAINS, TEIP155Chain } from '@/data/EIP155Data'
|
||||||
|
import ModalStore from '@/store/ModalStore'
|
||||||
|
import { getSignTypedDataParamsData } 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 { Fragment } from 'react'
|
||||||
|
|
||||||
|
export default function SessionSignTypedDataModal() {
|
||||||
|
// Get request and wallet data from store
|
||||||
|
const requestEvent = ModalStore.state.data?.requestEvent
|
||||||
|
const requestSession = ModalStore.state.data?.requestSession
|
||||||
|
|
||||||
|
// Ensure request and wallet are defined
|
||||||
|
if (!requestEvent || !requestSession) {
|
||||||
|
return <Text>Missing request data</Text>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get required request data
|
||||||
|
const { chainId } = requestEvent
|
||||||
|
const { method, params } = requestEvent.request
|
||||||
|
const { protocol } = requestSession.relay
|
||||||
|
const { name, icons, url } = requestSession.peer.metadata
|
||||||
|
|
||||||
|
// Get data
|
||||||
|
const data = getSignTypedDataParamsData(params)
|
||||||
|
|
||||||
|
// Handle approve action (logic varies based on request method)
|
||||||
|
async function onApprove() {
|
||||||
|
if (requestEvent) {
|
||||||
|
const response = await approveEIP155Request(requestEvent.request, wallet)
|
||||||
|
await walletConnectClient.respond({
|
||||||
|
topic: requestEvent.topic,
|
||||||
|
response
|
||||||
|
})
|
||||||
|
ModalStore.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle reject action
|
||||||
|
async function onReject() {
|
||||||
|
if (requestEvent) {
|
||||||
|
const response = rejectEIP155Request(requestEvent.request)
|
||||||
|
await walletConnectClient.respond({
|
||||||
|
topic: requestEvent.topic,
|
||||||
|
response
|
||||||
|
})
|
||||||
|
ModalStore.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Modal.Header>
|
||||||
|
<Text h3>Sign Typed Data</Text>
|
||||||
|
</Modal.Header>
|
||||||
|
|
||||||
|
<Modal.Body>
|
||||||
|
<Container css={{ padding: 0 }}>
|
||||||
|
<Row align="center">
|
||||||
|
<Col span={3}>
|
||||||
|
<Avatar src={icons[0]} />
|
||||||
|
</Col>
|
||||||
|
<Col span={14}>
|
||||||
|
<Text h5>{name}</Text>
|
||||||
|
<Link href={url}>{url}</Link>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Divider y={2} />
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<Text h5>Blockchain</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">{JSON.stringify(data)}</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Divider y={2} />
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<Text h5>Method</Text>
|
||||||
|
<Text color="$gray400">{method}</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Divider y={2} />
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<Text h5>Relay Protocol</Text>
|
||||||
|
<Text color="$gray400">{protocol}</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
</Modal.Body>
|
||||||
|
|
||||||
|
<Modal.Footer>
|
||||||
|
<Button auto flat color="error" onClick={onReject}>
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
<Button auto flat color="success" onClick={onApprove}>
|
||||||
|
Approve
|
||||||
|
</Button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user