Implement multi wallet support

This commit is contained in:
Ilja 2022-02-21 15:53:39 +02:00
parent c69121d673
commit e82ca094b3
12 changed files with 125 additions and 55 deletions

View File

@ -1,17 +1,18 @@
import SettingsStore from '@/store/SettingsStore' import SettingsStore from '@/store/SettingsStore'
import { addresses } from '@/utils/WalletUtil'
import { useSnapshot } from 'valtio' import { useSnapshot } from 'valtio'
export default function AccountPicker() { export default function AccountPicker() {
const { account } = useSnapshot(SettingsStore.state) const { address } = useSnapshot(SettingsStore.state)
return ( return (
<select <select
value={account} value={address}
onChange={e => SettingsStore.setAccount(e.currentTarget.value)} onChange={e => SettingsStore.setAddress(e.currentTarget.value)}
aria-label="accounts" aria-label="addresses"
> >
<option value={0}>Account 1</option> <option value={addresses[0]}>Account 1</option>
<option value={1}>Account 2</option> <option value={addresses[1]}>Account 2</option>
</select> </select>
) )
} }

View File

@ -1,3 +1,4 @@
import SettingsStore from '@/store/SettingsStore'
import { createWalletConnectClient } from '@/utils/WalletConnectUtil' import { createWalletConnectClient } from '@/utils/WalletConnectUtil'
import { createOrRestoreWallet } from '@/utils/WalletUtil' import { createOrRestoreWallet } from '@/utils/WalletUtil'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
@ -7,7 +8,8 @@ export default function useInitialization() {
const onInitialize = useCallback(async () => { const onInitialize = useCallback(async () => {
try { try {
createOrRestoreWallet() const { addresses } = createOrRestoreWallet()
SettingsStore.setAddress(addresses[0])
await createWalletConnectClient() await createWalletConnectClient()
setInitialized(true) setInitialized(true)
} catch (err: unknown) { } catch (err: unknown) {

View File

@ -3,14 +3,12 @@ import AccountPicker from '@/components/AccountPicker'
import PageHeader from '@/components/PageHeader' import PageHeader from '@/components/PageHeader'
import { EIP155_MAINNET_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 { wallets } from '@/utils/WalletUtil'
import { Text } from '@nextui-org/react' import { Text } from '@nextui-org/react'
import { Fragment } from 'react' import { Fragment } from 'react'
import { useSnapshot } from 'valtio' import { useSnapshot } from 'valtio'
export default function HomePage() { export default function HomePage() {
const { testNets, account } = useSnapshot(SettingsStore.state) const { testNets, address } = useSnapshot(SettingsStore.state)
const addresses = Object.keys(wallets)
return ( return (
<Fragment> <Fragment>
@ -21,7 +19,7 @@ export default function HomePage() {
Mainnets Mainnets
</Text> </Text>
{Object.values(EIP155_MAINNET_CHAINS).map(({ name, logo, rgb }) => ( {Object.values(EIP155_MAINNET_CHAINS).map(({ name, logo, rgb }) => (
<AccountCard key={name} name={name} logo={logo} rgb={rgb} address={addresses[account]} /> <AccountCard key={name} name={name} logo={logo} rgb={rgb} address={address} />
))} ))}
{testNets ? ( {testNets ? (
@ -30,13 +28,7 @@ export default function HomePage() {
Testnets Testnets
</Text> </Text>
{Object.values(EIP155_TEST_CHAINS).map(({ name, logo, rgb }) => ( {Object.values(EIP155_TEST_CHAINS).map(({ name, logo, rgb }) => (
<AccountCard <AccountCard key={name} name={name} logo={logo} rgb={rgb} address={address} />
key={name}
name={name}
logo={logo}
rgb={rgb}
address={addresses[account]}
/>
))} ))}
</Fragment> </Fragment>
) : null} ) : null}

View File

@ -6,7 +6,7 @@ import { Fragment } from 'react'
import { useSnapshot } from 'valtio' import { useSnapshot } from 'valtio'
export default function SettingsPage() { export default function SettingsPage() {
const { testNets } = useSnapshot(SettingsStore.state) const { testNets, address } = useSnapshot(SettingsStore.state)
return ( return (
<Fragment> <Fragment>
@ -15,9 +15,7 @@ export default function SettingsPage() {
Mnemonic Mnemonic
</Text> </Text>
<Card bordered borderWeight="light" css={{ minHeight: '75px' }}> <Card bordered borderWeight="light" css={{ minHeight: '75px' }}>
<Text css={{ fontFamily: '$mono' }}> <Text css={{ fontFamily: '$mono' }}>{wallets[address].mnemonic.phrase}</Text>
{wallets['0xD0712a5018b6F3401b90Cd75C15d95B3353a4088'].mnemonic.phrase}
</Text>
</Card> </Card>
<Text css={{ color: '$yellow500', marginTop: '$5', textAlign: 'center' }}> <Text css={{ color: '$yellow500', marginTop: '$5', textAlign: 'center' }}>

View File

@ -5,7 +5,7 @@ import { proxy } from 'valtio'
*/ */
interface State { interface State {
testNets: boolean testNets: boolean
account: number address: string
} }
/** /**
@ -13,7 +13,7 @@ interface State {
*/ */
const state = proxy<State>({ const state = proxy<State>({
testNets: typeof localStorage !== 'undefined' ? Boolean(localStorage.getItem('TEST_NETS')) : true, testNets: typeof localStorage !== 'undefined' ? Boolean(localStorage.getItem('TEST_NETS')) : true,
account: 0 address: ''
}) })
/** /**
@ -22,8 +22,8 @@ const state = proxy<State>({
const SettingsStore = { const SettingsStore = {
state, state,
setAccount(value: number | string) { setAddress(address: string) {
state.account = Number(value) state.address = address
}, },
toggleTestNets() { toggleTestNets() {

View File

@ -52,3 +52,20 @@ export function getSignTypedDataParamsData(params: string[]) {
return data return data
} }
/**
* Get our address from params checking if params string contains one
* of our wallet addresses
*/
export function getWalletAddressFromParams(addresses: string[], params: any) {
const paramsString = JSON.stringify(params)
let address = ''
addresses.forEach(addr => {
if (paramsString.includes(addr)) {
address = addr
}
})
return address
}

View File

@ -1,12 +1,19 @@
import { EIP155_SIGNING_METHODS } from '@/data/EIP155Data' import { EIP155_CHAINS, EIP155_SIGNING_METHODS, TEIP155Chain } from '@/data/EIP155Data'
import { getSignParamsMessage, getSignTypedDataParamsData } from '@/utils/HelperUtil' import {
getSignParamsMessage,
getSignTypedDataParamsData,
getWalletAddressFromParams
} from '@/utils/HelperUtil'
import { addresses, wallets } from '@/utils/WalletUtil'
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'
import { Wallet } from 'ethers' import { providers } from 'ethers'
export async function approveEIP155Request(request: RequestEvent['request'], wallet: Wallet) { export async function approveEIP155Request(requestEvent: RequestEvent) {
const { method, params, id } = request const { method, params, id } = requestEvent.request
const { chainId } = requestEvent
const wallet = wallets[getWalletAddressFromParams(addresses, params)]
switch (method) { switch (method) {
case EIP155_SIGNING_METHODS.PERSONAL_SIGN: case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
@ -25,8 +32,10 @@ export async function approveEIP155Request(request: RequestEvent['request'], wal
return formatJsonRpcResult(id, signedData) return formatJsonRpcResult(id, signedData)
case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION:
const provider = new providers.JsonRpcProvider(EIP155_CHAINS[chainId as TEIP155Chain].rpc)
const sendTransaction = params[0] const sendTransaction = params[0]
const { hash } = await wallet.sendTransaction(sendTransaction) const connectedWallet = wallet.connect(provider)
const { hash } = await connectedWallet.sendTransaction(sendTransaction)
return formatJsonRpcResult(id, hash) return formatJsonRpcResult(id, hash)
case EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION: case EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION:

View File

@ -1,6 +1,7 @@
import { Wallet } from 'ethers' import { Wallet } from 'ethers'
export let wallets: Record<string, Wallet> export let wallets: Record<string, Wallet>
export let addresses: string[]
export let wallet1: Wallet export let wallet1: Wallet
export let wallet2: Wallet export let wallet2: Wallet
@ -21,4 +22,10 @@ export function createOrRestoreWallet() {
[wallet1.address]: wallet1, [wallet1.address]: wallet1,
[wallet2.address]: wallet2 [wallet2.address]: wallet2
} }
addresses = Object.keys(wallets)
return {
wallets,
addresses
}
} }

View File

@ -1,11 +1,25 @@
import { EIP155_CHAINS, TEIP155Chain } from '@/data/EIP155Data' import { EIP155_CHAINS, TEIP155Chain } from '@/data/EIP155Data'
import ModalStore from '@/store/ModalStore' import ModalStore from '@/store/ModalStore'
import { truncate } from '@/utils/HelperUtil'
import { walletConnectClient } from '@/utils/WalletConnectUtil' import { walletConnectClient } from '@/utils/WalletConnectUtil'
import { wallets } from '@/utils/WalletUtil' import { addresses } from '@/utils/WalletUtil'
import { Avatar, Button, Col, Container, Divider, Link, Modal, Row, Text } from '@nextui-org/react' import {
import { Fragment } from 'react' Avatar,
Button,
Card,
Col,
Container,
Divider,
Link,
Modal,
Row,
Text
} from '@nextui-org/react'
import { Fragment, useState } from 'react'
export default function SessionProposalModal() { export default function SessionProposalModal() {
const [selectedAddresses, setSelectedAddresses] = useState<string[]>([])
// Get proposal data and wallet address from store // Get proposal data and wallet address from store
const proposal = ModalStore.state.data?.proposal const proposal = ModalStore.state.data?.proposal
@ -21,14 +35,29 @@ export default function SessionProposalModal() {
const { methods } = permissions.jsonrpc const { methods } = permissions.jsonrpc
const { protocol } = relay const { protocol } = relay
// Add / remove address from selection
function onSelectAddress(address: string) {
if (selectedAddresses.includes(address)) {
const newAddresses = selectedAddresses.filter(a => a !== address)
setSelectedAddresses(newAddresses)
} else {
setSelectedAddresses([...selectedAddresses, address])
}
}
// Hanlde approve action // Hanlde approve action
async function onApprove() { async function onApprove() {
if (proposal) { if (proposal) {
const accounts: string[] = []
chains.forEach(chain => {
selectedAddresses.forEach(address => {
accounts.push(`${chain}:${address}`)
})
})
const response = { const response = {
state: { state: {
accounts: chains.map( accounts
chain => `${chain}:${wallets['0xD0712a5018b6F3401b90Cd75C15d95B3353a4088'].address}`
)
} }
} }
await walletConnectClient.approve({ proposal, response }) await walletConnectClient.approve({ proposal, response })
@ -92,6 +121,27 @@ export default function SessionProposalModal() {
<Text color="$gray400">{protocol}</Text> <Text color="$gray400">{protocol}</Text>
</Col> </Col>
</Row> </Row>
<Divider y={2} />
<Row>
<Col>
<Text h5>Select Accounts to Connect</Text>
{addresses.map((address, index) => (
<Card
onClick={() => onSelectAddress(address)}
clickable
key={address}
css={{
marginTop: '$5',
backgroundColor: selectedAddresses.includes(address) ? '$green600' : '$accents2'
}}
>
<Text>{`Acc ${index + 1} - ${truncate(address, 19)}`}</Text>
</Card>
))}
</Col>
</Row>
</Container> </Container>
</Modal.Body> </Modal.Body>
@ -99,7 +149,14 @@ export default function SessionProposalModal() {
<Button auto flat color="error" onClick={onReject}> <Button auto flat color="error" onClick={onReject}>
Reject Reject
</Button> </Button>
<Button auto flat color="success" onClick={onApprove}> <Button
auto
flat
color="success"
onClick={onApprove}
disabled={!selectedAddresses.length}
css={{ opacity: selectedAddresses.length ? 1 : 0.4 }}
>
Approve Approve
</Button> </Button>
</Modal.Footer> </Modal.Footer>

View File

@ -3,7 +3,6 @@ import ModalStore from '@/store/ModalStore'
import { truncate } from '@/utils/HelperUtil' import { truncate } 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 { wallets } from '@/utils/WalletUtil'
import { import {
Avatar, Avatar,
Button, Button,
@ -16,7 +15,6 @@ import {
Row, Row,
Text Text
} from '@nextui-org/react' } from '@nextui-org/react'
import { providers } from 'ethers'
import { Fragment, useState } from 'react' import { Fragment, useState } from 'react'
export default function SessionSendTransactionModal() { export default function SessionSendTransactionModal() {
@ -42,10 +40,7 @@ export default function SessionSendTransactionModal() {
async function onApprove() { async function onApprove() {
if (requestEvent) { if (requestEvent) {
setLoading(true) setLoading(true)
const provider = new providers.JsonRpcProvider(EIP155_CHAINS[chainId as TEIP155Chain].rpc) const response = await approveEIP155Request(requestEvent)
const connectedWallet =
wallets['0xD0712a5018b6F3401b90Cd75C15d95B3353a4088'].connect(provider)
const response = await approveEIP155Request(requestEvent.request, connectedWallet)
await walletConnectClient.respond({ await walletConnectClient.respond({
topic: requestEvent.topic, topic: requestEvent.topic,
response response

View File

@ -3,7 +3,6 @@ import ModalStore from '@/store/ModalStore'
import { getSignParamsMessage } 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 { wallets } from '@/utils/WalletUtil'
import { Avatar, Button, Col, Container, Divider, Link, Modal, Row, Text } from '@nextui-org/react' import { Avatar, Button, Col, Container, Divider, Link, Modal, Row, Text } from '@nextui-org/react'
import { Fragment } from 'react' import { Fragment } from 'react'
@ -29,10 +28,7 @@ export default function SessionSignModal() {
// Handle approve action (logic varies based on request method) // Handle approve action (logic varies based on request method)
async function onApprove() { async function onApprove() {
if (requestEvent) { if (requestEvent) {
const response = await approveEIP155Request( const response = await approveEIP155Request(requestEvent)
requestEvent.request,
wallets['0xD0712a5018b6F3401b90Cd75C15d95B3353a4088']
)
await walletConnectClient.respond({ await walletConnectClient.respond({
topic: requestEvent.topic, topic: requestEvent.topic,
response response

View File

@ -3,7 +3,6 @@ import ModalStore from '@/store/ModalStore'
import { getSignTypedDataParamsData } from '@/utils/HelperUtil' import { getSignTypedDataParamsData } 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 { wallets } from '@/utils/WalletUtil'
import { Avatar, Button, Col, Container, Divider, Link, Modal, Row, Text } from '@nextui-org/react' import { Avatar, Button, Col, Container, Divider, Link, Modal, Row, Text } from '@nextui-org/react'
import { Fragment } from 'react' import { Fragment } from 'react'
import { CodeBlock, codepen } from 'react-code-blocks' import { CodeBlock, codepen } from 'react-code-blocks'
@ -30,10 +29,7 @@ export default function SessionSignTypedDataModal() {
// Handle approve action (logic varies based on request method) // Handle approve action (logic varies based on request method)
async function onApprove() { async function onApprove() {
if (requestEvent) { if (requestEvent) {
const response = await approveEIP155Request( const response = await approveEIP155Request(requestEvent)
requestEvent.request,
wallets['0xD0712a5018b6F3401b90Cd75C15d95B3353a4088']
)
await walletConnectClient.respond({ await walletConnectClient.respond({
topic: requestEvent.topic, topic: requestEvent.topic,
response response