diff --git a/wallets/react-wallet-v2/src/components/Modal.tsx b/wallets/react-wallet-v2/src/components/Modal.tsx
index 29f6204..4f66a34 100644
--- a/wallets/react-wallet-v2/src/components/Modal.tsx
+++ b/wallets/react-wallet-v2/src/components/Modal.tsx
@@ -1,6 +1,7 @@
import ModalStore from '@/store/ModalStore'
import SessionProposalModal from '@/views/SessionProposalModal'
import SessionRequestModal from '@/views/SessionSignModal'
+import SessionSignTypedDataModal from '@/views/SessionSignTypedDataModal'
import { Modal as NextModal } from '@nextui-org/react'
import { useSnapshot } from 'valtio'
@@ -11,6 +12,7 @@ export default function Modal() {
{view === 'SessionProposalModal' && }
{view === 'SessionSignModal' && }
+ {view === 'SessionSignTypedDataModal' && }
)
}
diff --git a/wallets/react-wallet-v2/src/data/EIP155Data.ts b/wallets/react-wallet-v2/src/data/EIP155Data.ts
index c9ecf2d..7e7a730 100644
--- a/wallets/react-wallet-v2/src/data/EIP155Data.ts
+++ b/wallets/react-wallet-v2/src/data/EIP155Data.ts
@@ -16,7 +16,7 @@ export type TEIP155Chain = keyof typeof EIP155_CHAINS
/**
* Chains
*/
-export const EIP155_CHAINS = {
+export const EIP155_MAINNET_CHAINS = {
'eip155:1': {
chainId: 1,
name: 'Ethereum',
@@ -70,6 +70,8 @@ export const EIP155_TEST_CHAINS = {
}
}
+export const EIP155_CHAINS = { ...EIP155_MAINNET_CHAINS, ...EIP155_TEST_CHAINS }
+
/**
* Methods
*/
@@ -78,6 +80,7 @@ export const EIP155_SIGNING_METHODS = {
ETH_SIGN: 'eth_sign',
ETH_SIGN_TRANSACTION: 'eth_signTransaction',
ETH_SIGN_TYPED_DATA: 'eth_signTypedData',
+ ETH_SIGN_TYPED_DATA_V3: 'eth_signTypedData_v3',
ETH_SIGN_TYPED_DATA_V4: 'eth_signTypedData_v4',
ETH_SIGN_RAW_TRANSACTION: 'eth_sendRawTransaction',
ETH_SEND_TRANSACTION: 'eth_sendTransaction'
diff --git a/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts b/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts
index d5497da..e1cd614 100644
--- a/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts
+++ b/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts
@@ -20,9 +20,21 @@ export default function useWalletConnectEventsManager(initialized: boolean) {
const { method } = request
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)) {
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(() => {
diff --git a/wallets/react-wallet-v2/src/pages/index.tsx b/wallets/react-wallet-v2/src/pages/index.tsx
index 36d6a0e..5946476 100644
--- a/wallets/react-wallet-v2/src/pages/index.tsx
+++ b/wallets/react-wallet-v2/src/pages/index.tsx
@@ -1,6 +1,6 @@
import AccountCard from '@/components/AccountCard'
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 { wallet } from '@/utils/WalletUtil'
import { Text } from '@nextui-org/react'
@@ -16,7 +16,7 @@ export default function HomePage() {
Mainnets
- {Object.values(EIP155_CHAINS).map(({ name, logo, rgb }) => (
+ {Object.values(EIP155_MAINNET_CHAINS).map(({ name, logo, rgb }) => (
))}
diff --git a/wallets/react-wallet-v2/src/store/ModalStore.ts b/wallets/react-wallet-v2/src/store/ModalStore.ts
index b46b454..2fe913d 100644
--- a/wallets/react-wallet-v2/src/store/ModalStore.ts
+++ b/wallets/react-wallet-v2/src/store/ModalStore.ts
@@ -13,7 +13,7 @@ interface ModalData {
interface State {
open: boolean
- view?: 'SessionProposalModal' | 'SessionSignModal'
+ view?: 'SessionProposalModal' | 'SessionSignModal' | 'SessionSignTypedDataModal'
data?: ModalData
}
diff --git a/wallets/react-wallet-v2/src/store/SettingsStore.ts b/wallets/react-wallet-v2/src/store/SettingsStore.ts
index 2771f4e..05c664f 100644
--- a/wallets/react-wallet-v2/src/store/SettingsStore.ts
+++ b/wallets/react-wallet-v2/src/store/SettingsStore.ts
@@ -11,7 +11,7 @@ interface State {
* State
*/
const state = proxy({
- testNets: false
+ testNets: Boolean(localStorage.getItem('TEST_NETS')) ?? false
})
/**
@@ -22,6 +22,11 @@ const SettingsStore = {
toggleTestNets() {
state.testNets = !state.testNets
+ if (state.testNets) {
+ localStorage.setItem('TEST_NETS', 'YES')
+ } else {
+ localStorage.removeItem('TEST_NETS')
+ }
}
}
diff --git a/wallets/react-wallet-v2/src/utils/HelperUtil.ts b/wallets/react-wallet-v2/src/utils/HelperUtil.ts
index 62c3567..0703c53 100644
--- a/wallets/react-wallet-v2/src/utils/HelperUtil.ts
+++ b/wallets/react-wallet-v2/src/utils/HelperUtil.ts
@@ -26,3 +26,29 @@ export function convertHexToUtf8(value: string) {
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
+}
diff --git a/wallets/react-wallet-v2/src/utils/RequestHandlerUtil.ts b/wallets/react-wallet-v2/src/utils/RequestHandlerUtil.ts
index 498e8b5..1db5ef5 100644
--- a/wallets/react-wallet-v2/src/utils/RequestHandlerUtil.ts
+++ b/wallets/react-wallet-v2/src/utils/RequestHandlerUtil.ts
@@ -1,5 +1,5 @@
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 { RequestEvent } from '@walletconnect/types'
import { ERROR } from '@walletconnect/utils'
@@ -9,13 +9,28 @@ export async function approveEIP155Request(request: RequestEvent['request'], wal
const { method, params, id } = request
switch (method) {
+ /**
+ * Handle message signing requests
+ */
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
- const personalSignResult = await wallet.signMessage(convertHexToUtf8(params[0]))
- return formatJsonRpcResult(id, personalSignResult)
-
case EIP155_SIGNING_METHODS.ETH_SIGN:
- const ethSignResult = await wallet.signMessage(convertHexToUtf8(params[1]))
- return formatJsonRpcResult(id, ethSignResult)
+ const message = getSignParamsMessage(params)
+ 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:
throw new Error(ERROR.UNKNOWN_JSONRPC_METHOD.format().message)
diff --git a/wallets/react-wallet-v2/src/utils/WalletUtil.ts b/wallets/react-wallet-v2/src/utils/WalletUtil.ts
index 527a66c..be73ad6 100644
--- a/wallets/react-wallet-v2/src/utils/WalletUtil.ts
+++ b/wallets/react-wallet-v2/src/utils/WalletUtil.ts
@@ -1,17 +1,15 @@
import { Wallet } from 'ethers'
-const STORAGE_KEY = 'WALLET_MNEMONIC'
-
export let wallet: Wallet
export function createOrRestoreWallet() {
- const mnemonic = localStorage.getItem(STORAGE_KEY)
+ const mnemonic = localStorage.getItem('WALLET_MNEMONIC')
if (mnemonic) {
wallet = Wallet.fromMnemonic(mnemonic)
} else {
wallet = Wallet.createRandom()
// 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)
}
}
diff --git a/wallets/react-wallet-v2/src/views/SessionSignModal.tsx b/wallets/react-wallet-v2/src/views/SessionSignModal.tsx
index 57a66c5..5c8a6d2 100644
--- a/wallets/react-wallet-v2/src/views/SessionSignModal.tsx
+++ b/wallets/react-wallet-v2/src/views/SessionSignModal.tsx
@@ -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 { convertHexToUtf8 } from '@/utils/HelperUtil'
+import { getSignParamsMessage } from '@/utils/HelperUtil'
import { approveEIP155Request, rejectEIP155Request } from '@/utils/RequestHandlerUtil'
import { walletConnectClient } from '@/utils/WalletConnectUtil'
import { wallet } from '@/utils/WalletUtil'
@@ -24,8 +24,7 @@ export default function SessionSignModal() {
const { name, icons, url } = requestSession.peer.metadata
// Get message, convert it to UTF8 string if it is valid hex
- let message = method === EIP155_SIGNING_METHODS.PERSONAL_SIGN ? params[0] : params[1]
- message = convertHexToUtf8(message)
+ const message = getSignParamsMessage(params)
// Handle approve action (logic varies based on request method)
async function onApprove() {
@@ -54,7 +53,7 @@ export default function SessionSignModal() {
return (
- Request
+ Sign Message
diff --git a/wallets/react-wallet-v2/src/views/SessionSignTypedDataModal.tsx b/wallets/react-wallet-v2/src/views/SessionSignTypedDataModal.tsx
new file mode 100644
index 0000000..025ce1e
--- /dev/null
+++ b/wallets/react-wallet-v2/src/views/SessionSignTypedDataModal.tsx
@@ -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 Missing request data
+ }
+
+ // 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 (
+
+
+ Sign Typed Data
+
+
+
+
+
+
+
+
+
+ {name}
+ {url}
+
+
+
+
+
+
+
+ Blockchain
+
+ {EIP155_CHAINS[chainId as TEIP155Chain]?.name ?? chainId}
+
+
+
+
+
+
+
+
+ Message
+ {JSON.stringify(data)}
+
+
+
+
+
+
+
+ Method
+ {method}
+
+
+
+
+
+
+
+ Relay Protocol
+ {protocol}
+
+
+
+
+
+
+
+
+
+
+ )
+}