feat: Web3Wallet Example (#94)

* feat: v2 wallet

* feat: Web3Wallet sign integration

* chore: adds `core` to package.json

* feat: Web3Wallet Auth integration

* chore: core & web3wallet canary

* chore: rm config

* chore: force redeploy

* chore: rm core & sign-client deps

* fix: rm `sign-client` usage

* refactor: updates README

* feat: adds metadata mock obj & removes relay url param

* refactor: more url mentions

* refactor: rm v2 wallet readme references & uses web3wallet.core...

* refactor: wallet -> web3wallet

* refactor: rm wallet to web3wallet

* fix: adds async to example listeners
This commit is contained in:
Gancho Radkov
2022-12-22 11:19:46 +02:00
committed by GitHub
parent a6ca910a6f
commit 7a3886073b
104 changed files with 11047 additions and 0 deletions
@@ -0,0 +1,51 @@
import ChainCard from '@/components/ChainCard'
import { truncate } from '@/utils/HelperUtil'
import { Avatar, Button, Text, Tooltip } from '@nextui-org/react'
import Image from 'next/image'
import { useState } from 'react'
interface Props {
name: string
logo: string
rgb: string
address: string
}
export default function AccountCard({ name, logo, rgb, address }: Props) {
const [copied, setCopied] = useState(false)
function onCopy() {
navigator?.clipboard?.writeText(address)
setCopied(true)
setTimeout(() => setCopied(false), 1500)
}
return (
<ChainCard rgb={rgb} flexDirection="row" alignItems="center">
<Avatar src={logo} />
<div style={{ flex: 1 }}>
<Text h5 css={{ marginLeft: '$9' }}>
{name}
</Text>
<Text weight="light" size={13} css={{ marginLeft: '$9' }}>
{truncate(address, 19)}
</Text>
</div>
<Tooltip content={copied ? 'Copied!' : 'Copy'} placement="left">
<Button
size="sm"
css={{ minWidth: 'auto', backgroundColor: 'rgba(255, 255, 255, 0.15)' }}
onClick={onCopy}
>
<Image
src={copied ? '/icons/checkmark-icon.svg' : '/icons/copy-icon.svg'}
width={15}
height={15}
alt="copy icon"
/>
</Button>
</Tooltip>
</ChainCard>
)
}
@@ -0,0 +1,28 @@
import SettingsStore from '@/store/SettingsStore'
import { cosmosAddresses } from '@/utils/CosmosWalletUtil'
import { eip155Addresses } from '@/utils/EIP155WalletUtil'
import { nearAddresses } from '@/utils/NearWalletUtil'
import { solanaAddresses } from '@/utils/SolanaWalletUtil'
import { elrondAddresses } from '@/utils/ElrondWalletUtil'
import { useSnapshot } from 'valtio'
export default function AccountPicker() {
const { account } = useSnapshot(SettingsStore.state)
function onSelect(value: string) {
const account = Number(value)
SettingsStore.setAccount(account)
SettingsStore.setEIP155Address(eip155Addresses[account])
SettingsStore.setCosmosAddress(cosmosAddresses[account])
SettingsStore.setSolanaAddress(solanaAddresses[account])
SettingsStore.setNearAddress(nearAddresses[account])
SettingsStore.setElrondAddress(elrondAddresses[account])
}
return (
<select value={account} onChange={e => onSelect(e.currentTarget.value)} aria-label="addresses">
<option value={0}>Account 1</option>
<option value={1}>Account 2</option>
</select>
)
}
@@ -0,0 +1,35 @@
import { truncate } from '@/utils/HelperUtil'
import { Card, Checkbox, Row, Text } from '@nextui-org/react'
/**
* Types
*/
interface IProps {
address: string
index: number
selected: boolean
onSelect: () => void
}
/**
* Component
*/
export default function AccountSelectCard({ address, selected, index, onSelect }: IProps) {
return (
<Card
onClick={onSelect}
clickable
key={address}
css={{
marginTop: '$5',
backgroundColor: selected ? 'rgba(23, 200, 100, 0.2)' : '$accents2'
}}
>
<Row justify="space-between" align="center">
<Checkbox size="lg" color="success" checked={selected} />
<Text>{`${truncate(address, 14)} - Account ${index + 1}`} </Text>
</Row>
</Card>
)
}
@@ -0,0 +1,36 @@
import { Card } from '@nextui-org/react'
import { ReactNode } from 'react'
interface Props {
children: ReactNode | ReactNode[]
rgb: string
flexDirection: 'row' | 'col'
alignItems: 'center' | 'flex-start'
}
export default function ChainCard({ rgb, children, flexDirection, alignItems }: Props) {
return (
<Card
bordered
borderWeight="light"
css={{
borderColor: `rgba(${rgb}, 0.4)`,
boxShadow: `0 0 10px 0 rgba(${rgb}, 0.15)`,
backgroundColor: `rgba(${rgb}, 0.25)`,
marginBottom: '$6',
minHeight: '70px'
}}
>
<Card.Body
css={{
flexDirection,
alignItems,
justifyContent: 'space-between',
overflow: 'hidden'
}}
>
{children}
</Card.Body>
</Card>
)
}
@@ -0,0 +1,89 @@
import Navigation from '@/components/Navigation'
import RouteTransition from '@/components/RouteTransition'
import { Card, Container, Loading } from '@nextui-org/react'
import { Fragment, ReactNode } from 'react'
/**
* Types
*/
interface Props {
initialized: boolean
children: ReactNode | ReactNode[]
}
/**
* Container
*/
export default function Layout({ children, initialized }: Props) {
return (
<Container
display="flex"
justify="center"
alignItems="center"
css={{
width: '100vw',
height: '100vh',
paddingLeft: 0,
paddingRight: 0
}}
>
<Card
bordered={{ '@initial': false, '@xs': true }}
borderWeight={{ '@initial': 'light', '@xs': 'light' }}
css={{
height: '100%',
width: '100%',
justifyContent: initialized ? 'normal' : 'center',
alignItems: initialized ? 'normal' : 'center',
borderRadius: 0,
paddingBottom: 5,
'@xs': {
borderRadius: '$lg',
height: '95vh',
maxWidth: '450px'
}
}}
>
{initialized ? (
<Fragment>
<RouteTransition>
<Card.Body
css={{
display: 'block',
paddingLeft: 2,
paddingRight: 2,
paddingBottom: '40px',
'@xs': {
padding: '20px',
paddingBottom: '40px'
}
}}
>
{children}
</Card.Body>
</RouteTransition>
<Card.Footer
css={{
height: '85px',
minHeight: '85px',
position: 'sticky',
justifyContent: 'flex-end',
alignItems: 'flex-end',
boxShadow: '0 -30px 20px #111111',
backgroundColor: '#111111',
zIndex: 200,
bottom: 0,
left: 0
}}
>
<Navigation />
</Card.Footer>
</Fragment>
) : (
<Loading />
)}
</Card>
</Container>
)
}
@@ -0,0 +1,34 @@
import ModalStore from '@/store/ModalStore'
import SessionProposalModal from '@/views/SessionProposalModal'
import SessionSendTransactionModal from '@/views/SessionSendTransactionModal'
import SessionSignCosmosModal from '@/views/SessionSignCosmosModal'
import SessionRequestModal from '@/views/SessionSignModal'
import SessionSignNearModal from '@/views/SessionSignNearModal'
import SessionSignPolkadotModal from '@/views/SessionSignPolkadotModal'
import SessionSignSolanaModal from '@/views/SessionSignSolanaModal'
import SessionSignElrondModal from '@/views/SessionSignElrondModal'
import SessionSignTypedDataModal from '@/views/SessionSignTypedDataModal'
import SessionUnsuportedMethodModal from '@/views/SessionUnsuportedMethodModal'
import { Modal as NextModal } from '@nextui-org/react'
import { useSnapshot } from 'valtio'
import AuthRequestModal from '@/views/AuthRequestModal'
export default function Modal() {
const { open, view } = useSnapshot(ModalStore.state)
return (
<NextModal blur open={open} style={{ border: '1px solid rgba(139, 139, 139, 0.4)' }}>
{view === 'SessionProposalModal' && <SessionProposalModal />}
{view === 'SessionSignModal' && <SessionRequestModal />}
{view === 'SessionSignTypedDataModal' && <SessionSignTypedDataModal />}
{view === 'SessionSendTransactionModal' && <SessionSendTransactionModal />}
{view === 'SessionUnsuportedMethodModal' && <SessionUnsuportedMethodModal />}
{view === 'SessionSignCosmosModal' && <SessionSignCosmosModal />}
{view === 'SessionSignSolanaModal' && <SessionSignSolanaModal />}
{view === 'SessionSignPolkadotModal' && <SessionSignPolkadotModal />}
{view === 'SessionSignNearModal' && <SessionSignNearModal />}
{view === 'SessionSignElrondModal' && <SessionSignElrondModal />}
{view === 'AuthRequestModal' && <AuthRequestModal />}
</NextModal>
)
}
@@ -0,0 +1,51 @@
import { Avatar, Row } from '@nextui-org/react'
import Image from 'next/image'
import Link from 'next/link'
export default function Navigation() {
return (
<Row justify="space-between" align="center">
<Link href="/" passHref>
<a className="navLink">
<Image alt="accounts icon" src="/icons/accounts-icon.svg" width={27} height={27} />
</a>
</Link>
<Link href="/sessions" passHref>
<a className="navLink">
<Image alt="sessions icon" src="/icons/sessions-icon.svg" width={27} height={27} />
</a>
</Link>
<Link href="/walletconnect" passHref>
<a className="navLink">
<Avatar
size="lg"
css={{ cursor: 'pointer' }}
color="gradient"
icon={
<Image
alt="wallet connect icon"
src="/wallet-connect-logo.svg"
width={30}
height={30}
/>
}
/>
</a>
</Link>
<Link href="/pairings" passHref>
<a className="navLink">
<Image alt="pairings icon" src="/icons/pairings-icon.svg" width={25} height={25} />
</a>
</Link>
<Link href="/settings" passHref>
<a className="navLink">
<Image alt="settings icon" src="/icons/settings-icon.svg" width={27} height={27} />
</a>
</Link>
</Row>
)
}
@@ -0,0 +1,36 @@
import { Col, Divider, Row, Text } from '@nextui-org/react'
import { Fragment, ReactNode } from 'react'
/**
* Types
*/
interface Props {
children?: ReactNode | ReactNode[]
title: string
}
/**
* Component
*/
export default function PageHeader({ title, children }: Props) {
return (
<Fragment>
<Row css={{ marginBottom: '$5', width: '100%' }} justify="space-between" align="center">
<Col>
<Text
h3
weight="bold"
css={{
textGradient: '90deg, $secondary, $primary 30%'
}}
>
{title}
</Text>
</Col>
{children ? <Col css={{ flex: 1 }}>{children}</Col> : null}
</Row>
<Divider css={{ marginBottom: '$10' }} />
</Fragment>
)
}
@@ -0,0 +1,54 @@
import { truncate } from '@/utils/HelperUtil'
import { Avatar, Button, Card, Link, Text, Tooltip } from '@nextui-org/react'
import Image from 'next/image'
/**
* Types
*/
interface IProps {
logo?: string
name?: string
url?: string
onDelete: () => Promise<void>
}
/**
* Component
*/
export default function PairingCard({ logo, name, url, onDelete }: IProps) {
return (
<Card
bordered
borderWeight="light"
css={{
position: 'relative',
marginBottom: '$6',
minHeight: '70px'
}}
>
<Card.Body
css={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
overflow: 'hidden'
}}
>
<Avatar src={logo} />
<div style={{ flex: 1 }}>
<Text h5 css={{ marginLeft: '$9' }}>
{name}
</Text>
<Link href={url} css={{ marginLeft: '$9' }}>
{truncate(url?.split('https://')[1] ?? 'Unknown', 23)}
</Link>
</div>
<Tooltip content="Delete" placement="left">
<Button size="sm" color="error" flat onClick={onDelete} css={{ minWidth: 'auto' }}>
<Image src={'/icons/delete-icon.svg'} width={15} height={15} alt="delete icon" />
</Button>
</Tooltip>
</Card.Body>
</Card>
)
}
@@ -0,0 +1,28 @@
import { Avatar, Col, Link, Row, Text } from '@nextui-org/react'
import { SignClientTypes } from '@walletconnect/types'
/**
* Types
*/
interface IProps {
metadata: SignClientTypes.Metadata
}
/**
* Components
*/
export default function ProjectInfoCard({ metadata }: IProps) {
const { icons, name, url } = metadata
return (
<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>
)
}
@@ -0,0 +1,39 @@
import AccountSelectCard from '@/components/AccountSelectCard'
import { Col, Row, Text } from '@nextui-org/react'
/**
* Types
*/
interface IProps {
chain: string
addresses: string[]
selectedAddresses: string[] | undefined
onSelect: (chain: string, address: string) => void
}
/**
* Component
*/
export default function ProposalSelectSection({
addresses,
selectedAddresses,
chain,
onSelect
}: IProps) {
return (
<Row>
<Col>
<Text h4 css={{ marginTop: '$5' }}>{`Choose ${chain} accounts`}</Text>
{addresses.map((address, index) => (
<AccountSelectCard
key={address}
address={address}
index={index}
onSelect={() => onSelect(chain, address)}
selected={selectedAddresses?.includes(address) ?? false}
/>
))}
</Col>
</Row>
)
}
@@ -0,0 +1,77 @@
import { Button, Loading } from '@nextui-org/react'
import dynamic from 'next/dynamic'
import Image from 'next/image'
import { Fragment, useState } from 'react'
/**
* You can use normal import if you are not within next / ssr environment
* @info https://nextjs.org/docs/advanced-features/dynamic-import
*/
const ReactQrReader = dynamic(() => import('react-qr-reader-es6'), { ssr: false })
/**
* Types
*/
interface IProps {
onConnect: (uri: string) => Promise<void>
}
/**
* Component
*/
export default function QrReader({ onConnect }: IProps) {
const [show, setShow] = useState(false)
const [loading, setLoading] = useState(false)
function onError() {
setShow(false)
}
async function onScan(data: string | null) {
if (data) {
await onConnect(data)
setShow(false)
}
}
function onShowScanner() {
setLoading(true)
setShow(true)
}
return (
<div className="container">
{show ? (
<Fragment>
{loading && <Loading css={{ position: 'absolute' }} />}
<div className="qrVideoMask">
<ReactQrReader
onLoad={() => setLoading(false)}
showViewFinder={false}
onError={onError}
onScan={onScan}
style={{ width: '100%' }}
/>
</div>
</Fragment>
) : (
<div className="container qrPlaceholder">
<Image
src="/icons/qr-icon.svg"
width={100}
height={100}
alt="qr code icon"
className="qrIcon"
/>
<Button
color="gradient"
css={{ marginTop: '$10', width: '100%' }}
onClick={onShowScanner}
>
Scan QR code
</Button>
</div>
)}
</div>
)
}
@@ -0,0 +1,27 @@
import { REGIONALIZED_RELAYER_ENDPOINTS } from '@/data/RelayerRegions'
import SettingsStore from '@/store/SettingsStore'
import { useSnapshot } from 'valtio'
export default function AccountPicker() {
const { relayerRegionURL } = useSnapshot(SettingsStore.state)
function onSelect(value: string) {
SettingsStore.setRelayerRegionURL(value)
}
return (
<select
value={relayerRegionURL}
onChange={e => onSelect(e.currentTarget.value)}
aria-label="relayerRegions"
>
{REGIONALIZED_RELAYER_ENDPOINTS.map((endpoint, index) => {
return (
<option key={index} value={endpoint.value}>
{endpoint.label}
</option>
)
})}
</select>
)
}
@@ -0,0 +1,28 @@
import { Col, Row, Text } from '@nextui-org/react'
import { CodeBlock, codepen } from 'react-code-blocks'
/**
* Types
*/
interface IProps {
data: Record<string, unknown>
}
/**
* Component
*/
export default function RequestDataCard({ data }: IProps) {
return (
<Row>
<Col>
<Text h5>Data</Text>
<CodeBlock
showLineNumbers={false}
text={JSON.stringify(data, null, 2)}
theme={codepen}
language="json"
/>
</Col>
</Row>
)
}
@@ -0,0 +1,52 @@
import { COSMOS_MAINNET_CHAINS, TCosmosChain } from '@/data/COSMOSData'
import { EIP155_CHAINS, TEIP155Chain } from '@/data/EIP155Data'
import { NEAR_TEST_CHAINS, TNearChain } from '@/data/NEARData'
import { SOLANA_CHAINS, TSolanaChain } from '@/data/SolanaData'
import { ELROND_CHAINS, TElrondChain } from '@/data/ElrondData'
import { Col, Divider, Row, Text } from '@nextui-org/react'
import { Fragment } from 'react'
/**
* Types
*/
interface IProps {
chains: string[]
protocol: string
}
/**
* Component
*/
export default function RequesDetailsCard({ chains, protocol }: IProps) {
return (
<Fragment>
<Row>
<Col>
<Text h5>Blockchain(s)</Text>
<Text color="$gray400">
{chains
.map(
chain =>
EIP155_CHAINS[chain as TEIP155Chain]?.name ??
COSMOS_MAINNET_CHAINS[chain as TCosmosChain]?.name ??
SOLANA_CHAINS[chain as TSolanaChain]?.name ??
NEAR_TEST_CHAINS[chain as TNearChain]?.name ??
ELROND_CHAINS[chain as TElrondChain]?.name ??
chain
)
.join(', ')}
</Text>
</Col>
</Row>
<Divider y={2} />
<Row>
<Col>
<Text h5>Relay Protocol</Text>
<Text color="$gray400">{protocol}</Text>
</Col>
</Row>
</Fragment>
)
}
@@ -0,0 +1,22 @@
import { Col, Row, Text } from '@nextui-org/react'
/**
* Types
*/
interface IProps {
methods: string[]
}
/**
* Component
*/
export default function RequestMethodCard({ methods }: IProps) {
return (
<Row>
<Col>
<Text h5>Methods</Text>
<Text color="$gray400">{methods.map(method => method).join(', ')}</Text>
</Col>
</Row>
)
}
@@ -0,0 +1,27 @@
import { Container, Modal, Text } from '@nextui-org/react'
import { Fragment, ReactNode } from 'react'
/**
* Types
*/
interface IProps {
title: string
children: ReactNode | ReactNode[]
}
/**
* Component
*/
export default function RequestModalContainer({ children, title }: IProps) {
return (
<Fragment>
<Modal.Header>
<Text h3>{title}</Text>
</Modal.Header>
<Modal.Body>
<Container css={{ padding: 0 }}>{children}</Container>
</Modal.Body>
</Fragment>
)
}
@@ -0,0 +1,32 @@
import { AnimatePresence, motion } from 'framer-motion'
import { useRouter } from 'next/router'
import { ReactNode } from 'react'
/**
* Types
*/
interface IProps {
children: ReactNode | ReactNode[]
}
/**
* Components
*/
export default function RouteTransition({ children }: IProps) {
const { pathname } = useRouter()
return (
<AnimatePresence exitBeforeEnter>
<motion.div
className="routeTransition"
key={pathname}
initial={{ opacity: 0, translateY: 7 }}
animate={{ opacity: 1, translateY: 0 }}
exit={{ opacity: 0, translateY: 7 }}
transition={{ duration: 0.18 }}
>
{children}
</motion.div>
</AnimatePresence>
)
}
@@ -0,0 +1,55 @@
import { truncate } from '@/utils/HelperUtil'
import { Avatar, Card, Link, Text } from '@nextui-org/react'
import Image from 'next/image'
import NextLink from 'next/link'
/**
* Types
*/
interface IProps {
topic?: string
logo?: string
name?: string
url?: string
}
/**
* Component
*/
export default function SessionCard({ logo, name, url, topic }: IProps) {
return (
<NextLink href={topic ? `/session?topic=${topic}` : '#'} passHref>
<Card
clickable
bordered
borderWeight="light"
css={{
position: 'relative',
marginBottom: '$6',
minHeight: '70px'
}}
>
<Card.Body
css={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
overflow: 'hidden'
}}
>
<Avatar src={logo} />
<div style={{ flex: 1 }}>
<Text h5 css={{ marginLeft: '$9' }}>
{name}
</Text>
<Link href={url} css={{ marginLeft: '$9' }}>
{truncate(url?.split('https://')[1] ?? 'Unknown', 23)}
</Link>
</div>
<Image src={'/icons/arrow-right-icon.svg'} width={20} height={20} alt="session icon" />
</Card.Body>
</Card>
</NextLink>
)
}
@@ -0,0 +1,91 @@
import ChainCard from '@/components/ChainCard'
import { COSMOS_MAINNET_CHAINS } from '@/data/COSMOSData'
import { EIP155_MAINNET_CHAINS, EIP155_TEST_CHAINS } from '@/data/EIP155Data'
import { NEAR_TEST_CHAINS } from '@/data/NEARData'
import { SOLANA_MAINNET_CHAINS, SOLANA_TEST_CHAINS } from '@/data/SolanaData'
import { ELROND_MAINNET_CHAINS, ELROND_TEST_CHAINS } from '@/data/ElrondData'
import { formatChainName } from '@/utils/HelperUtil'
import { Col, Row, Text } from '@nextui-org/react'
import { SessionTypes } from '@walletconnect/types'
import { Fragment } from 'react'
/**
* Utilities
*/
const CHAIN_METADATA = {
...COSMOS_MAINNET_CHAINS,
...SOLANA_MAINNET_CHAINS,
...ELROND_MAINNET_CHAINS,
...EIP155_MAINNET_CHAINS,
...EIP155_TEST_CHAINS,
...SOLANA_TEST_CHAINS,
...NEAR_TEST_CHAINS,
...ELROND_TEST_CHAINS
}
/**
* Types
*/
interface IProps {
namespace: SessionTypes.Namespace
}
/**
* Component
*/
export default function SessionChainCard({ namespace }: IProps) {
const chains: string[] = []
// WIP
namespace.accounts.forEach(account => {
const [type, chain] = account.split(':')
const chainId = `${type}:${chain}`
chains.push(chainId)
})
return (
<Fragment>
{chains.map(chainId => {
const extensionMethods: SessionTypes.Namespace['methods'] = []
const extensionEvents: SessionTypes.Namespace['events'] = []
namespace.extension?.map(({ accounts, methods, events }) => {
accounts.forEach(account => {
const [type, chain] = account.split(':')
const chainId = `${type}:${chain}`
if (chains.includes(chainId)) {
extensionMethods.push(...methods)
extensionEvents.push(...events)
}
})
})
const allMethods = [...namespace.methods, ...extensionMethods]
const allEvents = [...namespace.events, ...extensionEvents]
// @ts-expect-error
const rgb = CHAIN_METADATA[chainId]?.rgb
return (
<ChainCard key={chainId} rgb={rgb ?? ''} flexDirection="col" alignItems="flex-start">
<Text h5 css={{ marginBottom: '$5' }}>
{formatChainName(chainId)}
</Text>
<Row>
<Col>
<Text h6>Methods</Text>
<Text color="$gray300">{allMethods.length ? allMethods.join(', ') : '-'}</Text>
</Col>
</Row>
<Row css={{ marginTop: '$5' }}>
<Col>
<Text h6>Events</Text>
<Text color="$gray300">{allEvents.length ? allEvents.join(', ') : '-'}</Text>
</Col>
</Row>
</ChainCard>
)
})}
</Fragment>
)
}
@@ -0,0 +1,77 @@
import ChainCard from '@/components/ChainCard'
import { COSMOS_MAINNET_CHAINS } from '@/data/COSMOSData'
import { EIP155_MAINNET_CHAINS, EIP155_TEST_CHAINS } from '@/data/EIP155Data'
import { NEAR_TEST_CHAINS } from '@/data/NEARData'
import { SOLANA_MAINNET_CHAINS, SOLANA_TEST_CHAINS } from '@/data/SolanaData'
import { ELROND_MAINNET_CHAINS, ELROND_TEST_CHAINS } from '@/data/ElrondData'
import { formatChainName } from '@/utils/HelperUtil'
import { Col, Row, Text } from '@nextui-org/react'
import { ProposalTypes } from '@walletconnect/types'
import { Fragment } from 'react'
/**
* Utilities
*/
const CHAIN_METADATA = {
...COSMOS_MAINNET_CHAINS,
...SOLANA_MAINNET_CHAINS,
...ELROND_MAINNET_CHAINS,
...EIP155_MAINNET_CHAINS,
...EIP155_TEST_CHAINS,
...SOLANA_TEST_CHAINS,
...NEAR_TEST_CHAINS,
...ELROND_TEST_CHAINS
}
/**
* Types
*/
interface IProps {
requiredNamespace: ProposalTypes.RequiredNamespace
}
/**
* Component
*/
export default function SessionProposalChainCard({ requiredNamespace }: IProps) {
return (
<Fragment>
{requiredNamespace.chains.map(chainId => {
const extensionMethods: ProposalTypes.RequiredNamespace['methods'] = []
const extensionEvents: ProposalTypes.RequiredNamespace['events'] = []
requiredNamespace.extension?.map(({ chains, methods, events }) => {
if (chains.includes(chainId)) {
extensionMethods.push(...methods)
extensionEvents.push(...events)
}
})
const allMethods = [...requiredNamespace.methods, ...extensionMethods]
const allEvents = [...requiredNamespace.events, ...extensionEvents]
// @ts-expect-error
const rgb = CHAIN_METADATA[chainId]?.rgb
return (
<ChainCard key={chainId} rgb={rgb ?? ''} flexDirection="col" alignItems="flex-start">
<Text h5 css={{ marginBottom: '$5' }}>
{formatChainName(chainId)}
</Text>
<Row>
<Col>
<Text h6>Methods</Text>
<Text color="$gray300">{allMethods.length ? allMethods.join(', ') : '-'}</Text>
</Col>
</Row>
<Row css={{ marginTop: '$5' }}>
<Col>
<Text h6>Events</Text>
<Text color="$gray300">{allEvents.length ? allEvents.join(', ') : '-'}</Text>
</Col>
</Row>
</ChainCard>
)
})}
</Fragment>
)
}
@@ -0,0 +1,25 @@
/**
* Types
*/
export type TCosmosChain = keyof typeof COSMOS_MAINNET_CHAINS
/**
* Chains
*/
export const COSMOS_MAINNET_CHAINS = {
'cosmos:cosmoshub-4': {
chainId: 'cosmoshub-4',
name: 'Cosmos Hub',
logo: '/chain-logos/cosmos-cosmoshub-4.png',
rgb: '107, 111, 147',
rpc: ''
}
}
/**
* Methods
*/
export const COSMOS_SIGNING_METHODS = {
COSMOS_SIGN_DIRECT: 'cosmos_signDirect',
COSMOS_SIGN_AMINO: 'cosmos_signAmino'
}
@@ -0,0 +1,90 @@
/**
* @desc Refference list of eip155 chains
* @url https://chainlist.org
*/
/**
* Types
*/
export type TEIP155Chain = keyof typeof EIP155_CHAINS
/**
* Chains
*/
export const EIP155_MAINNET_CHAINS = {
'eip155:1': {
chainId: 1,
name: 'Ethereum',
logo: '/chain-logos/eip155-1.png',
rgb: '99, 125, 234',
rpc: 'https://cloudflare-eth.com/'
},
'eip155:43114': {
chainId: 43114,
name: 'Avalanche C-Chain',
logo: '/chain-logos/eip155-43113.png',
rgb: '232, 65, 66',
rpc: 'https://api.avax.network/ext/bc/C/rpc'
},
'eip155:137': {
chainId: 137,
name: 'Polygon',
logo: '/chain-logos/eip155-137.png',
rgb: '130, 71, 229',
rpc: 'https://polygon-rpc.com/'
},
'eip155:10': {
chainId: 10,
name: 'Optimism',
logo: '/chain-logos/eip155-10.png',
rgb: '235, 0, 25',
rpc: 'https://mainnet.optimism.io'
}
}
export const EIP155_TEST_CHAINS = {
'eip155:5': {
chainId: 5,
name: 'Ethereum Goerli',
logo: '/chain-logos/eip155-1.png',
rgb: '99, 125, 234',
rpc: 'https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161'
},
'eip155:43113': {
chainId: 43113,
name: 'Avalanche Fuji',
logo: '/chain-logos/eip155-43113.png',
rgb: '232, 65, 66',
rpc: 'https://api.avax-test.network/ext/bc/C/rpc'
},
'eip155:80001': {
chainId: 80001,
name: 'Polygon Mumbai',
logo: '/chain-logos/eip155-137.png',
rgb: '130, 71, 229',
rpc: 'https://matic-mumbai.chainstacklabs.com'
},
'eip155:420': {
chainId: 420,
name: 'Optimism Goerli',
logo: '/chain-logos/eip155-10.png',
rgb: '235, 0, 25',
rpc: 'https://goerli.optimism.io'
}
}
export const EIP155_CHAINS = { ...EIP155_MAINNET_CHAINS, ...EIP155_TEST_CHAINS }
/**
* Methods
*/
export const EIP155_SIGNING_METHODS = {
PERSONAL_SIGN: 'personal_sign',
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_SEND_RAW_TRANSACTION: 'eth_sendRawTransaction',
ETH_SEND_TRANSACTION: 'eth_sendTransaction'
}
@@ -0,0 +1,47 @@
/**
* Types
*/
export type TElrondChain = keyof typeof ELROND_MAINNET_CHAINS
/**
* Chains
*/
export const ELROND_MAINNET_CHAINS = {
'elrond:1': {
chainId: '1',
name: 'Elrond',
logo: '/chain-logos/elrond-1.png',
rgb: '43, 45, 46',
rpc: ''
}
}
export const ELROND_TEST_CHAINS = {
'elrond:D': {
chainId: 'D',
name: 'Elrond Devnet',
logo: '/chain-logos/elrond-1.png',
rgb: '43, 45, 46',
rpc: ''
}
// Keep only one Test Chain visible
// 'elrond:T': {
// chainId: 'T',
// name: 'Elrond Testnet',
// logo: '/chain-logos/elrond-1.png',
// rgb: '43, 45, 46',
// rpc: ''
// }
}
export const ELROND_CHAINS = { ...ELROND_MAINNET_CHAINS, ...ELROND_TEST_CHAINS }
/**
* Methods
*/
export const ELROND_SIGNING_METHODS = {
ELROND_SIGN_TRANSACTION: 'erd_signTransaction',
ELROND_SIGN_TRANSACTIONS: 'erd_signTransactions',
ELROND_SIGN_MESSAGE: 'erd_signMessage',
ELROND_SIGN_LOGIN_TOKEN: 'erd_signLoginToken'
}
@@ -0,0 +1,54 @@
/**
* @desc Reference list of NEAR chains
* @url https://chainlist.org
*/
/**
* Types
*/
export type TNearChain = keyof typeof NEAR_TEST_CHAINS
/**
* Chains
*/
export const NEAR_MAINNET_CHAINS = {
// TODO: Dev account creation isn't supported on NEAR Mainnet.
}
interface NearTestChains {
[key: string]: ChainMetadata
}
type ChainMetadata = {
chainId: string
name: string
logo: string
rgb: string
rpc: string
}
export const NEAR_TEST_CHAINS: NearTestChains = {
'near:testnet': {
chainId: 'testnet',
name: 'NEAR Testnet',
logo: '/chain-logos/near.png',
rgb: '99, 125, 234',
rpc: 'https://rpc.testnet.near.org'
}
}
export const NEAR_CHAINS = { ...NEAR_MAINNET_CHAINS, ...NEAR_TEST_CHAINS }
/**
* Methods
*/
export const NEAR_SIGNING_METHODS = {
NEAR_SIGN_IN: 'near_signIn',
NEAR_SIGN_OUT: 'near_signOut',
NEAR_GET_ACCOUNTS: 'near_getAccounts',
NEAR_SIGN_TRANSACTION: 'near_signTransaction',
NEAR_SIGN_AND_SEND_TRANSACTION: 'near_signAndSendTransaction',
NEAR_SIGN_TRANSACTIONS: 'near_signTransactions',
NEAR_SIGN_AND_SEND_TRANSACTIONS: 'near_signAndSendTransactions',
NEAR_VERIFY_OWNER: 'near_verifyOwner'
}
@@ -0,0 +1,35 @@
/**
* Types
*/
export type TPolkadotChain = keyof typeof POLKADOT_MAINNET_CHAINS
/**
* Chains
*/
export const POLKADOT_MAINNET_CHAINS = {
'polkadot:91b171bb158e2d3848fa23a9f1c25182': {
chainId: '91b171bb158e2d3848fa23a9f1c25182',
name: 'Polkadot',
logo: '/chain-logos/polkadot.svg',
rgb: '230, 1, 122',
rpc: ''
}
}
export const POLKADOT_TEST_CHAINS = {
'polkadot:e143f23803ac50e8f6f8e62695d1ce9e': {
chainId: 'e143f23803ac50e8f6f8e62695d1ce9e',
name: 'Polkadot Westend',
logo: '/chain-logos/westend.svg',
rgb: '218, 104, 167',
rpc: ''
}
}
/**
* Methods
*/
export const POLKADOT_SIGNING_METHODS = {
POLKADOT_SIGN_TRANSACTION: 'polkadot_signTransaction',
POLKADOT_SIGN_MESSAGE: 'polkadot_signMessage'
}
@@ -0,0 +1,31 @@
/**
* Types
*/
type RelayerType = {
value: string
label: string
}
/**
* Relayer Regions
*/
export const REGIONALIZED_RELAYER_ENDPOINTS: RelayerType[] = [
{
value: 'wss://relay.walletconnect.com',
label: 'Default'
},
{
value: 'wss://us-east-1.relay.walletconnect.com/',
label: 'US'
},
{
value: 'wss://eu-central-1.relay.walletconnect.com/',
label: 'EU'
},
{
value: 'wss://ap-southeast-1.relay.walletconnect.com/',
label: 'Asia Pacific'
}
]
@@ -0,0 +1,37 @@
/**
* Types
*/
export type TSolanaChain = keyof typeof SOLANA_MAINNET_CHAINS
/**
* Chains
*/
export const SOLANA_MAINNET_CHAINS = {
'solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ': {
chainId: '4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ',
name: 'Solana',
logo: '/chain-logos/solana-4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.png',
rgb: '30, 240, 166',
rpc: ''
}
}
export const SOLANA_TEST_CHAINS = {
'solana:8E9rvCKLFQia2Y35HXjjpWzj8weVo44K': {
chainId: '8E9rvCKLFQia2Y35HXjjpWzj8weVo44K',
name: 'Solana Devnet',
logo: '/chain-logos/solana-4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.png',
rgb: '30, 240, 166',
rpc: ''
}
}
export const SOLANA_CHAINS = { ...SOLANA_MAINNET_CHAINS, ...SOLANA_TEST_CHAINS }
/**
* Methods
*/
export const SOLANA_SIGNING_METHODS = {
SOLANA_SIGN_TRANSACTION: 'solana_signTransaction',
SOLANA_SIGN_MESSAGE: 'solana_signMessage'
}
@@ -0,0 +1,54 @@
import SettingsStore from '@/store/SettingsStore'
import { createOrRestoreCosmosWallet } from '@/utils/CosmosWalletUtil'
import { createOrRestoreEIP155Wallet } from '@/utils/EIP155WalletUtil'
import { createOrRestoreSolanaWallet } from '@/utils/SolanaWalletUtil'
import { createOrRestorePolkadotWallet } from '@/utils/PolkadotWalletUtil'
import { createOrRestoreElrondWallet } from '@/utils/ElrondWalletUtil'
import { createWeb3Wallet } from '@/utils/WalletConnectUtil'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useSnapshot } from 'valtio'
import { createOrRestoreNearWallet } from '@/utils/NearWalletUtil'
export default function useInitialization() {
const [initialized, setInitialized] = useState(false)
const prevRelayerURLValue = useRef<string>('')
const { relayerRegionURL } = useSnapshot(SettingsStore.state)
const onInitialize = useCallback(async () => {
try {
const { eip155Addresses } = createOrRestoreEIP155Wallet()
const { cosmosAddresses } = await createOrRestoreCosmosWallet()
const { solanaAddresses } = await createOrRestoreSolanaWallet()
const { polkadotAddresses } = await createOrRestorePolkadotWallet()
const { nearAddresses } = await createOrRestoreNearWallet()
const { elrondAddresses } = await createOrRestoreElrondWallet()
SettingsStore.setEIP155Address(eip155Addresses[0])
SettingsStore.setCosmosAddress(cosmosAddresses[0])
SettingsStore.setSolanaAddress(solanaAddresses[0])
SettingsStore.setPolkadotAddress(polkadotAddresses[0])
SettingsStore.setNearAddress(nearAddresses[0])
SettingsStore.setElrondAddress(elrondAddresses[0])
prevRelayerURLValue.current = relayerRegionURL
await createWeb3Wallet(relayerRegionURL)
setInitialized(true)
} catch (err: unknown) {
alert(err)
}
}, [relayerRegionURL])
useEffect(() => {
if (!initialized) {
onInitialize()
}
if (prevRelayerURLValue.current !== relayerRegionURL) {
setInitialized(false)
onInitialize()
}
}, [initialized, onInitialize, relayerRegionURL])
return initialized
}
@@ -0,0 +1,111 @@
import { COSMOS_SIGNING_METHODS } from '@/data/COSMOSData'
import { EIP155_SIGNING_METHODS } from '@/data/EIP155Data'
import { SOLANA_SIGNING_METHODS } from '@/data/SolanaData'
import { POLKADOT_SIGNING_METHODS } from '@/data/PolkadotData'
import { ELROND_SIGNING_METHODS } from '@/data/ElrondData'
import ModalStore from '@/store/ModalStore'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { SignClientTypes } from '@walletconnect/types'
import { useCallback, useEffect } from 'react'
import { NEAR_SIGNING_METHODS } from '@/data/NEARData'
import { approveNearRequest } from '@/utils/NearRequestHandlerUtil'
import { Web3WalletTypes } from '@walletconnect/web3wallet'
export default function useWalletConnectEventsManager(initialized: boolean) {
/******************************************************************************
* 1. Open session proposal modal for confirmation / rejection
*****************************************************************************/
const onSessionProposal = useCallback(
(proposal: SignClientTypes.EventArguments['session_proposal']) => {
ModalStore.open('SessionProposalModal', { proposal })
},
[]
)
const onAuthRequest = useCallback((request: Web3WalletTypes.AuthRequest) => {
ModalStore.open('AuthRequestModal', { request })
}, [])
/******************************************************************************
* 3. Open request handling modal based on method that was used
*****************************************************************************/
const onSessionRequest = useCallback(
async (requestEvent: SignClientTypes.EventArguments['session_request']) => {
console.log('session_request', requestEvent)
const { topic, params } = requestEvent
const { request } = params
// const requestSession = signClient.session.get(topic)
const requestSession = web3wallet.engine.signClient.session.get(topic)
switch (request.method) {
case EIP155_SIGNING_METHODS.ETH_SIGN:
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
return ModalStore.open('SessionSignModal', { requestEvent, requestSession })
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:
return ModalStore.open('SessionSignTypedDataModal', { requestEvent, requestSession })
case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION:
case EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION:
return ModalStore.open('SessionSendTransactionModal', { requestEvent, requestSession })
case COSMOS_SIGNING_METHODS.COSMOS_SIGN_DIRECT:
case COSMOS_SIGNING_METHODS.COSMOS_SIGN_AMINO:
return ModalStore.open('SessionSignCosmosModal', { requestEvent, requestSession })
case SOLANA_SIGNING_METHODS.SOLANA_SIGN_MESSAGE:
case SOLANA_SIGNING_METHODS.SOLANA_SIGN_TRANSACTION:
return ModalStore.open('SessionSignSolanaModal', { requestEvent, requestSession })
case POLKADOT_SIGNING_METHODS.POLKADOT_SIGN_MESSAGE:
case POLKADOT_SIGNING_METHODS.POLKADOT_SIGN_TRANSACTION:
return ModalStore.open('SessionSignPolkadotModal', { requestEvent, requestSession })
case NEAR_SIGNING_METHODS.NEAR_SIGN_IN:
case NEAR_SIGNING_METHODS.NEAR_SIGN_OUT:
case NEAR_SIGNING_METHODS.NEAR_SIGN_TRANSACTION:
case NEAR_SIGNING_METHODS.NEAR_SIGN_AND_SEND_TRANSACTION:
case NEAR_SIGNING_METHODS.NEAR_SIGN_TRANSACTIONS:
case NEAR_SIGNING_METHODS.NEAR_SIGN_AND_SEND_TRANSACTIONS:
case NEAR_SIGNING_METHODS.NEAR_VERIFY_OWNER:
return ModalStore.open('SessionSignNearModal', { requestEvent, requestSession })
case ELROND_SIGNING_METHODS.ELROND_SIGN_MESSAGE:
case ELROND_SIGNING_METHODS.ELROND_SIGN_TRANSACTION:
case ELROND_SIGNING_METHODS.ELROND_SIGN_TRANSACTIONS:
case ELROND_SIGNING_METHODS.ELROND_SIGN_LOGIN_TOKEN:
return ModalStore.open('SessionSignElrondModal', { requestEvent, requestSession })
case NEAR_SIGNING_METHODS.NEAR_GET_ACCOUNTS:
return web3wallet.respondSessionRequest({
topic,
response: await approveNearRequest(requestEvent)
})
default:
return ModalStore.open('SessionUnsuportedMethodModal', { requestEvent, requestSession })
}
},
[]
)
/******************************************************************************
* Set up WalletConnect event listeners
*****************************************************************************/
useEffect(() => {
if (initialized) {
// sign
web3wallet.on('session_proposal', onSessionProposal)
web3wallet.on('session_request', onSessionRequest)
// auth
web3wallet.on('auth_request', onAuthRequest)
// TODOs
// signClient.on('session_ping', data => console.log('ping', data))
// signClient.on('session_event', data => console.log('event', data))
// signClient.on('session_update', data => console.log('update', data))
// signClient.on('session_delete', data => console.log('delete', data))
}
}, [initialized, onSessionProposal, onSessionRequest, onAuthRequest])
}
@@ -0,0 +1,63 @@
import { Secp256k1Wallet, StdSignDoc } from '@cosmjs/amino'
import { fromHex } from '@cosmjs/encoding'
import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'
// @ts-expect-error
import { SignDoc } from '@cosmjs/proto-signing/build/codec/cosmos/tx/v1beta1/tx'
import Keyring from 'mnemonic-keyring'
/**
* Constants
*/
const DEFAULT_PATH = "m/44'/118'/0'/0/0"
const DEFAULT_PREFIX = 'cosmos'
/**
* Types
*/
interface IInitArguments {
mnemonic?: string
path?: string
prefix?: string
}
/**
* Library
*/
export default class CosmosLib {
private keyring: Keyring
private directSigner: DirectSecp256k1Wallet
private aminoSigner: Secp256k1Wallet
constructor(keyring: Keyring, directSigner: DirectSecp256k1Wallet, aminoSigner: Secp256k1Wallet) {
this.directSigner = directSigner
this.keyring = keyring
this.aminoSigner = aminoSigner
}
static async init({ mnemonic, path, prefix }: IInitArguments) {
const keyring = await Keyring.init({ mnemonic: mnemonic ?? Keyring.generateMnemonic() })
const privateKey = fromHex(keyring.getPrivateKey(path ?? DEFAULT_PATH))
const directSigner = await DirectSecp256k1Wallet.fromKey(privateKey, prefix ?? DEFAULT_PREFIX)
const aminoSigner = await Secp256k1Wallet.fromKey(privateKey, prefix ?? DEFAULT_PREFIX)
return new CosmosLib(keyring, directSigner, aminoSigner)
}
public getMnemonic() {
return this.keyring.mnemonic
}
public async getAddress() {
const account = await this.directSigner.getAccounts()
return account[0].address
}
public async signDirect(address: string, signDoc: SignDoc) {
return await this.directSigner.signDirect(address, signDoc)
}
public async signAmino(address: string, signDoc: StdSignDoc) {
return await this.aminoSigner.signAmino(address, signDoc)
}
}
@@ -0,0 +1,49 @@
import { providers, Wallet } from 'ethers'
/**
* Types
*/
interface IInitArgs {
mnemonic?: string
}
/**
* Library
*/
export default class EIP155Lib {
wallet: Wallet
constructor(wallet: Wallet) {
this.wallet = wallet
}
static init({ mnemonic }: IInitArgs) {
const wallet = mnemonic ? Wallet.fromMnemonic(mnemonic) : Wallet.createRandom()
return new EIP155Lib(wallet)
}
getMnemonic() {
return this.wallet.mnemonic.phrase
}
getAddress() {
return this.wallet.address
}
signMessage(message: string) {
return this.wallet.signMessage(message)
}
_signTypedData(domain: any, types: any, data: any) {
return this.wallet._signTypedData(domain, types, data)
}
connect(provider: providers.JsonRpcProvider) {
return this.wallet.connect(provider)
}
signTransaction(transaction: providers.TransactionRequest) {
return this.wallet.signTransaction(transaction)
}
}
@@ -0,0 +1,90 @@
import { Transaction, SignableMessage } from '@elrondnetwork/erdjs'
import { Mnemonic, UserSecretKey, UserWallet, UserSigner } from '@elrondnetwork/erdjs-walletcore'
/**
* Types
*/
interface IInitArgs {
mnemonic?: string
}
/**
* Library
*/
export default class ElrondLib {
wallet: UserWallet
mnemonic: Mnemonic
password: string
constructor(mnemonic: Mnemonic) {
this.mnemonic = mnemonic
this.password = 'password' // test purposes only
const secretKey = mnemonic.deriveKey(0)
const secretKeyHex = secretKey.hex()
let fromHex = UserSecretKey.fromString(secretKeyHex)
this.wallet = new UserWallet(fromHex, this.password)
}
static init({ mnemonic }: IInitArgs) {
const mnemonicObj = mnemonic ? Mnemonic.fromString(mnemonic) : Mnemonic.generate()
return new ElrondLib(mnemonicObj)
}
getMnemonic() {
const secretKey = this.mnemonic.getWords().join(' ')
return secretKey
}
getAddress() {
const secretKey = UserWallet.decryptSecretKey(this.wallet.toJSON(), this.password)
const address = secretKey.generatePublicKey().toAddress().bech32()
return address
}
async signMessage(message: string) {
const secretKey = UserWallet.decryptSecretKey(this.wallet.toJSON(), this.password)
const secretKeyHex = secretKey.hex()
const signMessage = new SignableMessage({
message: Buffer.from(message)
})
const signer = new UserSigner(UserSecretKey.fromString(secretKeyHex))
await signer.sign(signMessage)
return { signature: signMessage.signature.hex() }
}
async signTransaction(transaction: any) {
const secretKey = UserWallet.decryptSecretKey(this.wallet.toJSON(), this.password)
const secretKeyHex = secretKey.hex()
const signTransaction = Transaction.fromPlainObject(transaction)
const signer = new UserSigner(UserSecretKey.fromString(secretKeyHex))
await signer.sign(signTransaction)
return { signature: signTransaction.getSignature().hex() }
}
async signTransactions(transactions: any[]) {
const secretKey = UserWallet.decryptSecretKey(this.wallet.toJSON(), this.password)
const secretKeyHex = secretKey.hex()
const signatures = await Promise.all(
transactions.map(async (transaction: any): Promise<any> => {
const signTransaction = Transaction.fromPlainObject(transaction)
const signer = new UserSigner(UserSecretKey.fromString(secretKeyHex))
await signer.sign(signTransaction)
return { signature: signTransaction.getSignature().hex() }
})
)
return { signatures }
}
}
+348
View File
@@ -0,0 +1,348 @@
import {
InMemorySigner,
providers,
keyStores as nearKeyStores,
transactions as nearTransactions,
utils
} from 'near-api-js'
import { AccessKeyView } from 'near-api-js/lib/providers/provider'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { NEAR_TEST_CHAINS, TNearChain } from '@/data/NEARData'
const MAX_ACCOUNTS = 2
interface Account {
accountId: string
publicKey: string
}
interface Transaction {
signerId: string
receiverId: string
actions: Array<nearTransactions.Action>
}
interface CreateTransactionsParams {
chainId: string
transactions: Array<Transaction>
}
interface GetAccountsParams {
topic: string
}
interface SignInParams {
chainId: string
topic: string
permission: nearTransactions.FunctionCallPermission
accounts: Array<Account>
}
interface SignOutParams {
chainId: string
topic: string
accounts: Array<Account>
}
interface SignTransactionsParams {
chainId: string
topic: string
transactions: Array<nearTransactions.Transaction>
}
interface SignAndSendTransactionParams {
chainId: string
topic: string
transaction: nearTransactions.Transaction
}
interface SignAndSendTransactionsParams {
chainId: string
topic: string
transactions: Array<nearTransactions.Transaction>
}
export class NearWallet {
private networkId: string
private keyStore: nearKeyStores.KeyStore
static async init(networkId: string) {
const keyStore = new nearKeyStores.BrowserLocalStorageKeyStore()
const accounts = await keyStore.getAccounts(networkId)
for (let i = 0; i < Math.max(MAX_ACCOUNTS - accounts.length, 0); i += 1) {
const { accountId, keyPair } = await NearWallet.createDevAccount()
await keyStore.setKey(networkId, accountId, keyPair)
}
return new NearWallet(networkId, keyStore)
}
static async createDevAccount() {
const keyPair = utils.KeyPair.fromRandom('ed25519')
const randomNumber = Math.floor(
Math.random() * (99999999999999 - 10000000000000) + 10000000000000
)
const accountId = `dev-${Date.now()}-${randomNumber}`
const publicKey = keyPair.getPublicKey().toString()
return fetch(`https://helper.testnet.near.org/account`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
newAccountId: accountId,
newAccountPublicKey: publicKey
})
}).then(res => {
if (res.ok) {
return {
accountId,
keyPair
}
}
throw new Error('Failed to create NEAR dev account')
})
}
private constructor(networkId: string, keyStore: nearKeyStores.KeyStore) {
this.networkId = networkId
this.keyStore = keyStore
}
getKeyStore() {
return this.keyStore
}
// Retrieve all imported accounts from wallet.
async getAllAccounts(): Promise<Array<Account>> {
const accountIds = await this.keyStore.getAccounts(this.networkId)
return Promise.all(
accountIds.map(async accountId => {
const keyPair = await this.keyStore.getKey(this.networkId, accountId)
return {
accountId,
publicKey: keyPair.getPublicKey().toString()
}
})
)
}
private isAccountsValid(topic: string, accounts: Array<{ accountId: string }>) {
const session = web3wallet.engine.signClient.session.get(topic)
const validAccountIds = session.namespaces.near.accounts.map(accountId => {
return accountId.split(':')[2]
})
return accounts.every(({ accountId }) => {
return validAccountIds.includes(accountId)
})
}
private isTransactionsValid(topic: string, transactions: Array<nearTransactions.Transaction>) {
const accounts = transactions.map(({ signerId }) => ({ accountId: signerId }))
return this.isAccountsValid(topic, accounts)
}
async createTransactions({
chainId,
transactions
}: CreateTransactionsParams): Promise<Array<nearTransactions.Transaction>> {
const provider = new providers.JsonRpcProvider(NEAR_TEST_CHAINS[chainId as TNearChain].rpc)
const txs: Array<nearTransactions.Transaction> = []
const [block, accounts] = await Promise.all([
provider.block({ finality: 'final' }),
this.getAllAccounts()
])
for (let i = 0; i < transactions.length; i += 1) {
const transaction = transactions[i]
const account = accounts.find(x => x.accountId === transaction.signerId)
if (!account) {
throw new Error('Invalid signer id')
}
const accessKey = await provider.query<AccessKeyView>({
request_type: 'view_access_key',
finality: 'final',
account_id: transaction.signerId,
public_key: account.publicKey
})
txs.push(
nearTransactions.createTransaction(
transaction.signerId,
utils.PublicKey.from(account.publicKey),
transaction.receiverId,
accessKey.nonce + i + 1,
transaction.actions,
utils.serialize.base_decode(block.header.hash)
)
)
}
return txs
}
async getAccounts({ topic }: GetAccountsParams): Promise<Array<Account>> {
const session = web3wallet.engine.signClient.session.get(topic)
return Promise.all(
session.namespaces.near.accounts.map(async account => {
const accountId = account.split(':')[2]
const keyPair = await this.keyStore.getKey(this.networkId, accountId)
return {
accountId,
publicKey: keyPair.getPublicKey().toString()
}
})
)
}
async signIn({ chainId, topic, permission, accounts }: SignInParams): Promise<Array<Account>> {
if (!this.isAccountsValid(topic, accounts)) {
throw new Error('Invalid accounts')
}
const result: Array<Account> = []
for (let i = 0; i < accounts.length; i += 1) {
const account = accounts[i]
try {
const [transaction] = await this.createTransactions({
chainId,
transactions: [
{
signerId: account.accountId,
receiverId: account.accountId,
actions: [
nearTransactions.addKey(
utils.PublicKey.from(account.publicKey),
nearTransactions.functionCallAccessKey(
permission.receiverId,
permission.methodNames,
permission.allowance
)
)
]
}
]
})
await this.signAndSendTransaction({ chainId, topic, transaction })
result.push(account)
} catch (err) {
console.log(`Failed to create FunctionCall access key for ${account.accountId}`)
console.error(err)
}
}
return result
}
async signOut({ chainId, topic, accounts }: SignOutParams): Promise<Array<Account>> {
if (!this.isAccountsValid(topic, accounts)) {
throw new Error('Invalid accounts')
}
const result: Array<Account> = []
for (let i = 0; i < accounts.length; i += 1) {
const account = accounts[i]
try {
const [transaction] = await this.createTransactions({
chainId,
transactions: [
{
signerId: account.accountId,
receiverId: account.accountId,
actions: [nearTransactions.deleteKey(utils.PublicKey.from(account.publicKey))]
}
]
})
await this.signAndSendTransaction({ chainId, topic, transaction })
} catch (err) {
console.log(`Failed to remove FunctionCall access key for ${account.accountId}`)
console.error(err)
result.push(account)
}
}
return result
}
async signTransactions({
chainId,
topic,
transactions
}: SignTransactionsParams): Promise<Array<nearTransactions.SignedTransaction>> {
const networkId = chainId.split(':')[1]
const signer = new InMemorySigner(this.keyStore)
const signedTxs: Array<nearTransactions.SignedTransaction> = []
if (!this.isTransactionsValid(topic, transactions)) {
throw new Error('Invalid transactions')
}
for (let i = 0; i < transactions.length; i += 1) {
const transaction = transactions[i]
const [, signedTx] = await nearTransactions.signTransaction(
transaction,
signer,
transaction.signerId,
networkId
)
signedTxs.push(signedTx)
}
return signedTxs
}
async signAndSendTransaction({
chainId,
topic,
transaction
}: SignAndSendTransactionParams): Promise<providers.FinalExecutionOutcome> {
const provider = new providers.JsonRpcProvider(NEAR_TEST_CHAINS[chainId as TNearChain].rpc)
const [signedTx] = await this.signTransactions({
chainId,
topic,
transactions: [transaction]
})
return provider.sendTransaction(signedTx)
}
async signAndSendTransactions({
chainId,
topic,
transactions
}: SignAndSendTransactionsParams): Promise<Array<providers.FinalExecutionOutcome>> {
const provider = new providers.JsonRpcProvider(NEAR_TEST_CHAINS[chainId as TNearChain].rpc)
const signedTxs = await this.signTransactions({ chainId, topic, transactions })
const results: Array<providers.FinalExecutionOutcome> = []
for (let i = 0; i < signedTxs.length; i += 1) {
const signedTx = signedTxs[i]
results.push(await provider.sendTransaction(signedTx))
}
return results
}
}
@@ -0,0 +1,65 @@
import { Keyring } from '@polkadot/keyring'
import { cryptoWaitReady, mnemonicGenerate } from '@polkadot/util-crypto'
import { KeyringPair } from '@polkadot/keyring/types'
import { u8aToHex } from '@polkadot/util'
import { SignerPayloadJSON } from '@polkadot/types/types'
import { TypeRegistry } from '@polkadot/types'
/**
* Types
*/
interface IInitArguments {
mnemonic?: string
}
/**
* Library
*/
export default class PolkadotLib {
keypair: KeyringPair
mnemonic: string
registry: TypeRegistry
constructor(keypair: KeyringPair, mnemonic: string) {
this.keypair = keypair
this.mnemonic = mnemonic
this.registry = new TypeRegistry()
}
static async init({ mnemonic }: IInitArguments) {
// wait till WASM is initialized, in case it is not initialized already (WASM is required for 'sr25519').
await cryptoWaitReady()
// create a keyring to load the account.
const keyring = new Keyring({ type: 'sr25519', ss58Format: 1 })
mnemonic = mnemonic || mnemonicGenerate()
const keypair = keyring.createFromUri(mnemonic)
return new PolkadotLib(keypair, mnemonic)
}
public getAddress() {
return this.keypair.address
}
public getMnemonic() {
return this.mnemonic
}
public async signMessage(message: string) {
return {
signature: u8aToHex(this.keypair.sign(message))
}
}
public async signTransaction(payload: SignerPayloadJSON) {
this.registry.setSignedExtensions(payload.signedExtensions)
const txPayload = this.registry.createType('ExtrinsicPayload', payload, {
version: payload.version
})
const { signature } = txPayload.sign(this.keypair)
return { signature }
}
}
@@ -0,0 +1,61 @@
import { Keypair } from '@solana/web3.js'
import bs58 from 'bs58'
import nacl from 'tweetnacl'
import SolanaWallet, { SolanaSignTransaction } from 'solana-wallet'
/**
* Types
*/
interface IInitArguments {
secretKey?: Uint8Array
}
/**
* Library
*/
export default class SolanaLib {
keypair: Keypair
solanaWallet: SolanaWallet
constructor(keypair: Keypair) {
this.keypair = keypair
this.solanaWallet = new SolanaWallet(Buffer.from(keypair.secretKey))
}
static init({ secretKey }: IInitArguments) {
const keypair = secretKey ? Keypair.fromSecretKey(secretKey) : Keypair.generate()
return new SolanaLib(keypair)
}
public async getAddress() {
return await this.keypair.publicKey.toBase58()
}
public getSecretKey() {
return this.keypair.secretKey.toString()
}
public async signMessage(message: string) {
const signature = nacl.sign.detached(bs58.decode(message), this.keypair.secretKey)
const bs58Signature = bs58.encode(signature)
return { signature: bs58Signature }
}
public async signTransaction(
feePayer: SolanaSignTransaction['feePayer'],
recentBlockhash: SolanaSignTransaction['recentBlockhash'],
instructions: SolanaSignTransaction['instructions'],
partialSignatures?: SolanaSignTransaction['partialSignatures']
) {
const { signature } = await this.solanaWallet.signTransaction(feePayer, {
feePayer,
instructions,
recentBlockhash,
partialSignatures: partialSignatures ?? []
})
return { signature }
}
}
@@ -0,0 +1,26 @@
import Layout from '@/components/Layout'
import Modal from '@/components/Modal'
import useInitialization from '@/hooks/useInitialization'
import useWalletConnectEventsManager from '@/hooks/useWalletConnectEventsManager'
import { createTheme, NextUIProvider } from '@nextui-org/react'
import { AppProps } from 'next/app'
import '../../public/main.css'
export default function App({ Component, pageProps }: AppProps) {
// Step 1 - Initialize wallets and wallet connect client
const initialized = useInitialization()
// Step 2 - Once initialized, set up wallet connect event manager
useWalletConnectEventsManager(initialized)
// render app
return (
<NextUIProvider theme={createTheme({ type: 'dark' })}>
<Layout initialized={initialized}>
<Component {...pageProps} />
</Layout>
<Modal />
</NextUIProvider>
)
}
@@ -0,0 +1,74 @@
import AccountCard from '@/components/AccountCard'
import AccountPicker from '@/components/AccountPicker'
import PageHeader from '@/components/PageHeader'
import { COSMOS_MAINNET_CHAINS } from '@/data/COSMOSData'
import { EIP155_MAINNET_CHAINS, EIP155_TEST_CHAINS } from '@/data/EIP155Data'
import { SOLANA_MAINNET_CHAINS, SOLANA_TEST_CHAINS } from '@/data/SolanaData'
import { POLKADOT_MAINNET_CHAINS, POLKADOT_TEST_CHAINS } from '@/data/PolkadotData'
import { ELROND_MAINNET_CHAINS, ELROND_TEST_CHAINS } from '@/data/ElrondData'
import SettingsStore from '@/store/SettingsStore'
import { Text } from '@nextui-org/react'
import { Fragment } from 'react'
import { useSnapshot } from 'valtio'
import { NEAR_TEST_CHAINS } from '@/data/NEARData'
export default function HomePage() {
const {
testNets,
eip155Address,
cosmosAddress,
solanaAddress,
polkadotAddress,
nearAddress,
elrondAddress
} = useSnapshot(SettingsStore.state)
return (
<Fragment>
<PageHeader title="Accounts">
<AccountPicker />
</PageHeader>
<Text h4 css={{ marginBottom: '$5' }}>
Mainnets
</Text>
{Object.values(EIP155_MAINNET_CHAINS).map(({ name, logo, rgb }) => (
<AccountCard key={name} name={name} logo={logo} rgb={rgb} address={eip155Address} />
))}
{Object.values(COSMOS_MAINNET_CHAINS).map(({ name, logo, rgb }) => (
<AccountCard key={name} name={name} logo={logo} rgb={rgb} address={cosmosAddress} />
))}
{Object.values(SOLANA_MAINNET_CHAINS).map(({ name, logo, rgb }) => (
<AccountCard key={name} name={name} logo={logo} rgb={rgb} address={solanaAddress} />
))}
{Object.values(POLKADOT_MAINNET_CHAINS).map(({ name, logo, rgb }) => (
<AccountCard key={name} name={name} logo={logo} rgb={rgb} address={polkadotAddress} />
))}
{Object.values(ELROND_MAINNET_CHAINS).map(({ name, logo, rgb }) => (
<AccountCard key={name} name={name} logo={logo} rgb={rgb} address={elrondAddress} />
))}
{testNets ? (
<Fragment>
<Text h4 css={{ marginBottom: '$5' }}>
Testnets
</Text>
{Object.values(EIP155_TEST_CHAINS).map(({ name, logo, rgb }) => (
<AccountCard key={name} name={name} logo={logo} rgb={rgb} address={eip155Address} />
))}
{Object.values(SOLANA_TEST_CHAINS).map(({ name, logo, rgb }) => (
<AccountCard key={name} name={name} logo={logo} rgb={rgb} address={solanaAddress} />
))}
{Object.values(POLKADOT_TEST_CHAINS).map(({ name, logo, rgb }) => (
<AccountCard key={name} name={name} logo={logo} rgb={rgb} address={polkadotAddress} />
))}
{Object.values(NEAR_TEST_CHAINS).map(({ name, logo, rgb }) => (
<AccountCard key={name} name={name} logo={logo} rgb={rgb} address={nearAddress} />
))}
{Object.values(ELROND_TEST_CHAINS).map(({ name, logo, rgb }) => (
<AccountCard key={name} name={name} logo={logo} rgb={rgb} address={elrondAddress} />
))}
</Fragment>
) : null}
</Fragment>
)
}
@@ -0,0 +1,41 @@
import PageHeader from '@/components/PageHeader'
import PairingCard from '@/components/PairingCard'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Text } from '@nextui-org/react'
import { getSdkError } from '@walletconnect/utils'
import { Fragment, useState } from 'react'
export default function PairingsPage() {
const [pairings, setPairings] = useState(
web3wallet.engine.signClient.core.pairing.pairings.values
)
async function onDelete(topic: string) {
await web3wallet.disconnectSession({ topic, reason: getSdkError('USER_DISCONNECTED') })
const newPairings = pairings.filter(pairing => pairing.topic !== topic)
setPairings(newPairings)
}
return (
<Fragment>
<PageHeader title="Pairings" />
{pairings.length ? (
pairings.map(pairing => {
const { peerMetadata } = pairing
return (
<PairingCard
key={pairing.topic}
logo={peerMetadata?.icons[0]}
url={peerMetadata?.url}
name={peerMetadata?.name}
onDelete={() => onDelete(pairing.topic)}
/>
)
})
) : (
<Text css={{ opacity: '0.5', textAlign: 'center', marginTop: '$20' }}>No pairings</Text>
)}
</Fragment>
)
}
@@ -0,0 +1,132 @@
import PageHeader from '@/components/PageHeader'
import ProjectInfoCard from '@/components/ProjectInfoCard'
import SessionChainCard from '@/components/SessionChainCard'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Button, Divider, Loading, Row, Text } from '@nextui-org/react'
import { getSdkError } from '@walletconnect/utils'
import { useRouter } from 'next/router'
import { Fragment, useEffect, useState } from 'react'
/**
* Component
*/
export default function SessionPage() {
const [topic, setTopic] = useState('')
const [updated, setUpdated] = useState(new Date())
const { query, replace } = useRouter()
const [loading, setLoading] = useState(false)
useEffect(() => {
if (query?.topic) {
setTopic(query.topic as string)
}
}, [query])
const session = web3wallet.getActiveSessions()[topic]
if (!session) {
return null
}
// Get necessary data from session
const expiryDate = new Date(session.expiry * 1000)
const { namespaces } = session
// Handle deletion of a session
async function onDeleteSession() {
setLoading(true)
await web3wallet.disconnectSession({ topic, reason: getSdkError('USER_DISCONNECTED') })
replace('/sessions')
setLoading(false)
}
async function onSessionPing() {
setLoading(true)
await web3wallet.engine.signClient.ping({ topic })
setLoading(false)
}
async function onSessionEmit() {
setLoading(true)
console.log('baleg')
await web3wallet.emitSessionEvent({
topic,
event: { name: 'chainChanged', data: 'Hello World' },
chainId: 'eip155:1'
})
setLoading(false)
}
const newNs = {
eip155: {
accounts: [
'eip155:1:0x70012948c348CBF00806A3C79E3c5DAdFaAa347B',
'eip155:137:0x70012948c348CBF00806A3C79E3c5DAdFaAa347B'
],
methods: ['personal_sign', 'eth_signTypedData', 'eth_sendTransaction'],
events: []
}
}
async function onSessionUpdate() {
setLoading(true)
await web3wallet.updateSession({ topic, namespaces: newNs })
setUpdated(new Date())
setLoading(false)
}
return (
<Fragment>
<PageHeader title="Session Details" />
<ProjectInfoCard metadata={session.peer.metadata} />
<Divider y={2} />
{Object.keys(namespaces).map(chain => {
return (
<Fragment key={chain}>
<Text h4 css={{ marginBottom: '$5' }}>{`Review ${chain} permissions`}</Text>
<SessionChainCard namespace={namespaces[chain]} />
{/* {renderAccountSelection(chain)} */}
<Divider y={2} />
</Fragment>
)
})}
<Row justify="space-between">
<Text h5>Expiry</Text>
<Text css={{ color: '$gray400' }}>{expiryDate.toDateString()}</Text>
</Row>
<Row justify="space-between">
<Text h5>Last Updated</Text>
<Text css={{ color: '$gray400' }}>{updated.toDateString()}</Text>
</Row>
<Row css={{ marginTop: '$10' }}>
<Button flat css={{ width: '100%' }} color="error" onClick={onDeleteSession}>
{loading ? <Loading size="sm" color="error" /> : 'Delete'}
</Button>
</Row>
<Row css={{ marginTop: '$10' }}>
<Button flat css={{ width: '100%' }} color="primary" onClick={onSessionPing}>
{loading ? <Loading size="sm" color="primary" /> : 'Ping'}
</Button>
</Row>
<Row css={{ marginTop: '$10' }}>
<Button flat css={{ width: '100%' }} color="secondary" onClick={onSessionEmit}>
{loading ? <Loading size="sm" color="secondary" /> : 'Emit'}
</Button>
</Row>
<Row css={{ marginTop: '$10' }}>
<Button flat css={{ width: '100%' }} color="warning" onClick={onSessionUpdate}>
{loading ? <Loading size="sm" color="warning" /> : 'Update'}
</Button>
</Row>
</Fragment>
)
}
@@ -0,0 +1,39 @@
import PageHeader from '@/components/PageHeader'
import SessionCard from '@/components/SessionCard'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Text } from '@nextui-org/react'
import { Fragment, useState } from 'react'
export default function SessionsPage() {
const [sessions, setSessions] = useState(Object.values(web3wallet.getActiveSessions()))
if (!sessions.length) {
return (
<Fragment>
<PageHeader title="Sessions" />
<Text css={{ opacity: '0.5', textAlign: 'center', marginTop: '$20' }}>No sessions</Text>
</Fragment>
)
}
return (
<Fragment>
<PageHeader title="Sessions" />
{sessions.length
? sessions.map(session => {
const { name, icons, url } = session.peer.metadata
return (
<SessionCard
key={session.topic}
topic={session.topic}
name={name}
logo={icons[0]}
url={url}
/>
)
})
: null}
</Fragment>
)
}
@@ -0,0 +1,93 @@
import PageHeader from '@/components/PageHeader'
import RelayRegionPicker from '@/components/RelayRegionPicker'
import SettingsStore from '@/store/SettingsStore'
import { cosmosWallets } from '@/utils/CosmosWalletUtil'
import { eip155Wallets } from '@/utils/EIP155WalletUtil'
import { solanaWallets } from '@/utils/SolanaWalletUtil'
import { elrondWallets } from '@/utils/ElrondWalletUtil'
import { Card, Divider, Row, Switch, Text } from '@nextui-org/react'
import { Fragment } from 'react'
import { useSnapshot } from 'valtio'
import packageJSON from '../../package.json'
export default function SettingsPage() {
const { testNets, eip155Address, cosmosAddress, solanaAddress, elrondAddress } = useSnapshot(
SettingsStore.state
)
return (
<Fragment>
<PageHeader title="Settings" />
<Text h4 css={{ marginBottom: '$5' }}>
Packages
</Text>
<Row justify="space-between" align="center">
<Text color="$gray400">@walletconnect/web3wallet</Text>
<Text color="$gray400">{packageJSON.dependencies['@walletconnect/web3wallet']}</Text>
</Row>
<Row justify="space-between" align="center">
<Text color="$gray400">@walletconnect/utils</Text>
<Text color="$gray400">{packageJSON.dependencies['@walletconnect/utils']}</Text>
</Row>
<Row justify="space-between" align="center">
<Text color="$gray400">@walletconnect/types</Text>
<Text color="$gray400">{packageJSON.devDependencies['@walletconnect/types']}</Text>
</Row>
<Divider y={2} />
<Text h4 css={{ marginBottom: '$5' }}>
Testnets
</Text>
<Row justify="space-between" align="center">
<Switch checked={testNets} onChange={SettingsStore.toggleTestNets} />
<Text>{testNets ? 'Enabled' : 'Disabled'}</Text>
</Row>
<Divider y={2} />
<Row justify="space-between" align="center">
<Text h4 css={{ marginBottom: '$5' }}>
Relayer Region
</Text>
<RelayRegionPicker />
</Row>
<Divider y={2} />
<Text css={{ color: '$yellow500', marginBottom: '$5', textAlign: 'left', padding: 0 }}>
Warning: mnemonics and secret keys are provided for development purposes only and should not
be used elsewhere!
</Text>
<Text h4 css={{ marginTop: '$5', marginBottom: '$5' }}>
EIP155 Mnemonic
</Text>
<Card bordered borderWeight="light" css={{ minHeight: '100px' }}>
<Text css={{ fontFamily: '$mono' }}>{eip155Wallets[eip155Address].getMnemonic()}</Text>
</Card>
<Text h4 css={{ marginTop: '$10', marginBottom: '$5' }}>
Cosmos Mnemonic
</Text>
<Card bordered borderWeight="light" css={{ minHeight: '100px' }}>
<Text css={{ fontFamily: '$mono' }}>{cosmosWallets[cosmosAddress].getMnemonic()}</Text>
</Card>
<Text h4 css={{ marginTop: '$10', marginBottom: '$5' }}>
Solana Secret Key
</Text>
<Card bordered borderWeight="light" css={{ minHeight: '215px', wordWrap: 'break-word' }}>
<Text css={{ fontFamily: '$mono' }}>{solanaWallets[solanaAddress].getSecretKey()}</Text>
</Card>
<Text h4 css={{ marginTop: '$10', marginBottom: '$5' }}>
Elrond Mnemonic
</Text>
<Card bordered borderWeight="light" css={{ minHeight: '215px', wordWrap: 'break-word' }}>
<Text css={{ fontFamily: '$mono' }}>{elrondWallets[elrondAddress].getMnemonic()}</Text>
</Card>
</Fragment>
)
}
@@ -0,0 +1,54 @@
import PageHeader from '@/components/PageHeader'
import QrReader from '@/components/QrReader'
import { pair } from '@/utils/WalletConnectUtil'
import { Button, Input, Loading, Text } from '@nextui-org/react'
import { Fragment, useState } from 'react'
export default function WalletConnectPage() {
const [uri, setUri] = useState('')
const [loading, setLoading] = useState(false)
async function onConnect(uri: string) {
try {
setLoading(true)
await pair({ uri })
} catch (err: unknown) {
alert(err)
} finally {
setUri('')
setLoading(false)
}
}
return (
<Fragment>
<PageHeader title="WalletConnect" />
<QrReader onConnect={onConnect} />
<Text size={13} css={{ textAlign: 'center', marginTop: '$10', marginBottom: '$10' }}>
or use walletconnect uri
</Text>
<Input
css={{ width: '100%' }}
bordered
aria-label="wc url connect input"
placeholder="e.g. wc:a281567bb3e4..."
onChange={e => setUri(e.target.value)}
value={uri}
contentRight={
<Button
size="xs"
disabled={!uri}
css={{ marginLeft: -60 }}
onClick={() => onConnect(uri)}
color="gradient"
>
{loading ? <Loading size="sm" /> : 'Connect'}
</Button>
}
/>
</Fragment>
)
}
@@ -0,0 +1,56 @@
import { SessionTypes, SignClientTypes } from '@walletconnect/types'
import { Web3WalletTypes } from '@walletconnect/web3wallet'
import { proxy } from 'valtio'
/**
* Types
*/
interface ModalData {
proposal?: SignClientTypes.EventArguments['session_proposal']
requestEvent?: SignClientTypes.EventArguments['session_request']
requestSession?: SessionTypes.Struct
request?: Web3WalletTypes.AuthRequest
}
interface State {
open: boolean
view?:
| 'SessionProposalModal'
| 'SessionSignModal'
| 'SessionSignTypedDataModal'
| 'SessionSendTransactionModal'
| 'SessionUnsuportedMethodModal'
| 'SessionSignCosmosModal'
| 'SessionSignSolanaModal'
| 'SessionSignPolkadotModal'
| 'SessionSignNearModal'
| 'SessionSignElrondModal'
| 'AuthRequestModal'
data?: ModalData
}
/**
* State
*/
const state = proxy<State>({
open: false
})
/**
* Store / Actions
*/
const ModalStore = {
state,
open(view: State['view'], data: State['data']) {
state.view = view
state.data = data
state.open = true
},
close() {
state.open = false
}
}
export default ModalStore
@@ -0,0 +1,79 @@
import { proxy } from 'valtio'
/**
* Types
*/
interface State {
testNets: boolean
account: number
eip155Address: string
cosmosAddress: string
solanaAddress: string
polkadotAddress: string
nearAddress: string
elrondAddress: string
relayerRegionURL: string
}
/**
* State
*/
const state = proxy<State>({
testNets: typeof localStorage !== 'undefined' ? Boolean(localStorage.getItem('TEST_NETS')) : true,
account: 0,
eip155Address: '',
cosmosAddress: '',
solanaAddress: '',
polkadotAddress: '',
nearAddress: '',
elrondAddress: '',
relayerRegionURL: ''
})
/**
* Store / Actions
*/
const SettingsStore = {
state,
setAccount(value: number) {
state.account = value
},
setEIP155Address(eip155Address: string) {
state.eip155Address = eip155Address
},
setCosmosAddress(cosmosAddresses: string) {
state.cosmosAddress = cosmosAddresses
},
setSolanaAddress(solanaAddress: string) {
state.solanaAddress = solanaAddress
},
setPolkadotAddress(polkadotAddress: string) {
state.polkadotAddress = polkadotAddress
},
setNearAddress(nearAddress: string) {
state.nearAddress = nearAddress
},
setRelayerRegionURL(relayerRegionURL: string) {
state.relayerRegionURL = relayerRegionURL
},
setElrondAddress(elrondAddress: string) {
state.elrondAddress = elrondAddress
},
toggleTestNets() {
state.testNets = !state.testNets
if (state.testNets) {
localStorage.setItem('TEST_NETS', 'YES')
} else {
localStorage.removeItem('TEST_NETS')
}
}
}
export default SettingsStore
@@ -0,0 +1,40 @@
import { COSMOS_SIGNING_METHODS } from '@/data/COSMOSData'
import { cosmosAddresses, cosmosWallets } from '@/utils/CosmosWalletUtil'
import { getWalletAddressFromParams } from '@/utils/HelperUtil'
import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'
import { SignClientTypes } from '@walletconnect/types'
import { getSdkError } from '@walletconnect/utils'
import { parseSignDocValues } from 'cosmos-wallet'
export async function approveCosmosRequest(
requestEvent: SignClientTypes.EventArguments['session_request']
) {
const { params, id } = requestEvent
const { request } = params
const wallet = cosmosWallets[getWalletAddressFromParams(cosmosAddresses, params)]
switch (request.method) {
case COSMOS_SIGNING_METHODS.COSMOS_SIGN_DIRECT:
const signedDirect = await wallet.signDirect(
request.params.signerAddress,
parseSignDocValues(request.params.signDoc)
)
return formatJsonRpcResult(id, signedDirect.signature)
case COSMOS_SIGNING_METHODS.COSMOS_SIGN_AMINO:
const signedAmino = await wallet.signAmino(
request.params.signerAddress,
request.params.signDoc
)
return formatJsonRpcResult(id, signedAmino.signature)
default:
throw new Error(getSdkError('INVALID_METHOD').message)
}
}
export function rejectCosmosRequest(request: SignClientTypes.EventArguments['session_request']) {
const { id } = request
return formatJsonRpcError(id, getSdkError('USER_REJECTED_METHODS').message)
}
@@ -0,0 +1,43 @@
import CosmosLib from '@/lib/CosmosLib'
export let wallet1: CosmosLib
export let wallet2: CosmosLib
export let cosmosWallets: Record<string, CosmosLib>
export let cosmosAddresses: string[]
let address1: string
let address2: string
/**
* Utilities
*/
export async function createOrRestoreCosmosWallet() {
const mnemonic1 = localStorage.getItem('COSMOS_MNEMONIC_1')
const mnemonic2 = localStorage.getItem('COSMOS_MNEMONIC_2')
if (mnemonic1 && mnemonic2) {
wallet1 = await CosmosLib.init({ mnemonic: mnemonic1 })
wallet2 = await CosmosLib.init({ mnemonic: mnemonic2 })
} else {
wallet1 = await CosmosLib.init({})
wallet2 = await CosmosLib.init({})
// Don't store mnemonic in local storage in a production project!
localStorage.setItem('COSMOS_MNEMONIC_1', wallet1.getMnemonic())
localStorage.setItem('COSMOS_MNEMONIC_2', wallet2.getMnemonic())
}
address1 = await wallet1.getAddress()
address2 = await wallet2.getAddress()
cosmosWallets = {
[address1]: wallet1,
[address2]: wallet2
}
cosmosAddresses = Object.keys(cosmosWallets)
return {
cosmosWallets,
cosmosAddresses
}
}
@@ -0,0 +1,57 @@
import { EIP155_CHAINS, EIP155_SIGNING_METHODS, TEIP155Chain } from '@/data/EIP155Data'
import { eip155Addresses, eip155Wallets } from '@/utils/EIP155WalletUtil'
import {
getSignParamsMessage,
getSignTypedDataParamsData,
getWalletAddressFromParams
} from '@/utils/HelperUtil'
import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'
import { SignClientTypes } from '@walletconnect/types'
import { getSdkError } from '@walletconnect/utils'
import { providers } from 'ethers'
export async function approveEIP155Request(
requestEvent: SignClientTypes.EventArguments['session_request']
) {
const { params, id } = requestEvent
const { chainId, request } = params
const wallet = eip155Wallets[getWalletAddressFromParams(eip155Addresses, params)]
switch (request.method) {
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
case EIP155_SIGNING_METHODS.ETH_SIGN:
const message = getSignParamsMessage(request.params)
const signedMessage = await wallet.signMessage(message)
return formatJsonRpcResult(id, signedMessage)
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(request.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)
case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION:
const provider = new providers.JsonRpcProvider(EIP155_CHAINS[chainId as TEIP155Chain].rpc)
const sendTransaction = request.params[0]
const connectedWallet = wallet.connect(provider)
const { hash } = await connectedWallet.sendTransaction(sendTransaction)
return formatJsonRpcResult(id, hash)
case EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION:
const signTransaction = request.params[0]
const signature = await wallet.signTransaction(signTransaction)
return formatJsonRpcResult(id, signature)
default:
throw new Error(getSdkError('INVALID_METHOD').message)
}
}
export function rejectEIP155Request(request: SignClientTypes.EventArguments['session_request']) {
const { id } = request
return formatJsonRpcError(id, getSdkError('USER_REJECTED_METHODS').message)
}
@@ -0,0 +1,43 @@
import EIP155Lib from '@/lib/EIP155Lib'
export let wallet1: EIP155Lib
export let wallet2: EIP155Lib
export let eip155Wallets: Record<string, EIP155Lib>
export let eip155Addresses: string[]
let address1: string
let address2: string
/**
* Utilities
*/
export function createOrRestoreEIP155Wallet() {
const mnemonic1 = localStorage.getItem('EIP155_MNEMONIC_1')
const mnemonic2 = localStorage.getItem('EIP155_MNEMONIC_2')
if (mnemonic1 && mnemonic2) {
wallet1 = EIP155Lib.init({ mnemonic: mnemonic1 })
wallet2 = EIP155Lib.init({ mnemonic: mnemonic2 })
} else {
wallet1 = EIP155Lib.init({})
wallet2 = EIP155Lib.init({})
// Don't store mnemonic in local storage in a production project!
localStorage.setItem('EIP155_MNEMONIC_1', wallet1.getMnemonic())
localStorage.setItem('EIP155_MNEMONIC_2', wallet2.getMnemonic())
}
address1 = wallet1.getAddress()
address2 = wallet2.getAddress()
eip155Wallets = {
[address1]: wallet1,
[address2]: wallet2
}
eip155Addresses = Object.keys(eip155Wallets)
return {
eip155Wallets,
eip155Addresses
}
}
@@ -0,0 +1,54 @@
import { ELROND_SIGNING_METHODS } from '@/data/ElrondData'
import { getWalletAddressFromParams } from '@/utils/HelperUtil'
import { elrondAddresses, elrondWallets } from '@/utils/ElrondWalletUtil'
import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'
import { SignClientTypes } from '@walletconnect/types'
import { getSdkError } from '@walletconnect/utils'
export async function approveElrondRequest(
requestEvent: SignClientTypes.EventArguments['session_request']
) {
const { params, id } = requestEvent
const { request } = params
const account = getWalletAddressFromParams(elrondAddresses, params)
const wallet = elrondWallets[account]
switch (request.method) {
case ELROND_SIGNING_METHODS.ELROND_SIGN_MESSAGE:
const signedMessage = await wallet.signMessage(request.params.message)
return formatJsonRpcResult(id, signedMessage)
case ELROND_SIGNING_METHODS.ELROND_SIGN_TRANSACTION:
const signTransaction = request.params.transaction
// Transactions must be signed with the Sender's Private Key before submitting them to the Elrond Network.
// Signing is performed with the Ed25519 algorithm.
const signature = await wallet.signTransaction(signTransaction)
return formatJsonRpcResult(id, signature)
case ELROND_SIGNING_METHODS.ELROND_SIGN_TRANSACTIONS:
// Elrond Allows for a Batch of Transactions to be signed
const signTransactions = request.params.transactions
const signatures = await wallet.signTransactions(signTransactions)
return formatJsonRpcResult(id, signatures)
case ELROND_SIGNING_METHODS.ELROND_SIGN_LOGIN_TOKEN:
// Sometimes a dApp (and its backend) might want to reliably assign an off-chain user identity to an Elrond address.
// On this purpose, the signing providers allow a login token to be used within the login flow - this token is signed using the wallet of the user.
// Afterwards, a backend application would normally verify the signature of the token
const message = `${account}${request.params.token}{}`
const { signature: signedLoginToken } = await wallet.signMessage(message)
return formatJsonRpcResult(id, { signature: signedLoginToken })
default:
throw new Error(getSdkError('UNSUPPORTED_METHODS').message)
}
}
export function rejectElrondRequest(request: SignClientTypes.EventArguments['session_request']) {
const { id } = request
return formatJsonRpcError(id, getSdkError('USER_REJECTED_METHODS').message)
}
@@ -0,0 +1,43 @@
import ElrondLib from '@/lib/ElrondLib'
export let wallet1: ElrondLib
export let wallet2: ElrondLib
export let elrondWallets: Record<string, ElrondLib>
export let elrondAddresses: string[]
let address1: string
let address2: string
/**
* Utilities
*/
export async function createOrRestoreElrondWallet() {
const mnemonic1 = localStorage.getItem('ELROND_MNEMONIC_1')
const mnemonic2 = localStorage.getItem('ELROND_MNEMONIC_2')
if (mnemonic1 && mnemonic2) {
wallet1 = await ElrondLib.init({ mnemonic: mnemonic1 })
wallet2 = await ElrondLib.init({ mnemonic: mnemonic2 })
} else {
wallet1 = await ElrondLib.init({})
wallet2 = await ElrondLib.init({})
// Don't store mnemonic in local storage in a production project!
localStorage.setItem('ELROND_MNEMONIC_1', wallet1.getMnemonic())
localStorage.setItem('ELROND_MNEMONIC_2', wallet2.getMnemonic())
}
address1 = await wallet1.getAddress()
address2 = await wallet2.getAddress()
elrondWallets = {
[address1]: wallet1,
[address2]: wallet2
}
elrondAddresses = Object.keys(elrondWallets)
return {
elrondWallets,
elrondAddresses
}
}
@@ -0,0 +1,132 @@
import { COSMOS_MAINNET_CHAINS, TCosmosChain } from '@/data/COSMOSData'
import { EIP155_CHAINS, TEIP155Chain } from '@/data/EIP155Data'
import { NEAR_TEST_CHAINS, TNearChain } from '@/data/NEARData'
import { SOLANA_CHAINS, TSolanaChain } from '@/data/SolanaData'
import { ELROND_CHAINS, TElrondChain } from '@/data/ElrondData'
import { utils } from 'ethers'
/**
* Truncates string (in the middle) via given lenght value
*/
export function truncate(value: string, length: number) {
if (value?.length <= length) {
return value
}
const separator = '...'
const stringLength = length - separator.length
const frontLength = Math.ceil(stringLength / 2)
const backLength = Math.floor(stringLength / 2)
return value.substring(0, frontLength) + separator + value.substring(value.length - backLength)
}
/**
* Converts hex to utf8 string if it is valid bytes
*/
export function convertHexToUtf8(value: string) {
if (utils.isHexString(value)) {
return utils.toUtf8String(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
}
/**
* 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.toLowerCase().includes(addr.toLowerCase())) {
address = addr
}
})
return address
}
/**
* Check if chain is part of EIP155 standard
*/
export function isEIP155Chain(chain: string) {
return chain.includes('eip155')
}
/**
* Check if chain is part of COSMOS standard
*/
export function isCosmosChain(chain: string) {
return chain.includes('cosmos')
}
/**
* Check if chain is part of SOLANA standard
*/
export function isSolanaChain(chain: string) {
return chain.includes('solana')
}
/**
* Check if chain is part of POLKADOT standard
*/
export function isPolkadotChain(chain: string) {
return chain.includes('polkadot')
}
/**
* Check if chain is part of NEAR standard
*/
export function isNearChain(chain: string) {
return chain.includes('near')
}
/**
* Check if chain is part of ELROND standard
*/
export function isElrondChain(chain: string) {
return chain.includes('elrond')
}
/**
* Formats chainId to its name
*/
export function formatChainName(chainId: string) {
return (
EIP155_CHAINS[chainId as TEIP155Chain]?.name ??
COSMOS_MAINNET_CHAINS[chainId as TCosmosChain]?.name ??
SOLANA_CHAINS[chainId as TSolanaChain]?.name ??
NEAR_TEST_CHAINS[chainId as TNearChain]?.name ??
ELROND_CHAINS[chainId as TElrondChain]?.name ??
chainId
)
}
@@ -0,0 +1,197 @@
import { NEAR_SIGNING_METHODS, NEAR_TEST_CHAINS } from '@/data/NEARData'
import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'
import { SignClientTypes } from '@walletconnect/types'
import { getSdkError } from '@walletconnect/utils'
import { nearWallet } from '@/utils/NearWalletUtil'
import { InMemorySigner, transactions, utils, Connection } from 'near-api-js'
import { Transaction } from '@near-wallet-selector/core'
import { createAction } from '@near-wallet-selector/wallet-utils'
export async function approveNearRequest(
requestEvent: SignClientTypes.EventArguments['session_request']
) {
const { params, id, topic } = requestEvent
const { chainId, request } = params
switch (request.method) {
case NEAR_SIGNING_METHODS.NEAR_SIGN_IN: {
console.log('approve', { id, params })
if (!chainId) {
throw new Error('Invalid chain id')
}
const accounts = await nearWallet.signIn({
chainId,
topic,
permission: request.params.permission,
accounts: request.params.accounts
})
return formatJsonRpcResult(id, accounts)
}
case NEAR_SIGNING_METHODS.NEAR_SIGN_OUT: {
console.log('approve', { id, params })
if (!chainId) {
throw new Error('Invalid chain id')
}
const accounts = await nearWallet.signOut({
chainId,
topic,
accounts: request.params.accounts
})
return formatJsonRpcResult(id, accounts)
}
case NEAR_SIGNING_METHODS.NEAR_GET_ACCOUNTS: {
console.log('approve', { id, params })
if (!chainId) {
throw new Error('Invalid chain id')
}
const accounts = await nearWallet.getAccounts({ topic })
return formatJsonRpcResult(id, accounts)
}
case NEAR_SIGNING_METHODS.NEAR_SIGN_TRANSACTION: {
console.log('approve', { id, params })
if (!chainId) {
throw new Error('Invalid chain id')
}
const [signedTx] = await nearWallet.signTransactions({
chainId,
topic,
transactions: [transactions.Transaction.decode(Buffer.from(request.params.transaction))]
})
return formatJsonRpcResult(id, signedTx.encode())
}
case NEAR_SIGNING_METHODS.NEAR_SIGN_AND_SEND_TRANSACTION: {
console.log('approve', { id, params })
if (!chainId) {
throw new Error('Invalid chain id')
}
const [transaction] = await nearWallet.createTransactions({
chainId,
transactions: [
{
...params.request.params.transaction,
actions: params.request.params.transaction.actions.map(createAction)
}
]
})
const result = await nearWallet.signAndSendTransaction({
chainId,
topic,
transaction
})
return formatJsonRpcResult(id, result)
}
case NEAR_SIGNING_METHODS.NEAR_SIGN_TRANSACTIONS: {
console.log('approve', { id, params })
if (!chainId) {
throw new Error('Invalid chain id')
}
const signedTxs = await nearWallet.signTransactions({
chainId,
topic,
transactions: params.request.params.transactions.map((tx: Uint8Array) => {
return transactions.Transaction.decode(Buffer.from(tx))
})
})
return formatJsonRpcResult(
id,
signedTxs.map(x => x.encode())
)
}
case NEAR_SIGNING_METHODS.NEAR_VERIFY_OWNER: {
console.log('approve', { id, params })
if (!chainId) {
throw new Error('Invalid chain id')
}
const accounts = await nearWallet.getAllAccounts()
const account = accounts.find(acc => acc.accountId === params.request.params.accountId)
if (!account) {
throw new Error(`Did not find account with id: ${params.request.params.accountId}`)
}
if (!NEAR_TEST_CHAINS[chainId]) {
throw new Error('Invalid chain id')
}
const signer = new InMemorySigner(nearWallet.getKeyStore())
const networkId = chainId.split(':')[1]
const connection = Connection.fromConfig({
networkId,
provider: { type: 'JsonRpcProvider', args: { url: NEAR_TEST_CHAINS[chainId].rpc } },
signer
})
const blockInfo = await connection.provider.block({ finality: 'final' })
const publicKey = utils.PublicKey.from(account.publicKey)
const data = {
accountId: account.accountId,
message: params.request.params.message,
blockId: blockInfo.header.hash,
publicKey: Buffer.from(publicKey.data).toString('base64'),
keyType: publicKey.keyType
}
const encoded = new Uint8Array(Buffer.from(JSON.stringify(data)))
const signed = await signer.signMessage(encoded, account.accountId, networkId)
return formatJsonRpcResult(id, {
...data,
signature: Buffer.from(signed.signature).toString('base64'),
keyType: signed.publicKey.keyType
})
}
case NEAR_SIGNING_METHODS.NEAR_SIGN_AND_SEND_TRANSACTIONS: {
console.log('approve', { id, params })
if (!chainId) {
throw new Error('Invalid chain id')
}
const transactions = await nearWallet.createTransactions({
chainId,
transactions: params.request.params.transactions.map((transaction: Transaction) => ({
...transaction,
actions: transaction.actions.map(createAction)
}))
})
const result = await nearWallet.signAndSendTransactions({
chainId,
topic,
transactions
})
return formatJsonRpcResult(id, result)
}
default:
throw new Error(getSdkError('INVALID_METHOD').message)
}
}
export function rejectNearRequest(request: SignClientTypes.EventArguments['session_request']) {
const { id } = request
return formatJsonRpcError(id, getSdkError('USER_REJECTED_METHODS').message)
}
@@ -0,0 +1,21 @@
import { NearWallet } from '@/lib/NearLib'
export let nearAddresses: string[]
export let nearWallet: NearWallet
/**
* Utilities
*/
export async function createOrRestoreNearWallet() {
// NEAR only supports dev accounts in testnet.
const wallet = await NearWallet.init('testnet')
const accounts = await wallet.getAllAccounts()
nearAddresses = accounts.map(x => x.accountId)
nearWallet = wallet
return {
nearWallet,
nearAddresses
}
}
@@ -0,0 +1,38 @@
import { POLKADOT_SIGNING_METHODS } from '@/data/PolkadotData'
import { getWalletAddressFromParams } from '@/utils/HelperUtil'
import { getPolkadotWallet, polkadotAddresses, polkadotWallets } from '@/utils/PolkadotWalletUtil'
import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'
import { SignClientTypes } from '@walletconnect/types'
import { getSdkError } from '@walletconnect/utils'
export async function approvePolkadotRequest(
requestEvent: SignClientTypes.EventArguments['session_request']
) {
const { params, id } = requestEvent
const { request } = params
const address = request.params?.address
const wallet = getPolkadotWallet(address)
if (!wallet) {
throw new Error('Polkadot wallet does not exist')
}
switch (request.method) {
case POLKADOT_SIGNING_METHODS.POLKADOT_SIGN_MESSAGE:
const signature = await wallet.signMessage(request.params.message)
return formatJsonRpcResult(id, signature)
case POLKADOT_SIGNING_METHODS.POLKADOT_SIGN_TRANSACTION:
const signedTx = await wallet?.signTransaction(request.params.transactionPayload)
return formatJsonRpcResult(id, signedTx)
default:
throw new Error(getSdkError('INVALID_METHOD').message)
}
}
export function rejectPolkadotRequest(request: SignClientTypes.EventArguments['session_request']) {
const { id } = request
return formatJsonRpcError(id, getSdkError('USER_REJECTED_METHODS').message)
}
@@ -0,0 +1,51 @@
import PolkadotLib from '@/lib/PolkadotLib'
import { addressEq } from '@polkadot/util-crypto'
export let wallet1: PolkadotLib
export let wallet2: PolkadotLib
export let polkadotWallets: Record<string, PolkadotLib>
export let polkadotAddresses: string[]
let address1: string
let address2: string
/**
* Utilities
*/
export function getPolkadotWallet(address: string) {
let wallet = Object.entries(polkadotWallets).find(([walletAddress, _]) => {
return addressEq(address, walletAddress)
})
return wallet?.[1]
}
export async function createOrRestorePolkadotWallet() {
const mnemonic1 = localStorage.getItem('POLKADOT_MNEMONIC_1')
const mnemonic2 = localStorage.getItem('POLKADOT_MNEMONIC_2')
if (mnemonic1 && mnemonic2) {
wallet1 = await PolkadotLib.init({ mnemonic: mnemonic1 })
wallet2 = await PolkadotLib.init({ mnemonic: mnemonic2 })
} else {
wallet1 = await PolkadotLib.init({})
wallet2 = await PolkadotLib.init({})
// Don't store mnemonic in local storage in a production project!
localStorage.setItem('POLKADOT_MNEMONIC_1', wallet1.getMnemonic())
localStorage.setItem('POLKADOT_MNEMONIC_2', wallet2.getMnemonic())
}
address1 = wallet1.getAddress()
address2 = wallet2.getAddress()
polkadotWallets = {
[address1]: wallet1,
[address2]: wallet2
}
polkadotAddresses = Object.keys(polkadotWallets)
return {
polkadotWallets,
polkadotAddresses
}
}
@@ -0,0 +1,38 @@
import { SOLANA_SIGNING_METHODS } from '@/data/SolanaData'
import { getWalletAddressFromParams } from '@/utils/HelperUtil'
import { solanaAddresses, solanaWallets } from '@/utils/SolanaWalletUtil'
import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'
import { SignClientTypes } from '@walletconnect/types'
import { getSdkError } from '@walletconnect/utils'
export async function approveSolanaRequest(
requestEvent: SignClientTypes.EventArguments['session_request']
) {
const { params, id } = requestEvent
const { request } = params
const wallet = solanaWallets[getWalletAddressFromParams(solanaAddresses, params)]
switch (request.method) {
case SOLANA_SIGNING_METHODS.SOLANA_SIGN_MESSAGE:
const signedMessage = await wallet.signMessage(request.params.message)
return formatJsonRpcResult(id, signedMessage)
case SOLANA_SIGNING_METHODS.SOLANA_SIGN_TRANSACTION:
const signedTransaction = await wallet.signTransaction(
request.params.feePayer,
request.params.recentBlockhash,
request.params.instructions
)
return formatJsonRpcResult(id, signedTransaction)
default:
throw new Error(getSdkError('INVALID_METHOD').message)
}
}
export function rejectSolanaRequest(request: SignClientTypes.EventArguments['session_request']) {
const { id } = request
return formatJsonRpcError(id, getSdkError('USER_REJECTED_METHODS').message)
}
@@ -0,0 +1,51 @@
import SolanaLib from '@/lib/SolanaLib'
export let wallet1: SolanaLib
export let wallet2: SolanaLib
export let solanaWallets: Record<string, SolanaLib>
export let solanaAddresses: string[]
let address1: string
let address2: string
/**
* Utilities
*/
export async function createOrRestoreSolanaWallet() {
const secretKey1 = localStorage.getItem('SOLANA_SECRET_KEY_1')
const secretKey2 = localStorage.getItem('SOLANA_SECRET_KEY_2')
if (secretKey1 && secretKey2) {
const secretArray1: number[] = Object.values(JSON.parse(secretKey1))
const secretArray2: number[] = Object.values(JSON.parse(secretKey2))
wallet1 = SolanaLib.init({ secretKey: Uint8Array.from(secretArray1) })
wallet2 = SolanaLib.init({ secretKey: Uint8Array.from(secretArray2) })
} else {
wallet1 = SolanaLib.init({})
wallet2 = SolanaLib.init({})
// Don't store secretKey in local storage in a production project!
localStorage.setItem(
'SOLANA_SECRET_KEY_1',
JSON.stringify(Array.from(wallet1.keypair.secretKey))
)
localStorage.setItem(
'SOLANA_SECRET_KEY_2',
JSON.stringify(Array.from(wallet2.keypair.secretKey))
)
}
address1 = await wallet1.getAddress()
address2 = await wallet2.getAddress()
solanaWallets = {
[address1]: wallet1,
[address2]: wallet2
}
solanaAddresses = Object.keys(solanaWallets)
return {
solanaWallets,
solanaAddresses
}
}
@@ -0,0 +1,27 @@
import { Core } from '@walletconnect/core'
import { ICore } from '@walletconnect/types'
import { Web3Wallet, IWeb3Wallet } from '@walletconnect/web3wallet'
export let web3wallet: IWeb3Wallet
export let core: ICore
export async function createWeb3Wallet(relayerRegionURL: string) {
core = new Core({
logger: 'debug',
projectId: process.env.NEXT_PUBLIC_PROJECT_ID,
relayUrl: relayerRegionURL ?? process.env.NEXT_PUBLIC_RELAY_URL
})
web3wallet = await Web3Wallet.init({
core,
metadata: {
name: 'React Web3Wallet',
description: 'React Web3Wallet for WalletConnect',
url: 'https://walletconnect.com/',
icons: ['https://avatars.githubusercontent.com/u/37784886']
}
})
}
export async function pair(params: { uri: string }) {
return await core.pairing.pair({ uri: params.uri })
}
@@ -0,0 +1,88 @@
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import SettingsStore from '@/store/SettingsStore'
import { approveEIP155Request, rejectEIP155Request } from '@/utils/EIP155RequestHandlerUtil'
import { eip155Addresses, eip155Wallets } from '@/utils/EIP155WalletUtil'
import { getSignParamsMessage } from '@/utils/HelperUtil'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Button, Col, Divider, Modal, Row, Text, Code } from '@nextui-org/react'
import { getSdkError } from '@walletconnect/utils'
import { Fragment } from 'react'
import { useSnapshot } from 'valtio'
export default function AuthRequestModal() {
const { account } = useSnapshot(SettingsStore.state)
console.log('modal data', ModalStore.state.data, account)
// Get request and wallet data from store
const request = ModalStore.state.data?.request
// Ensure request and wallet are defined
if (!request) {
return <Text>Missing request data</Text>
}
const address = eip155Addresses[account]
const iss = `did:pkh:eip155:1:${address}`
// Get required request data
const { params } = request
const message = web3wallet.formatMessage(params.cacaoPayload, iss)
// Handle approve action (logic varies based on request method)
async function onApprove() {
if (request) {
const signature = await eip155Wallets[address].signMessage(message)
await web3wallet.respondAuthRequest(
{
id: request.id,
signature: {
s: signature,
t: 'eip191'
}
},
iss
)
ModalStore.close()
}
}
// Handle reject action
async function onReject() {
if (request) {
await web3wallet.respondAuthRequest(
{
id: request.id,
error: getSdkError('USER_REJECTED')
},
iss
)
ModalStore.close()
}
}
return (
<Fragment>
<RequestModalContainer title="Auth Message">
<Divider y={2} />
<Row>
<Col>
<Text h5>Message</Text>
<Code>
<Text color="$gray400">{message}</Text>
</Code>
</Col>
</Row>
<Divider y={2} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onClick={onReject}>
Reject
</Button>
<Button auto flat color="success" onClick={onApprove}>
Approve
</Button>
</Modal.Footer>
</Fragment>
)
}
@@ -0,0 +1,193 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import ProposalSelectSection from '@/components/ProposalSelectSection'
import RequestModalContainer from '@/components/RequestModalContainer'
import SessionProposalChainCard from '@/components/SessionProposalChainCard'
import ModalStore from '@/store/ModalStore'
import { cosmosAddresses } from '@/utils/CosmosWalletUtil'
import { eip155Addresses } from '@/utils/EIP155WalletUtil'
import { polkadotAddresses } from '@/utils/PolkadotWalletUtil'
import { elrondAddresses } from '@/utils/ElrondWalletUtil'
import {
isCosmosChain,
isEIP155Chain,
isSolanaChain,
isPolkadotChain,
isNearChain,
isElrondChain
} from '@/utils/HelperUtil'
import { solanaAddresses } from '@/utils/SolanaWalletUtil'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Button, Divider, Modal, Text } from '@nextui-org/react'
import { SessionTypes } from '@walletconnect/types'
import { getSdkError } from '@walletconnect/utils'
import { Fragment, useState } from 'react'
import { nearAddresses } from '@/utils/NearWalletUtil'
export default function SessionProposalModal() {
const [selectedAccounts, setSelectedAccounts] = useState<Record<string, string[]>>({})
const hasSelected = Object.keys(selectedAccounts).length
// Get proposal data and wallet address from store
const proposal = ModalStore.state.data?.proposal
// Ensure proposal is defined
if (!proposal) {
return <Text>Missing proposal data</Text>
}
// Get required proposal data
const { id, params } = proposal
const { proposer, requiredNamespaces, relays } = params
// Add / remove address from EIP155 selection
function onSelectAccount(chain: string, account: string) {
if (selectedAccounts[chain]?.includes(account)) {
const newSelectedAccounts = selectedAccounts[chain]?.filter(a => a !== account)
setSelectedAccounts(prev => ({
...prev,
[chain]: newSelectedAccounts
}))
} else {
const prevChainAddresses = selectedAccounts[chain] ?? []
setSelectedAccounts(prev => ({
...prev,
[chain]: [...prevChainAddresses, account]
}))
}
}
// Hanlde approve action, construct session namespace
async function onApprove() {
if (proposal) {
const namespaces: SessionTypes.Namespaces = {}
Object.keys(requiredNamespaces).forEach(key => {
const accounts: string[] = []
requiredNamespaces[key].chains.map(chain => {
selectedAccounts[key].map(acc => accounts.push(`${chain}:${acc}`))
})
namespaces[key] = {
accounts,
methods: requiredNamespaces[key].methods,
events: requiredNamespaces[key].events
}
})
await web3wallet.approveSession({
id,
relayProtocol: relays[0].protocol,
namespaces
})
}
ModalStore.close()
}
// Hanlde reject action
async function onReject() {
if (proposal) {
await web3wallet.rejectSession({
id,
reason: getSdkError('USER_REJECTED_METHODS')
})
}
ModalStore.close()
}
// Render account selection checkboxes based on chain
function renderAccountSelection(chain: string) {
if (isEIP155Chain(chain)) {
return (
<ProposalSelectSection
addresses={eip155Addresses}
selectedAddresses={selectedAccounts[chain]}
onSelect={onSelectAccount}
chain={chain}
/>
)
} else if (isCosmosChain(chain)) {
return (
<ProposalSelectSection
addresses={cosmosAddresses}
selectedAddresses={selectedAccounts[chain]}
onSelect={onSelectAccount}
chain={chain}
/>
)
} else if (isSolanaChain(chain)) {
return (
<ProposalSelectSection
addresses={solanaAddresses}
selectedAddresses={selectedAccounts[chain]}
onSelect={onSelectAccount}
chain={chain}
/>
)
} else if (isPolkadotChain(chain)) {
return (
<ProposalSelectSection
addresses={polkadotAddresses}
selectedAddresses={selectedAccounts[chain]}
onSelect={onSelectAccount}
chain={chain}
/>
)
} else if (isNearChain(chain)) {
return (
<ProposalSelectSection
addresses={nearAddresses}
selectedAddresses={selectedAccounts[chain]}
onSelect={onSelectAccount}
chain={chain}
/>
)
} else if (isElrondChain(chain)) {
return (
<ProposalSelectSection
addresses={elrondAddresses}
selectedAddresses={selectedAccounts[chain]}
onSelect={onSelectAccount}
chain={chain}
/>
)
}
}
return (
<Fragment>
<RequestModalContainer title="Session Proposal">
<ProjectInfoCard metadata={proposer.metadata} />
{/* TODO(ilja) Relays selection */}
<Divider y={2} />
{Object.keys(requiredNamespaces).map(chain => {
return (
<Fragment key={chain}>
<Text h4 css={{ marginBottom: '$5' }}>{`Review ${chain} permissions`}</Text>
<SessionProposalChainCard requiredNamespace={requiredNamespaces[chain]} />
{renderAccountSelection(chain)}
<Divider y={2} />
</Fragment>
)
})}
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onClick={onReject}>
Reject
</Button>
<Button
auto
flat
color="success"
onClick={onApprove}
disabled={!hasSelected}
css={{ opacity: hasSelected ? 1 : 0.4 }}
>
Approve
</Button>
</Modal.Footer>
</Fragment>
)
}
@@ -0,0 +1,83 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import RequestDataCard from '@/components/RequestDataCard'
import RequesDetailsCard from '@/components/RequestDetalilsCard'
import RequestMethodCard from '@/components/RequestMethodCard'
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import { approveEIP155Request, rejectEIP155Request } from '@/utils/EIP155RequestHandlerUtil'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Button, Divider, Loading, Modal, Text } from '@nextui-org/react'
import { Fragment, useState } from 'react'
export default function SessionSendTransactionModal() {
const [loading, setLoading] = useState(false)
// 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 proposal data
const { topic, params } = requestEvent
const { request, chainId } = params
const transaction = request.params[0]
// Handle approve action
async function onApprove() {
if (requestEvent) {
setLoading(true)
const response = await approveEIP155Request(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
// Handle reject action
async function onReject() {
if (requestEvent) {
const response = rejectEIP155Request(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
return (
<Fragment>
<RequestModalContainer title="Send / Sign Transaction">
<ProjectInfoCard metadata={requestSession.peer.metadata} />
<Divider y={2} />
<RequestDataCard data={transaction} />
<Divider y={2} />
<RequesDetailsCard chains={[chainId ?? '']} protocol={requestSession.relay.protocol} />
<Divider y={2} />
<RequestMethodCard methods={[request.method]} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onClick={onReject} disabled={loading}>
Reject
</Button>
<Button auto flat color="success" onClick={onApprove} disabled={loading}>
{loading ? <Loading size="sm" color="success" /> : 'Approve'}
</Button>
</Modal.Footer>
</Fragment>
)
}
@@ -0,0 +1,78 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import RequestDataCard from '@/components/RequestDataCard'
import RequesDetailsCard from '@/components/RequestDetalilsCard'
import RequestMethodCard from '@/components/RequestMethodCard'
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import { approveCosmosRequest, rejectCosmosRequest } from '@/utils/CosmosRequestHandler'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Button, Divider, Modal, Text } from '@nextui-org/react'
import { Fragment } from 'react'
export default function SessionSignCosmosModal() {
// 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 { topic, params } = requestEvent
const { chainId, request } = params
// Handle approve action (logic varies based on request method)
async function onApprove() {
if (requestEvent) {
const response = await approveCosmosRequest(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
// Handle reject action
async function onReject() {
if (requestEvent) {
const response = rejectCosmosRequest(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
return (
<Fragment>
<RequestModalContainer title="Sign Message">
<ProjectInfoCard metadata={requestSession.peer.metadata} />
<Divider y={2} />
<RequesDetailsCard chains={[chainId ?? '']} protocol={requestSession.relay.protocol} />
<Divider y={2} />
<RequestDataCard data={params} />
<Divider y={2} />
<RequestMethodCard methods={[request.method]} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onClick={onReject}>
Reject
</Button>
<Button auto flat color="success" onClick={onApprove}>
Approve
</Button>
</Modal.Footer>
</Fragment>
)
}
@@ -0,0 +1,78 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import RequestDataCard from '@/components/RequestDataCard'
import RequesDetailsCard from '@/components/RequestDetalilsCard'
import RequestMethodCard from '@/components/RequestMethodCard'
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import { approveElrondRequest, rejectElrondRequest } from '@/utils/ElrondRequestHandlerUtil'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Button, Divider, Modal, Text } from '@nextui-org/react'
import { Fragment } from 'react'
export default function SessionSignElrondModal() {
// 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 { topic, params } = requestEvent
const { request, chainId } = params
// Handle approve action (logic varies based on request method)
async function onApprove() {
if (requestEvent) {
const response = await approveElrondRequest(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
// Handle reject action
async function onReject() {
if (requestEvent) {
const response = rejectElrondRequest(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
return (
<Fragment>
<RequestModalContainer title="Sign Message">
<ProjectInfoCard metadata={requestSession.peer.metadata} />
<Divider y={2} />
<RequesDetailsCard chains={[chainId ?? '']} protocol={requestSession.relay.protocol} />
<Divider y={2} />
<RequestDataCard data={params} />
<Divider y={2} />
<RequestMethodCard methods={[request.method]} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onClick={onReject}>
Reject
</Button>
<Button auto flat color="success" onClick={onApprove}>
Approve
</Button>
</Modal.Footer>
</Fragment>
)
}
@@ -0,0 +1,86 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import RequesDetailsCard from '@/components/RequestDetalilsCard'
import RequestMethodCard from '@/components/RequestMethodCard'
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import { approveEIP155Request, rejectEIP155Request } from '@/utils/EIP155RequestHandlerUtil'
import { getSignParamsMessage } from '@/utils/HelperUtil'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Button, Col, Divider, Modal, Row, Text } from '@nextui-org/react'
import { Fragment } from 'react'
export default function SessionSignModal() {
// 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 { topic, params } = requestEvent
const { request, chainId } = params
// Get message, convert it to UTF8 string if it is valid hex
const message = getSignParamsMessage(request.params)
// Handle approve action (logic varies based on request method)
async function onApprove() {
if (requestEvent) {
const response = await approveEIP155Request(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
// Handle reject action
async function onReject() {
if (requestEvent) {
const response = rejectEIP155Request(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
return (
<Fragment>
<RequestModalContainer title="Sign Message">
<ProjectInfoCard metadata={requestSession.peer.metadata} />
<Divider y={2} />
<RequesDetailsCard chains={[chainId ?? '']} protocol={requestSession.relay.protocol} />
<Divider y={2} />
<Row>
<Col>
<Text h5>Message</Text>
<Text color="$gray400">{message}</Text>
</Col>
</Row>
<Divider y={2} />
<RequestMethodCard methods={[request.method]} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onClick={onReject}>
Reject
</Button>
<Button auto flat color="success" onClick={onApprove}>
Approve
</Button>
</Modal.Footer>
</Fragment>
)
}
@@ -0,0 +1,191 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import RequestDataCard from '@/components/RequestDataCard'
import RequestDetailsCard from '@/components/RequestDetalilsCard'
import RequestMethodCard from '@/components/RequestMethodCard'
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import { approveNearRequest, rejectNearRequest } from '@/utils/NearRequestHandlerUtil'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { NEAR_SIGNING_METHODS } from '@/data/NEARData'
import { transactions } from 'near-api-js'
import { Button, Divider, Modal, Text } from '@nextui-org/react'
import { Fragment } from 'react'
export default function SessionSignNearModal() {
// 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 { topic, params } = requestEvent
const { request, chainId } = params
const formatTransaction = (transaction: Uint8Array) => {
const tx = transactions.Transaction.decode(Buffer.from(transaction))
return {
signerId: tx.signerId,
receiverId: tx.receiverId,
publicKey: tx.publicKey.toString(),
actions: tx.actions.map(action => {
switch (action.enum) {
case 'createAccount': {
return {
type: 'CreateAccount',
params: action.createAccount
}
}
case 'deployContract': {
return {
type: 'DeployContract',
params: {
...action.deployContract,
args: Buffer.from(action.deployContract.code).toString()
}
}
}
case 'functionCall': {
return {
type: 'FunctionCall',
params: {
...action.functionCall,
args: JSON.parse(Buffer.from(action.functionCall.args).toString())
}
}
}
case 'transfer': {
return {
type: 'Transfer',
params: action.transfer
}
}
case 'stake': {
return {
type: 'Stake',
params: {
...action.stake,
publicKey: action.stake.publicKey.toString()
}
}
}
case 'addKey': {
return {
type: 'AddKey',
params: {
...action.addKey,
publicKey: action.addKey.publicKey.toString()
}
}
}
case 'deleteKey': {
return {
type: 'DeleteKey',
params: {
...action.deleteKey,
publicKey: action.deleteKey.publicKey.toString()
}
}
}
case 'deleteAccount': {
return {
type: 'DeleteAccount',
params: action.deleteAccount
}
}
default:
return {
type: action.enum,
params: {}
}
}
})
}
}
const formatParams = () => {
switch (params.request.method) {
case NEAR_SIGNING_METHODS.NEAR_SIGN_TRANSACTION:
return {
...params,
request: {
...params.request,
params: {
...params.request.params,
transaction: formatTransaction(params.request.params.transaction)
}
}
}
case NEAR_SIGNING_METHODS.NEAR_SIGN_TRANSACTIONS:
return {
...params,
request: {
...params.request,
params: {
...params.request.params,
transactions: params.request.params.transactions.map(formatTransaction)
}
}
}
default:
return params
}
}
// Handle approve action (logic varies based on request method)
async function onApprove() {
if (requestEvent) {
const response = await approveNearRequest(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
// Handle reject action
async function onReject() {
if (requestEvent) {
const response = rejectNearRequest(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
return (
<Fragment>
<RequestModalContainer title="NEAR">
<ProjectInfoCard metadata={requestSession.peer.metadata} />
<Divider y={2} />
<RequestDetailsCard chains={[chainId ?? '']} protocol={requestSession.relay.protocol} />
<Divider y={2} />
<RequestDataCard data={formatParams()} />
<Divider y={2} />
<RequestMethodCard methods={[request.method]} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onClick={onReject}>
Reject
</Button>
<Button auto flat color="success" onClick={onApprove}>
Approve
</Button>
</Modal.Footer>
</Fragment>
)
}
@@ -0,0 +1,78 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import RequestDataCard from '@/components/RequestDataCard'
import RequesDetailsCard from '@/components/RequestDetalilsCard'
import RequestMethodCard from '@/components/RequestMethodCard'
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import { approvePolkadotRequest, rejectPolkadotRequest } from '@/utils/PolkadotRequestHandlerUtil'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Button, Divider, Modal, Text } from '@nextui-org/react'
import { Fragment } from 'react'
export default function SessionSignPolkadotModal() {
// 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 { topic, params } = requestEvent
const { request, chainId } = params
// Handle approve action (logic varies based on request method)
async function onApprove() {
if (requestEvent) {
const response = await approvePolkadotRequest(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
// Handle reject action
async function onReject() {
if (requestEvent) {
const response = rejectPolkadotRequest(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
return (
<Fragment>
<RequestModalContainer title="Sign Message">
<ProjectInfoCard metadata={requestSession.peer.metadata} />
<Divider y={2} />
<RequesDetailsCard chains={[chainId ?? '']} protocol={requestSession.relay.protocol} />
<Divider y={2} />
<RequestDataCard data={params} />
<Divider y={2} />
<RequestMethodCard methods={[request.method]} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onClick={onReject}>
Reject
</Button>
<Button auto flat color="success" onClick={onApprove}>
Approve
</Button>
</Modal.Footer>
</Fragment>
)
}
@@ -0,0 +1,78 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import RequestDataCard from '@/components/RequestDataCard'
import RequesDetailsCard from '@/components/RequestDetalilsCard'
import RequestMethodCard from '@/components/RequestMethodCard'
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import { approveSolanaRequest, rejectSolanaRequest } from '@/utils/SolanaRequestHandlerUtil'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Button, Divider, Modal, Text } from '@nextui-org/react'
import { Fragment } from 'react'
export default function SessionSignSolanaModal() {
// 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 { topic, params } = requestEvent
const { request, chainId } = params
// Handle approve action (logic varies based on request method)
async function onApprove() {
if (requestEvent) {
const response = await approveSolanaRequest(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
// Handle reject action
async function onReject() {
if (requestEvent) {
const response = rejectSolanaRequest(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
return (
<Fragment>
<RequestModalContainer title="Sign Message">
<ProjectInfoCard metadata={requestSession.peer.metadata} />
<Divider y={2} />
<RequesDetailsCard chains={[chainId ?? '']} protocol={requestSession.relay.protocol} />
<Divider y={2} />
<RequestDataCard data={params} />
<Divider y={2} />
<RequestMethodCard methods={[request.method]} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onClick={onReject}>
Reject
</Button>
<Button auto flat color="success" onClick={onApprove}>
Approve
</Button>
</Modal.Footer>
</Fragment>
)
}
@@ -0,0 +1,82 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import RequestDataCard from '@/components/RequestDataCard'
import RequesDetailsCard from '@/components/RequestDetalilsCard'
import RequestMethodCard from '@/components/RequestMethodCard'
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import { approveEIP155Request, rejectEIP155Request } from '@/utils/EIP155RequestHandlerUtil'
import { getSignTypedDataParamsData } from '@/utils/HelperUtil'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Button, Divider, Modal, 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 { topic, params } = requestEvent
const { request, chainId } = params
// Get data
const data = getSignTypedDataParamsData(request.params)
// Handle approve action (logic varies based on request method)
async function onApprove() {
if (requestEvent) {
const response = await approveEIP155Request(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
// Handle reject action
async function onReject() {
if (requestEvent) {
const response = rejectEIP155Request(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
return (
<Fragment>
<RequestModalContainer title="Sign Typed Data">
<ProjectInfoCard metadata={requestSession.peer.metadata} />
<Divider y={2} />
<RequesDetailsCard chains={[chainId ?? '']} protocol={requestSession.relay.protocol} />
<Divider y={2} />
<RequestDataCard data={data} />
<Divider y={2} />
<RequestMethodCard methods={[request.method]} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onClick={onReject}>
Reject
</Button>
<Button auto flat color="success" onClick={onApprove}>
Approve
</Button>
</Modal.Footer>
</Fragment>
)
}
@@ -0,0 +1,44 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import RequesDetailsCard from '@/components/RequestDetalilsCard'
import RequestMethodCard from '@/components/RequestMethodCard'
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import { Button, Divider, Modal, Text } from '@nextui-org/react'
import { Fragment } from 'react'
export default function SessionUnsuportedMethodModal() {
// 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 { topic, params } = requestEvent
const { chainId, request } = params
return (
<Fragment>
<RequestModalContainer title="Unsuported Method">
<ProjectInfoCard metadata={requestSession.peer.metadata} />
<Divider y={2} />
<RequesDetailsCard chains={[chainId ?? '']} protocol={requestSession.relay.protocol} />
<Divider y={2} />
<RequestMethodCard methods={[request.method]} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onClick={ModalStore.close}>
Close
</Button>
</Modal.Footer>
</Fragment>
)
}