From de05b8c82cb63bb6642e5c17aa829c39c5c7e6bb Mon Sep 17 00:00:00 2001 From: Ilja Date: Mon, 14 Feb 2022 12:10:44 +0200 Subject: [PATCH] Refactor wallet into util, restore from mnemonic, prep for session page --- wallets/react-wallet-v2/package.json | 4 +- .../src/components/AccountCard.tsx | 1 + .../EIP155Data.ts} | 23 +++++++- .../src/hooks/useInitialization.ts | 8 +-- .../hooks/useWalletConnectEventsManager.ts | 14 ++--- wallets/react-wallet-v2/src/pages/index.tsx | 11 +--- .../react-wallet-v2/src/pages/settings.tsx | 4 +- .../src/pages/walletconnect.tsx | 4 +- .../react-wallet-v2/src/store/WalletStore.ts | 29 --------- .../react-wallet-v2/src/utils/HelperUtil.ts | 14 +++++ .../src/utils/WalletConnectUtil.ts | 6 +- .../react-wallet-v2/src/utils/WalletUtil.ts | 17 ++++++ .../src/views/SessionProposalModal.tsx | 19 +++--- .../src/views/SessionRequestModal.tsx | 19 +++--- wallets/react-wallet-v2/yarn.lock | 59 ++++++++++--------- 15 files changed, 127 insertions(+), 105 deletions(-) rename wallets/react-wallet-v2/src/{utils/EIP155ChainsUtil.ts => data/EIP155Data.ts} (64%) delete mode 100644 wallets/react-wallet-v2/src/store/WalletStore.ts create mode 100644 wallets/react-wallet-v2/src/utils/WalletUtil.ts diff --git a/wallets/react-wallet-v2/package.json b/wallets/react-wallet-v2/package.json index 749fdd5..edad553 100644 --- a/wallets/react-wallet-v2/package.json +++ b/wallets/react-wallet-v2/package.json @@ -19,13 +19,13 @@ "react-qr-reader-es6": "2.2.1-2", "framer-motion": "6.2.6", "ethers": "5.5.4", - "valtio": "1.2.12" + "valtio": "1.3.0" }, "devDependencies": { "@walletconnect/types": "2.0.0-beta.22", "@types/node": "17.0.17", "@types/react": "17.0.39", - "eslint": "8.8.0", + "eslint": "8.9.0", "eslint-config-next": "12.0.10", "eslint-config-prettier": "8.3.0", "prettier": "2.5.1", diff --git a/wallets/react-wallet-v2/src/components/AccountCard.tsx b/wallets/react-wallet-v2/src/components/AccountCard.tsx index 0350aff..fe57c11 100644 --- a/wallets/react-wallet-v2/src/components/AccountCard.tsx +++ b/wallets/react-wallet-v2/src/components/AccountCard.tsx @@ -12,6 +12,7 @@ export default function AccountCard({ name, logo, rgb, address }: Props) { return ( { try { - WalletStore.createWallet() - await createClient() + createOrRestoreWallet() + await createWalletConnectClient() setInitialized(true) } catch (err: unknown) { alert(err) diff --git a/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts b/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts index 8391f77..fe6f9bd 100644 --- a/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts +++ b/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts @@ -1,5 +1,5 @@ import ModalStore from '@/store/ModalStore' -import { client } from '@/utils/WalletConnectUtil' +import { walletConnectClient } from '@/utils/WalletConnectUtil' import { CLIENT_EVENTS } from '@walletconnect/client' import { SessionTypes } from '@walletconnect/types' import { useCallback, useEffect } from 'react' @@ -10,24 +10,24 @@ export default function useWalletConnectEventsManager(initialized: boolean) { }, []) const onSessionCreated = useCallback((created: SessionTypes.Created) => { - // TODO show successful feedback here + // TODO show successful connection feedback here }, []) const onSessionRequest = useCallback(async (request: SessionTypes.RequestEvent) => { - const requestSession = await client?.session.get(request.topic) + const requestSession = await walletConnectClient.session.get(request.topic) ModalStore.open('SessionRequestModal', { request, requestSession }) }, []) useEffect(() => { - if (initialized && client) { + if (initialized) { // 1. Open session proposal modal for confirmation / rejection - client.on(CLIENT_EVENTS.session.proposal, onSessionProposal) + walletConnectClient.on(CLIENT_EVENTS.session.proposal, onSessionProposal) // 2. Open session created modal to show success feedback - client.on(CLIENT_EVENTS.session.created, onSessionCreated) + walletConnectClient.on(CLIENT_EVENTS.session.created, onSessionCreated) // 3. Open rpc request handling modal - client.on(CLIENT_EVENTS.session.request, onSessionRequest) + walletConnectClient.on(CLIENT_EVENTS.session.request, onSessionRequest) } }, [initialized, onSessionProposal, onSessionCreated, onSessionRequest]) } diff --git a/wallets/react-wallet-v2/src/pages/index.tsx b/wallets/react-wallet-v2/src/pages/index.tsx index b87ef28..ae54a27 100644 --- a/wallets/react-wallet-v2/src/pages/index.tsx +++ b/wallets/react-wallet-v2/src/pages/index.tsx @@ -1,17 +1,10 @@ import AccountCard from '@/components/AccountCard' import PageHeader from '@/components/PageHeader' -import WalletStore from '@/store/WalletStore' -import { MAINNET_CHAINS } from '@/utils/EIP155ChainsUtil' +import { MAINNET_CHAINS } from '@/data/EIP155Data' +import { wallet } from '@/utils/WalletUtil' import { Fragment } from 'react' -import { useSnapshot } from 'valtio' export default function HomePage() { - const { wallet } = useSnapshot(WalletStore.state) - - if (!wallet) { - return null - } - return ( Accounts diff --git a/wallets/react-wallet-v2/src/pages/settings.tsx b/wallets/react-wallet-v2/src/pages/settings.tsx index 1db5561..8f43dfd 100644 --- a/wallets/react-wallet-v2/src/pages/settings.tsx +++ b/wallets/react-wallet-v2/src/pages/settings.tsx @@ -1,5 +1,5 @@ import PageHeader from '@/components/PageHeader' -import WalletStore from '@/store/WalletStore' +import { wallet } from '@/utils/WalletUtil' import { Card, Divider, Row, Switch, Text } from '@nextui-org/react' import { Fragment } from 'react' @@ -11,7 +11,7 @@ export default function SettingsPage() { Mnemonic - {WalletStore.state.wallet?.mnemonic.phrase} + {wallet.mnemonic.phrase} diff --git a/wallets/react-wallet-v2/src/pages/walletconnect.tsx b/wallets/react-wallet-v2/src/pages/walletconnect.tsx index 7180319..8486c3c 100644 --- a/wallets/react-wallet-v2/src/pages/walletconnect.tsx +++ b/wallets/react-wallet-v2/src/pages/walletconnect.tsx @@ -1,6 +1,6 @@ import PageHeader from '@/components/PageHeader' import QrReader from '@/components/QrReader' -import { client } from '@/utils/WalletConnectUtil' +import { walletConnectClient } from '@/utils/WalletConnectUtil' import { Button, Input, Loading, Text } from '@nextui-org/react' import { Fragment, useState } from 'react' @@ -11,7 +11,7 @@ export default function WalletConnectPage() { async function onConnect(uri: string) { try { setLoading(true) - await client?.pair({ uri }) + await walletConnectClient.pair({ uri }) } catch (err: unknown) { alert(err) } finally { diff --git a/wallets/react-wallet-v2/src/store/WalletStore.ts b/wallets/react-wallet-v2/src/store/WalletStore.ts deleted file mode 100644 index f9cb799..0000000 --- a/wallets/react-wallet-v2/src/store/WalletStore.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Wallet } from 'ethers' -import { proxy } from 'valtio' - -/** - * Types - */ -interface State { - wallet?: Wallet -} - -/** - * State - */ -const state = proxy({ - wallet: undefined -}) - -/** - * Store / Actions - */ -const WalletStore = { - state, - - createWallet() { - state.wallet = Wallet.createRandom() - } -} - -export default WalletStore diff --git a/wallets/react-wallet-v2/src/utils/HelperUtil.ts b/wallets/react-wallet-v2/src/utils/HelperUtil.ts index c00c749..53f1d43 100644 --- a/wallets/react-wallet-v2/src/utils/HelperUtil.ts +++ b/wallets/react-wallet-v2/src/utils/HelperUtil.ts @@ -1,3 +1,6 @@ +/** + * Truncates string (in the middle) via given lenght value + */ export function truncate(value: string, length: number) { if (value.length <= length) { return value @@ -10,3 +13,14 @@ export function truncate(value: string, length: number) { return value.substring(0, frontLength) + separator + value.substring(value.length - backLength) } + +/** + * Helps to get message from various sign methods present in eth + * @details https://docs.metamask.io/guide/signing-data.html#a-brief-history + */ +export function getSignMessage(params: string[], walletAddress: string) { + // Remove our own address from params, so we are left with message + params.filter(p => p !== walletAddress) + + return params[0] +} diff --git a/wallets/react-wallet-v2/src/utils/WalletConnectUtil.ts b/wallets/react-wallet-v2/src/utils/WalletConnectUtil.ts index aaf43fa..6d1e75a 100644 --- a/wallets/react-wallet-v2/src/utils/WalletConnectUtil.ts +++ b/wallets/react-wallet-v2/src/utils/WalletConnectUtil.ts @@ -1,9 +1,9 @@ import WalletConnectClient from '@walletconnect/client' -export let client: WalletConnectClient | undefined = undefined +export let walletConnectClient: WalletConnectClient -export async function createClient() { - client = await WalletConnectClient.init({ +export async function createWalletConnectClient() { + walletConnectClient = await WalletConnectClient.init({ controller: true, projectId: '8f331b9812e0e5b8f2da2c7203624869', relayUrl: 'wss://relay.walletconnect.com', diff --git a/wallets/react-wallet-v2/src/utils/WalletUtil.ts b/wallets/react-wallet-v2/src/utils/WalletUtil.ts new file mode 100644 index 0000000..527a66c --- /dev/null +++ b/wallets/react-wallet-v2/src/utils/WalletUtil.ts @@ -0,0 +1,17 @@ +import { Wallet } from 'ethers' + +const STORAGE_KEY = 'WALLET_MNEMONIC' + +export let wallet: Wallet + +export function createOrRestoreWallet() { + const mnemonic = localStorage.getItem(STORAGE_KEY) + + 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) + } +} diff --git a/wallets/react-wallet-v2/src/views/SessionProposalModal.tsx b/wallets/react-wallet-v2/src/views/SessionProposalModal.tsx index 8d778d0..d4975b5 100644 --- a/wallets/react-wallet-v2/src/views/SessionProposalModal.tsx +++ b/wallets/react-wallet-v2/src/views/SessionProposalModal.tsx @@ -1,14 +1,13 @@ +import { MAINNET_CHAINS, TChain } from '@/data/EIP155Data' import ModalStore from '@/store/ModalStore' -import WalletStore from '@/store/WalletStore' -import { CHAIN, MAINNET_CHAINS } from '@/utils/EIP155ChainsUtil' -import { client } from '@/utils/WalletConnectUtil' +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 SessionProposalModal() { // Get proposal data and wallet address from store const proposal = ModalStore.state.data?.proposal - const address = WalletStore.state.wallet?.address // Ensure proposal is defined if (!proposal) { @@ -24,21 +23,21 @@ export default function SessionProposalModal() { // Hanlde approve action async function onApprove() { - if (client && proposal && address) { + if (proposal) { const response = { state: { - accounts: chains.map(chain => `${chain}:${address}`) + accounts: chains.map(chain => `${chain}:${wallet.address}`) } } - await client.approve({ proposal, response }) + await walletConnectClient.approve({ proposal, response }) } ModalStore.close() } // Hanlde reject action async function onReject() { - if (client && proposal) { - await client.reject({ proposal }) + if (proposal) { + await walletConnectClient.reject({ proposal }) } ModalStore.close() } @@ -67,7 +66,7 @@ export default function SessionProposalModal() { Blockchains - {chains.map(chain => MAINNET_CHAINS[chain as CHAIN]?.name ?? chain).join(', ')} + {chains.map(chain => MAINNET_CHAINS[chain as TChain]?.name ?? chain).join(', ')} diff --git a/wallets/react-wallet-v2/src/views/SessionRequestModal.tsx b/wallets/react-wallet-v2/src/views/SessionRequestModal.tsx index 7b7f22e..93f3817 100644 --- a/wallets/react-wallet-v2/src/views/SessionRequestModal.tsx +++ b/wallets/react-wallet-v2/src/views/SessionRequestModal.tsx @@ -1,7 +1,7 @@ +import { MAINNET_CHAINS, TChain } from '@/data/EIP155Data' import ModalStore from '@/store/ModalStore' -import WalletStore from '@/store/WalletStore' -import { CHAIN, MAINNET_CHAINS } from '@/utils/EIP155ChainsUtil' -import { client } from '@/utils/WalletConnectUtil' +import { getSignMessage } from '@/utils/HelperUtil' +import { wallet } from '@/utils/WalletUtil' import { Avatar, Button, Col, Container, Divider, Link, Modal, Row, Text } from '@nextui-org/react' import { Fragment } from 'react' @@ -9,23 +9,24 @@ export default function SessionRequestModal() { // Get request and wallet data from store const request = ModalStore.state.data?.request const requestSession = ModalStore.state.data?.requestSession - const { wallet } = WalletStore.state // Ensure request and wallet are defined - if (!request || !requestSession || !wallet) { + if (!request || !requestSession) { return Missing request data } // Get required request data const { chainId } = request - const { method } = request.request + const { method, params } = request.request const { protocol } = requestSession.relay const { name, icons, url } = requestSession.peer.metadata // Handle approve action (logic varies based on request method) async function onApprove() { - if (client && wallet) { - // TODO figure out how to sign different personal messages correctly with ethers + // Handle sign requests + if (['eth_sign', 'personal_sign'].includes(method)) { + const message = getSignMessage(params, wallet.address) + const signedMessage = wallet.signMessage(message) } } @@ -55,7 +56,7 @@ export default function SessionRequestModal() { Blockchain - {MAINNET_CHAINS[chainId as CHAIN]?.name ?? chainId} + {MAINNET_CHAINS[chainId as TChain]?.name ?? chainId} diff --git a/wallets/react-wallet-v2/yarn.lock b/wallets/react-wallet-v2/yarn.lock index 48026dc..3a96e5a 100644 --- a/wallets/react-wallet-v2/yarn.lock +++ b/wallets/react-wallet-v2/yarn.lock @@ -43,14 +43,14 @@ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== -"@eslint/eslintrc@^1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" - integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ== +"@eslint/eslintrc@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.1.0.tgz#583d12dbec5d4f22f333f9669f7d0b7c7815b4d3" + integrity sha512-C1DfL7XX4nPqGd6jcP01W9pVM1HYCuUkFk1432D7F0v3JSlUIeOYn9oCoi3eoLZ+iwBSb29BMFxxny0YrrEZqg== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.2.0" + espree "^9.3.1" globals "^13.9.0" ignore "^4.0.6" import-fresh "^3.2.1" @@ -1465,10 +1465,10 @@ eslint-plugin-react@^7.27.0: semver "^6.3.0" string.prototype.matchall "^4.0.6" -eslint-scope@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153" - integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg== +eslint-scope@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" + integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" @@ -1485,17 +1485,22 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.1.0, eslint-visitor-keys@^3.2.0: +eslint-visitor-keys@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1" integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ== -eslint@8.8.0: - version "8.8.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.8.0.tgz#9762b49abad0cb4952539ffdb0a046392e571a2d" - integrity sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ== +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + +eslint@8.9.0: + version "8.9.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.9.0.tgz#a2a8227a99599adc4342fd9b854cb8d8d6412fdb" + integrity sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q== dependencies: - "@eslint/eslintrc" "^1.0.5" + "@eslint/eslintrc" "^1.1.0" "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" @@ -1503,10 +1508,10 @@ eslint@8.8.0: debug "^4.3.2" doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.1.0" + eslint-scope "^7.1.1" eslint-utils "^3.0.0" - eslint-visitor-keys "^3.2.0" - espree "^9.3.0" + eslint-visitor-keys "^3.3.0" + espree "^9.3.1" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -1531,14 +1536,14 @@ eslint@8.8.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^9.2.0, espree@^9.3.0: - version "9.3.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8" - integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ== +espree@^9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd" + integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ== dependencies: acorn "^8.7.0" acorn-jsx "^5.3.1" - eslint-visitor-keys "^3.1.0" + eslint-visitor-keys "^3.3.0" esquery@^1.4.0: version "1.4.0" @@ -3116,10 +3121,10 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -valtio@1.2.12: - version "1.2.12" - resolved "https://registry.yarnpkg.com/valtio/-/valtio-1.2.12.tgz#14f46f6c90b63c6e4176831c68ab9b729fea38ee" - integrity sha512-TlQkbSma4aAAgs6tQXrvGilMZBVH0q8gbbWxRbo2R43FhkW4lWi45f9jC6gAdJdC40bsNnYyBsYDB9OOKNlqSw== +valtio@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/valtio/-/valtio-1.3.0.tgz#b83cfcca8455940119738659946565d5c0389af8" + integrity sha512-wsE6EDIkt+CNZPNHOxNVzoi026Fyt6ZRT750etZCAvrndcdT3N7Z+SSV4kJQdCwl5gNxsnU4BhP1wFS7cu21oA== dependencies: proxy-compare "2.0.2"