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:
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user