Merge branch 'wallets'

This commit is contained in:
Ilja 2022-03-14 14:58:19 +02:00
commit b38452e02c
18 changed files with 943 additions and 438 deletions

View File

@ -11,6 +11,7 @@
"@walletconnect/client": "experimental",
"@walletconnect/utils": "experimental",
"@json-rpc-tools/utils": "1.7.6",
"@solana/web3.js": "1.36.0",
"@nextui-org/react": "1.0.2-beta.4",
"mnemonic-keyring": "1.4.0",
"next": "12.1.0",
@ -18,7 +19,7 @@
"react-dom": "17.0.2",
"react-qr-reader-es6": "2.2.1-2",
"framer-motion": "6.2.8",
"ethers": "5.5.4",
"ethers": "5.6.0",
"valtio": "1.3.1",
"react-code-blocks": "0.0.9-0",
"@cosmjs/proto-signing": "0.27.1",
@ -28,8 +29,8 @@
"devDependencies": {
"@walletconnect/types": "experimental",
"@types/node": "17.0.21",
"@types/react": "17.0.39",
"eslint": "8.10.0",
"@types/react": "17.0.40",
"eslint": "8.11.0",
"eslint-config-next": "12.1.0",
"eslint-config-prettier": "8.5.0",
"prettier": "2.5.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -1,6 +1,7 @@
import SettingsStore from '@/store/SettingsStore'
import { cosmosAddresses } from '@/utils/CosmosWalletUtil'
import { eip155Addresses } from '@/utils/EIP155WalletUtil'
import { solanaAddresses } from '@/utils/SolanaWalletUtil'
import { useSnapshot } from 'valtio'
export default function AccountPicker() {
@ -11,6 +12,7 @@ export default function AccountPicker() {
SettingsStore.setAccount(account)
SettingsStore.setEIP155Address(eip155Addresses[account])
SettingsStore.setCosmosAddress(cosmosAddresses[account])
SettingsStore.setSolanaAddress(solanaAddresses[account])
}
return (

View File

@ -36,7 +36,6 @@ export default function Layout({ children, initialized }: Props) {
justifyContent: initialized ? 'normal' : 'center',
alignItems: initialized ? 'normal' : 'center',
borderRadius: 0,
paddingBottom: 5,
'@xs': {
borderRadius: '$lg',
@ -53,7 +52,8 @@ export default function Layout({ children, initialized }: Props) {
paddingLeft: 2,
paddingRight: 2,
'@xs': {
padding: '20px'
padding: '20px',
paddingBottom: '40px'
}
}}
>
@ -68,6 +68,8 @@ export default function Layout({ children, initialized }: Props) {
position: 'sticky',
justifyContent: 'flex-end',
alignItems: 'flex-end',
boxShadow: '0 -30px 20px #111111',
zIndex: 200,
bottom: 0,
left: 0
}}

View File

@ -0,0 +1,46 @@
import AccountSelectCard from '@/components/AccountSelectCard'
import { Col, Divider, Row, Text } from '@nextui-org/react'
import { Fragment } from 'react'
/**
* Types
*/
interface IProps {
name: string
chain: string
addresses: string[]
selectedAddresses: string[]
onSelect: (address: string) => void
}
/**
* Component
*/
export default function ProposalSelectSection({
name,
addresses,
selectedAddresses,
chain,
onSelect
}: IProps) {
return (
<Fragment>
<Divider y={2} />
<Row>
<Col>
<Text h5>{`Select ${name} Accounts`}</Text>
{addresses.map((address, index) => (
<AccountSelectCard
key={address}
address={address}
index={index}
onSelect={() => onSelect(`${chain}:${address}`)}
selected={selectedAddresses.includes(`${chain}:${address}`)}
/>
))}
</Col>
</Row>
</Fragment>
)
}

View File

@ -1,5 +1,6 @@
import { COSMOS_MAINNET_CHAINS, TCosmosChain } from '@/data/COSMOSData'
import { EIP155_CHAINS, TEIP155Chain } from '@/data/EIP155Data'
import { SOLANA_CHAINS, TSolanaChain } from '@/data/SolanaData'
import { Col, Divider, Row, Text } from '@nextui-org/react'
import { Fragment } from 'react'
@ -26,6 +27,7 @@ export default function RequesDetailsCard({ chains, protocol }: IProps) {
chain =>
EIP155_CHAINS[chain as TEIP155Chain]?.name ??
COSMOS_MAINNET_CHAINS[chain as TCosmosChain]?.name ??
SOLANA_CHAINS[chain as TSolanaChain]?.name ??
chain
)
.join(', ')}

View File

@ -0,0 +1,53 @@
import AccountSelectCard from '@/components/AccountSelectCard'
import { Col, Divider, Row, Text } from '@nextui-org/react'
import { Fragment } from 'react'
/**
* Types
*/
interface IProps {
name: string
chain: string
addresses: string[]
selectedAddresses: string[]
onDelete: (address: string) => void
onAdd: (address: string) => void
}
/**
* Component
*/
export default function SessionSelectSection({
name,
addresses,
chain,
selectedAddresses,
onDelete,
onAdd
}: IProps) {
return (
<Fragment>
<Divider y={2} />
<Row>
<Col>
<Text h5>{`${name} Accounts`}</Text>
{addresses.map((address, index) => {
const fullAddress = `${chain}:${address}`
const selected = selectedAddresses.includes(fullAddress)
return (
<AccountSelectCard
key={address}
address={address}
index={index}
onSelect={() => (selected ? onDelete(fullAddress) : onAdd(fullAddress))}
selected={selected}
/>
)
})}
</Col>
</Row>
</Fragment>
)
}

View File

@ -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'
}

View File

@ -1,6 +1,7 @@
import SettingsStore from '@/store/SettingsStore'
import { createOrRestoreCosmosWallet } from '@/utils/CosmosWalletUtil'
import { createOrRestoreEIP155Wallet } from '@/utils/EIP155WalletUtil'
import { createOrRestoreSolanaWallet } from '@/utils/SolanaWalletUtil'
import { createWalletConnectClient } from '@/utils/WalletConnectUtil'
import { useCallback, useEffect, useState } from 'react'
@ -11,9 +12,11 @@ export default function useInitialization() {
try {
const { eip155Addresses } = createOrRestoreEIP155Wallet()
const { cosmosAddresses } = await createOrRestoreCosmosWallet()
const { solanaAddresses } = await createOrRestoreSolanaWallet()
SettingsStore.setEIP155Address(eip155Addresses[0])
SettingsStore.setCosmosAddress(cosmosAddresses[0])
SettingsStore.setSolanaAddress(solanaAddresses[0])
await createWalletConnectClient()

View File

@ -0,0 +1,19 @@
import { Keypair } from '@solana/web3.js'
export class Solana {
keypair: Keypair
constructor(keypair: Keypair) {
this.keypair = keypair
}
static init(secretKey?: Uint8Array) {
const keypair = secretKey ? Keypair.fromSecretKey(secretKey) : Keypair.generate()
return new Solana(keypair)
}
public async getAccount() {
return await this.keypair.publicKey.toBase58()
}
}

View File

@ -3,13 +3,14 @@ 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 SettingsStore from '@/store/SettingsStore'
import { Text } from '@nextui-org/react'
import { Fragment } from 'react'
import { useSnapshot } from 'valtio'
export default function HomePage() {
const { testNets, eip155Address, cosmosAddress } = useSnapshot(SettingsStore.state)
const { testNets, eip155Address, cosmosAddress, solanaAddress } = useSnapshot(SettingsStore.state)
return (
<Fragment>
@ -25,6 +26,9 @@ export default function HomePage() {
{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} />
))}
{testNets ? (
<Fragment>
@ -34,6 +38,9 @@ export default function HomePage() {
{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} />
))}
</Fragment>
) : null}
</Fragment>

View File

@ -1,11 +1,13 @@
import AccountSelectCard from '@/components/AccountSelectCard'
import PageHeader from '@/components/PageHeader'
import ProjectInfoCard from '@/components/ProjectInfoCard'
import SessionSelectSection from '@/components/SessionSelectSection'
import { COSMOS_MAINNET_CHAINS, TCosmosChain } from '@/data/COSMOSData'
import { EIP155_CHAINS, TEIP155Chain } from '@/data/EIP155Data'
import { SOLANA_CHAINS, TSolanaChain } from '@/data/SolanaData'
import { cosmosAddresses } from '@/utils/CosmosWalletUtil'
import { eip155Addresses } from '@/utils/EIP155WalletUtil'
import { isCosmosChain, isEIP155Chain } from '@/utils/HelperUtil'
import { isCosmosChain, isEIP155Chain, isSolanaChain } from '@/utils/HelperUtil'
import { solanaAddresses } from '@/utils/SolanaWalletUtil'
import { walletConnectClient } from '@/utils/WalletConnectUtil'
import { Button, Col, Divider, Row, Text } from '@nextui-org/react'
import { ERROR } from '@walletconnect/utils'
@ -79,59 +81,39 @@ export default function SessionPage() {
{chains.map(chain => {
if (isEIP155Chain(chain)) {
return (
<Fragment key={chain}>
<Divider y={2} />
<Row>
<Col>
<Text h5>{`${EIP155_CHAINS[chain as TEIP155Chain].name} Accounts`}</Text>
{eip155Addresses.map((address, index) => {
const fullAddress = `${chain}:${address}`
const selected = accounts.includes(fullAddress)
return (
<AccountSelectCard
key={address}
address={address}
index={index}
onSelect={() =>
selected ? onDeleteAccount(fullAddress) : onAddAccount(fullAddress)
}
selected={selected}
/>
)
})}
</Col>
</Row>
</Fragment>
<SessionSelectSection
key={chain}
chain={chain}
name={EIP155_CHAINS[chain as TEIP155Chain]?.name}
addresses={eip155Addresses}
selectedAddresses={accounts}
onDelete={onDeleteAccount}
onAdd={onAddAccount}
/>
)
} else if (isCosmosChain(chain)) {
return (
<Fragment key={chain}>
<Divider y={2} />
<Row>
<Col>
<Text h5>{`${COSMOS_MAINNET_CHAINS[chain as TCosmosChain].name} Accounts`}</Text>
{cosmosAddresses.map((address, index) => {
const fullAddress = `${chain}:${address}`
const selected = accounts.includes(fullAddress)
return (
<AccountSelectCard
key={address}
address={address}
index={index}
onSelect={() =>
selected ? onDeleteAccount(fullAddress) : onAddAccount(fullAddress)
}
selected={selected}
/>
)
})}
</Col>
</Row>
</Fragment>
<SessionSelectSection
key={chain}
chain={chain}
name={COSMOS_MAINNET_CHAINS[chain as TCosmosChain]?.name}
addresses={cosmosAddresses}
selectedAddresses={accounts}
onDelete={onDeleteAccount}
onAdd={onAddAccount}
/>
)
} else if (isSolanaChain(chain)) {
return (
<SessionSelectSection
key={chain}
chain={chain}
name={SOLANA_CHAINS[chain as TSolanaChain]?.name}
addresses={solanaAddresses}
selectedAddresses={accounts}
onDelete={onDeleteAccount}
onAdd={onAddAccount}
/>
)
}
})}

View File

@ -32,6 +32,7 @@ export default function WalletConnectPage() {
<Input
bordered
aria-label="wc url connect input"
placeholder="e.g. wc:a281567bb3e4..."
onChange={e => setUri(e.target.value)}
value={uri}

View File

@ -8,6 +8,7 @@ interface State {
account: number
eip155Address: string
cosmosAddress: string
solanaAddress: string
}
/**
@ -17,7 +18,8 @@ const state = proxy<State>({
testNets: typeof localStorage !== 'undefined' ? Boolean(localStorage.getItem('TEST_NETS')) : true,
account: 0,
eip155Address: '',
cosmosAddress: ''
cosmosAddress: '',
solanaAddress: ''
})
/**
@ -38,6 +40,10 @@ const SettingsStore = {
state.cosmosAddress = cosmosAddresses
},
setSolanaAddress(solanaAddress: string) {
state.solanaAddress = solanaAddress
},
toggleTestNets() {
state.testNets = !state.testNets
if (state.testNets) {

View File

@ -83,3 +83,10 @@ export function isEIP155Chain(chain: string) {
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')
}

View File

@ -0,0 +1,52 @@
import { Solana } from '@/lib/Solana'
export let wallet1: Solana
export let wallet2: Solana
export let solanaWallets: Record<string, Solana>
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 = Solana.init(Uint8Array.from(secretArray1))
wallet2 = Solana.init(Uint8Array.from(secretArray2))
address1 = await wallet1.getAccount()
address2 = await wallet2.getAccount()
} else {
wallet1 = Solana.init()
wallet2 = Solana.init()
address1 = await wallet1.getAccount()
address2 = await wallet2.getAccount()
// 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))
)
}
solanaWallets = {
[address1]: wallet1,
[address2]: wallet2
}
solanaAddresses = Object.keys(solanaWallets)
return {
solanaWallets,
solanaAddresses
}
}

View File

@ -1,21 +1,25 @@
import AccountSelectCard from '@/components/AccountSelectCard'
import ProjectInfoCard from '@/components/ProjectInfoCard'
import ProposalSelectSection from '@/components/ProposalSelectSection'
import RequesDetailsCard from '@/components/RequestDetalilsCard'
import RequestMethodCard from '@/components/RequestMethodCard'
import RequestModalContainer from '@/components/RequestModalContainer'
import { COSMOS_MAINNET_CHAINS, TCosmosChain } from '@/data/COSMOSData'
import { EIP155_CHAINS, TEIP155Chain } from '@/data/EIP155Data'
import { SOLANA_CHAINS, TSolanaChain } from '@/data/SolanaData'
import ModalStore from '@/store/ModalStore'
import { cosmosAddresses } from '@/utils/CosmosWalletUtil'
import { eip155Addresses } from '@/utils/EIP155WalletUtil'
import { isCosmosChain, isEIP155Chain } from '@/utils/HelperUtil'
import { isCosmosChain, isEIP155Chain, isSolanaChain } from '@/utils/HelperUtil'
import { solanaAddresses } from '@/utils/SolanaWalletUtil'
import { walletConnectClient } from '@/utils/WalletConnectUtil'
import { Button, Col, Divider, Modal, Row, Text } from '@nextui-org/react'
import { Button, Divider, Modal, Text } from '@nextui-org/react'
import { Fragment, useState } from 'react'
export default function SessionProposalModal() {
const [selectedEIP155, setSelectedEip155] = useState<string[]>([])
const [selectedCosmos, setSelectedCosmos] = useState<string[]>([])
const [selectedSolana, setSelectedSolana] = useState<string[]>([])
const allSelected = [...selectedEIP155, ...selectedCosmos, ...selectedSolana]
// Get proposal data and wallet address from store
const proposal = ModalStore.state.data?.proposal
@ -50,10 +54,20 @@ export default function SessionProposalModal() {
}
}
// Add / remove address from Solana selection
function onSelectSolana(address: string) {
if (selectedSolana.includes(address)) {
const newAddresses = selectedSolana.filter(a => a !== address)
setSelectedSolana(newAddresses)
} else {
setSelectedSolana([...selectedSolana, address])
}
}
// Hanlde approve action
async function onApprove() {
if (proposal) {
const accounts = [...selectedEIP155, ...selectedCosmos]
const accounts = allSelected
const response = {
state: {
accounts
@ -88,47 +102,33 @@ export default function SessionProposalModal() {
{chains.map(chain => {
if (isEIP155Chain(chain)) {
return (
<Fragment>
<Divider y={2} />
<Row>
<Col>
<Text h5>{`Select ${EIP155_CHAINS[chain as TEIP155Chain].name} Accounts`}</Text>
{eip155Addresses.map((address, index) => (
<AccountSelectCard
key={address}
address={address}
index={index}
onSelect={() => onSelectEIP155(`${chain}:${address}`)}
selected={selectedEIP155.includes(`${chain}:${address}`)}
/>
))}
</Col>
</Row>
</Fragment>
<ProposalSelectSection
name={EIP155_CHAINS[chain as TEIP155Chain]?.name}
addresses={eip155Addresses}
selectedAddresses={selectedEIP155}
onSelect={onSelectEIP155}
chain={chain}
/>
)
} else if (isCosmosChain(chain)) {
return (
<Fragment>
<Divider y={2} />
<Row>
<Col>
<Text h5>
{`Select ${COSMOS_MAINNET_CHAINS[chain as TCosmosChain].name} Accounts`}
</Text>
{cosmosAddresses.map((address, index) => (
<AccountSelectCard
key={address}
address={address}
index={index}
onSelect={() => onSelectCosmos(`${chain}:${address}`)}
selected={selectedCosmos.includes(`${chain}:${address}`)}
/>
))}
</Col>
</Row>
</Fragment>
<ProposalSelectSection
name={COSMOS_MAINNET_CHAINS[chain as TCosmosChain]?.name}
addresses={cosmosAddresses}
selectedAddresses={selectedCosmos}
onSelect={onSelectCosmos}
chain={chain}
/>
)
} else if (isSolanaChain(chain)) {
return (
<ProposalSelectSection
name={SOLANA_CHAINS[chain as TSolanaChain]?.name}
addresses={solanaAddresses}
selectedAddresses={selectedSolana}
onSelect={onSelectSolana}
chain={chain}
/>
)
}
})}
@ -144,8 +144,10 @@ export default function SessionProposalModal() {
flat
color="success"
onClick={onApprove}
disabled={![...selectedEIP155, ...selectedCosmos].length}
css={{ opacity: [...selectedEIP155, ...selectedCosmos].length ? 1 : 0.4 }}
disabled={!allSelected.length}
css={{
opacity: allSelected.length ? 1 : 0.4
}}
>
Approve
</Button>

File diff suppressed because it is too large Load Diff