diff --git a/wallets/react-wallet-v2/package.json b/wallets/react-wallet-v2/package.json index edad553..48dbcdb 100644 --- a/wallets/react-wallet-v2/package.json +++ b/wallets/react-wallet-v2/package.json @@ -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", diff --git a/wallets/react-wallet-v2/src/styles/main.css b/wallets/react-wallet-v2/public/main.css similarity index 100% rename from wallets/react-wallet-v2/src/styles/main.css rename to wallets/react-wallet-v2/public/main.css diff --git a/wallets/react-wallet-v2/src/components/AccountCard.tsx b/wallets/react-wallet-v2/src/components/AccountCard.tsx index fe57c11..fb6ff69 100644 --- a/wallets/react-wallet-v2/src/components/AccountCard.tsx +++ b/wallets/react-wallet-v2/src/components/AccountCard.tsx @@ -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,36 +11,38 @@ interface Props { export default function AccountCard({ name, logo, rgb, address }: Props) { return ( - - + - -
- - {name} - - - {truncate(address, 19)} - -
-
-
+ + +
+ + {name} + + + {truncate(address, 19)} + +
+
+ + ) } diff --git a/wallets/react-wallet-v2/src/components/Modal.tsx b/wallets/react-wallet-v2/src/components/Modal.tsx index 52e5239..29f6204 100644 --- a/wallets/react-wallet-v2/src/components/Modal.tsx +++ b/wallets/react-wallet-v2/src/components/Modal.tsx @@ -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 ( {view === 'SessionProposalModal' && } - {view === 'SessionRequestModal' && } + {view === 'SessionSignModal' && } ) } diff --git a/wallets/react-wallet-v2/src/data/EIP155Data.ts b/wallets/react-wallet-v2/src/data/EIP155Data.ts index 20672d8..efc5cee 100644 --- a/wallets/react-wallet-v2/src/data/EIP155Data.ts +++ b/wallets/react-wallet-v2/src/data/EIP155Data.ts @@ -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' } diff --git a/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts b/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts index fe6f9bd..079ce4a 100644 --- a/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts +++ b/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts @@ -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]) diff --git a/wallets/react-wallet-v2/src/pages/_app.tsx b/wallets/react-wallet-v2/src/pages/_app.tsx index 92fb4ce..a375bb2 100644 --- a/wallets/react-wallet-v2/src/pages/_app.tsx +++ b/wallets/react-wallet-v2/src/pages/_app.tsx @@ -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 diff --git a/wallets/react-wallet-v2/src/pages/index.tsx b/wallets/react-wallet-v2/src/pages/index.tsx index ae54a27..a1014ff 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 { 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 ( Accounts - {Object.values(MAINNET_CHAINS).map(({ name, logo, rgb }) => ( + {Object.values(EIP155_CHAINS).map(({ name, logo, rgb }) => ( ))} diff --git a/wallets/react-wallet-v2/src/pages/sessions.tsx b/wallets/react-wallet-v2/src/pages/sessions.tsx new file mode 100644 index 0000000..5b0e968 --- /dev/null +++ b/wallets/react-wallet-v2/src/pages/sessions.tsx @@ -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 ( + + {truncate(address, 15)} + + ) +} diff --git a/wallets/react-wallet-v2/src/store/ModalStore.ts b/wallets/react-wallet-v2/src/store/ModalStore.ts index 789269d..b46b454 100644 --- a/wallets/react-wallet-v2/src/store/ModalStore.ts +++ b/wallets/react-wallet-v2/src/store/ModalStore.ts @@ -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 } diff --git a/wallets/react-wallet-v2/src/utils/RequestHandlerUtil.ts b/wallets/react-wallet-v2/src/utils/RequestHandlerUtil.ts new file mode 100644 index 0000000..640fa7a --- /dev/null +++ b/wallets/react-wallet-v2/src/utils/RequestHandlerUtil.ts @@ -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) +} diff --git a/wallets/react-wallet-v2/src/views/SessionProposalModal.tsx b/wallets/react-wallet-v2/src/views/SessionProposalModal.tsx index d4975b5..2f9e13e 100644 --- a/wallets/react-wallet-v2/src/views/SessionProposalModal.tsx +++ b/wallets/react-wallet-v2/src/views/SessionProposalModal.tsx @@ -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() { Blockchains - {chains.map(chain => MAINNET_CHAINS[chain as TChain]?.name ?? chain).join(', ')} + {chains + .map(chain => EIP155_CHAINS[chain as TEIP155Chain]?.name ?? chain) + .join(', ')} diff --git a/wallets/react-wallet-v2/src/views/SessionRequestModal.tsx b/wallets/react-wallet-v2/src/views/SessionSignModal.tsx similarity index 56% rename from wallets/react-wallet-v2/src/views/SessionRequestModal.tsx rename to wallets/react-wallet-v2/src/views/SessionSignModal.tsx index 93f3817..563e01c 100644 --- a/wallets/react-wallet-v2/src/views/SessionRequestModal.tsx +++ b/wallets/react-wallet-v2/src/views/SessionSignModal.tsx @@ -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 Missing request data } // 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 ( @@ -56,7 +76,18 @@ export default function SessionRequestModal() { Blockchain - {MAINNET_CHAINS[chainId as TChain]?.name ?? chainId} + + {EIP155_CHAINS[chainId as TEIP155Chain]?.name ?? chainId} + + + + + + + + + Message + {message} diff --git a/wallets/react-wallet-v2/yarn.lock b/wallets/react-wallet-v2/yarn.lock index 3a96e5a..439b972 100644 --- a/wallets/react-wallet-v2/yarn.lock +++ b/wallets/react-wallet-v2/yarn.lock @@ -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"