This commit is contained in:
Filipe Medeiros 2021-11-27 04:34:37 +00:00
parent 3e271c68d7
commit a92cafff92
32 changed files with 387 additions and 216 deletions

View file

@ -19,6 +19,3 @@ const handler: NextApiHandler<ResponseBody> = (req, res) => {
}
export default handler

View file

@ -0,0 +1,15 @@
---
to: lib/db/<%= name %>.ts
---
import Dexie, { Table } from 'dexie'
import db from '.'
interface Value {
}
export type Key = unknown
export type Value = Value
export const schema = undefined // this should be a string like 'key,value'

View file

@ -8,6 +8,3 @@ const <%= name %> = () => {
}
export default <%= name %>

View file

@ -1,11 +1,11 @@
import clsx from 'clsx'
import { tools } from 'nanocurrency-web'
import { FC, useMemo } from 'react'
import { FC } from 'react'
import useSWR from 'swr'
import { useAccount, useAccounts } from '../lib/context/accountContext'
import { useAccount } from '../lib/context/accountContext'
import { usePreferences } from '../lib/context/preferencesContext'
import { ShowCurrencyPreference } from '../lib/db/preferences'
import { ShowCurrencyPreference } from '../lib/db/types'
import fetcher from '../lib/fetcher'
export interface Props {

View file

@ -15,6 +15,7 @@ import { useRouter } from 'next/router'
import { FC, useState } from 'react'
import { checkBiometrics } from '../lib/biometrics'
import computeWorkAsync from '../lib/computeWorkAsync'
import { useAccount, useAccounts } from '../lib/context/accountContext'
import { usePreferences } from '../lib/context/preferencesContext'
import { getEncryptedSeed } from '../lib/db/encryptedSeeds'
@ -142,7 +143,7 @@ const BottomMenu: FC<Props> = ({ className }) => {
? 'left-0 -translate-x-2/3 rounded-l'
: 'right-0 translate-x-2/3 rounded-r'
)}
onClick={() => push('/readQrCode')}
onClick={() => push('/send/qr')}
>
<UploadIcon className="h-full text-purple-50 dark:text-gray-900 w-full" />
</button>
@ -159,7 +160,7 @@ const BottomMenu: FC<Props> = ({ className }) => {
? 'right-0 translate-x-2/3 rounded-r'
: 'left-0 -translate-x-2/3 rounded-l'
)}
onClick={() => push('/myQrCode')}
onClick={() => push('/receive/qr')}
>
<DownloadIcon className="h-full text-purple-50 dark:text-gray-900 w-full" />
</button>
@ -175,6 +176,11 @@ const BottomMenu: FC<Props> = ({ className }) => {
<button
disabled={isWelcoming}
className="bg-purple-500 p-1 h-7 rounded hover:bg-purple-400 disabled:hover:bg-purple-500 shadow-lg disabled:cursor-default"
onClick={() =>
computeWorkAsync(
account?.frontier ?? account?.publicKey ?? ''
).then(console.log)
}
>
<RssIcon className="h-full text-purple-50 dark:text-gray-900" />
</button>

45
lib/computeWorkAsync.ts Normal file
View file

@ -0,0 +1,45 @@
const computeWorkAsync = (frontier: string, workerCount = 4) => {
const workers: Worker[] = []
const cleanup = () => {
workers.forEach(worker => worker.terminate())
abortController.abort()
}
const abortController = new AbortController()
const onlineWorkPromise = fetch(`/api/computeWork?frontier=${frontier}`, {
signal: abortController.signal,
}).then(async res => {
if (res.status !== 200) throw new Error()
else {
const { work } = await res.json()
return work as string
}
})
const offlineWorkPromise = new Promise<string | null>((res, rej) => {
const maxWorkers = navigator.hardwareConcurrency ?? workerCount
const createWorker = (id: number) => {
const worker = new Worker(new URL('./workComputer.ts', import.meta.url))
worker.onmessage = work => {
cleanup()
if (work === null) rej('failed_work')
else res(work.data)
}
worker.onerror = work => {
cleanup()
rej(work)
}
worker.postMessage({ frontier, id })
return worker
}
for (let i = 0; i < maxWorkers; i++) workers.push(createWorker(i))
})
return Promise.any([onlineWorkPromise, offlineWorkPromise])
}
export default computeWorkAsync

View file

@ -9,17 +9,17 @@ import {
import { AccountInfoCache } from '../types'
export interface AddressContextValue {
export interface AccountContextValue {
currAccount: AccountInfoCache | undefined
accounts: { [key: number]: AccountInfoCache } | undefined
setAccount: (info: AccountInfoCache) => void
removeAccount: (index: number) => void
}
const addressContext = createContext<AddressContextValue | undefined>(undefined)
const accountContext = createContext<AccountContextValue | undefined>(undefined)
export const useAccounts = () => {
const contextValue = useContext(addressContext)
const contextValue = useContext(accountContext)
if (contextValue === undefined)
throw new Error('`useAddress` must be used insisde a context `Provider`')
return contextValue
@ -36,7 +36,7 @@ export const useAccount = (index?: number) => {
: contextValue.currAccount
}
export const AddressProvider: FC<{
export const AccountProvider: FC<{
initialAccounts?: { [key: number]: AccountInfoCache } | undefined
initialAccountIndex?: number
}> = ({ children, initialAccounts, initialAccountIndex }) => {
@ -66,16 +66,23 @@ export const AddressProvider: FC<{
})
}, [])
const currAccount = accounts?.[currAccountIndex]
useEffect(() => {
if (currAccount !== undefined) {
}
}, [currAccount])
return (
<addressContext.Provider
<accountContext.Provider
value={{
accounts,
setAccount,
removeAccount,
currAccount: accounts?.[currAccountIndex],
currAccount,
}}
>
{children}
</addressContext.Provider>
</accountContext.Provider>
)
}

View file

@ -7,13 +7,12 @@ import {
useState,
} from 'react'
import { getPreference, putPreference } from '../db/preferences'
import {
PreferenceName,
PreferenceTypes,
ShowCurrencyPreference,
getPreference,
putPreference,
} from '../db/preferences'
} from '../db/types'
import useDarkMode from '../hooks/useDarkMode'
import isiOS from '../isiOS'

View file

@ -1,23 +1,25 @@
import Dexie, { Table } from 'dexie'
import db from '.'
import computeWorkAsync from '../computeWorkAsync'
import { AccountInfoCache } from '../types'
interface Account {
index: number
account: AccountInfoCache
export const addAccount = async (index: number, account: AccountInfoCache) => {
db.accounts.add({
...account,
index,
precomputedWork: null,
})
const precomputedWork = await computeWorkAsync(
account.frontier ?? account.publicKey
)
const hasWork = precomputedWork !== null
if (hasWork)
db.accounts.where({ index }).modify(account => {
if (account !== undefined) account.precomputedWork = precomputedWork
})
}
export type Key = number
export type Value = Account
export const schema = 'index,account'
export const addAccount = (index: number, account: AccountInfoCache) =>
db.accounts.add({ account, index })
export const putAccount = (index: number, account: AccountInfoCache) =>
db.accounts.put({ account, index })
db.accounts.update(index, { ...account })
export const removeAccount = (index: number) => db.accounts.delete(index)
@ -25,7 +27,39 @@ export const getAccount = (index: number) =>
db.accounts
.where({ index })
.first()
.then(res => (res === undefined ? undefined : res.account))
.then(res => (res === undefined ? undefined : res))
export const hasAccount = async (index: number) =>
(await db.accounts.where({ index }).count()) > 0
export const addPrecomputedWork = async (
address: string,
work?: string | null
) =>
db.accounts.where({ address }).modify(async account => {
if (work !== undefined && work !== null) account.precomputedWork = work
else {
const precomputedWork = await computeWorkAsync(
account.frontier ?? account.publicKey
)
if (precomputedWork !== null) account.precomputedWork = precomputedWork
}
})
export const getPrecomputedWork = async (address: string) =>
db.accounts
.where({ address })
.first()
.then(account => account?.precomputedWork)
export const consumePrecomputedWork = async (address: string) => {
const account = await db.accounts.where({ address }).first()
if (account === undefined) return undefined
db.accounts.where({ address }).modify(account => {
account.precomputedWork = null
})
const precomputedWork = await computeWorkAsync(
account.frontier ?? account.publicKey
)
addPrecomputedWork(address, precomputedWork)
}

View file

@ -1,18 +1,5 @@
import Dexie, { Table } from 'dexie'
import db from '.'
export type CryptoAssetId = 'challenge' | 'credentialId'
interface CryptoAsset {
id: CryptoAssetId
cryptoAsset: Uint8Array
}
export type Key = CryptoAssetId
export type Value = CryptoAsset
export const schema = 'id,cryptoAsset'
import { CryptoAssetId } from './types'
export const addCryptoAsset = (id: CryptoAssetId, cryptoAsset: Uint8Array) =>
db.cryptoAssets.add({ id, cryptoAsset })

37
lib/db/database.ts Normal file
View file

@ -0,0 +1,37 @@
import Dexie, { Table } from 'dexie'
import type {
AccountsKey,
AccountsValue,
CryptoAssetKey,
CryptoAssetValue,
EncryptedSeedKey,
EncryptedSeedValue,
PreferenceKey,
PreferenceValue,
} from './types'
import {
accountsSchema,
cryptoAssetSchema,
encryptedSeedSchema,
preferenceSchema,
} from './types'
class Database extends Dexie {
public encryptedSeeds!: Table<EncryptedSeedValue, EncryptedSeedKey>
public cryptoAssets!: Table<CryptoAssetValue, CryptoAssetKey>
public accounts!: Table<AccountsValue, AccountsKey>
public preferences!: Table<PreferenceValue, PreferenceKey>
public constructor() {
super('Database')
this.version(1).stores({
encryptedSeeds: encryptedSeedSchema,
cryptoAssets: cryptoAssetSchema,
preferences: preferenceSchema,
accounts: accountsSchema,
})
}
}
export default Database

View file

@ -1,18 +1,5 @@
import Dexie, { Table } from 'dexie'
import db from '.'
export type EncryptedSeedId = 'pin' | 'os'
interface EncryptedSeed {
id: EncryptedSeedId
encryptedSeed: string
}
export type Key = EncryptedSeedId
export type Value = EncryptedSeed
export const schema = 'id,encryptedSeed'
import { EncryptedSeedId } from './types'
export const addEncryptedSeed = (id: EncryptedSeedId, encryptedSeed: string) =>
db.encryptedSeeds.add({ id, encryptedSeed })

View file

@ -1,42 +1,4 @@
import Dexie, { Table } from 'dexie'
import {
Key as AccountsKey,
Value as AccountsValue,
schema as accountsSchema,
} from './accounts'
import {
Key as CryptoAssetsKey,
Value as CryptoAssetsValue,
schema as cryptoAssetsSchema,
} from './cryptoAssets'
import {
Key as EncryptedSeedsKey,
Value as EncryptedSeedsValue,
schema as encryptedSeedsSchema,
} from './encryptedSeeds'
import {
Key as PreferencesKey,
Value as PreferencesValue,
schema as preferencesSchema,
} from './preferences'
class Database extends Dexie {
public encryptedSeeds!: Table<EncryptedSeedsValue, EncryptedSeedsKey>
public cryptoAssets!: Table<CryptoAssetsValue, CryptoAssetsKey>
public accounts!: Table<AccountsValue, AccountsKey>
public preferences!: Table<PreferencesValue, PreferencesKey>
public constructor() {
super('Database')
this.version(1).stores({
encryptedSeeds: encryptedSeedsSchema,
cryptoAssets: cryptoAssetsSchema,
preferences: preferencesSchema,
accounts: accountsSchema,
})
}
}
import Database from './database'
const db = new Database()

View file

@ -1,31 +1,5 @@
import Dexie, { Table } from 'dexie'
import db from '.'
export enum ShowCurrencyPreference {
Xno = 'xno',
Both = 'both',
None = 'none',
}
export interface PreferenceTypes {
darkMode: boolean | undefined
biometricsAuth: boolean | undefined
leftHanded: boolean | undefined
showCurrencyDash: ShowCurrencyPreference | undefined
}
export type PreferenceName = keyof PreferenceTypes
interface Preference {
name: PreferenceName
value: string
}
export type Key = PreferenceName
export type Value = Preference
export const schema = 'name,value'
import { PreferenceName, PreferenceTypes } from './types'
export const addPreference = <P extends PreferenceName>(
name: P,

59
lib/db/types.ts Normal file
View file

@ -0,0 +1,59 @@
import { AccountInfoCache } from '../types'
export type EncryptedSeedId = 'pin' | 'os'
interface EncryptedSeed {
id: EncryptedSeedId
encryptedSeed: string
}
export type EncryptedSeedKey = EncryptedSeedId
export type EncryptedSeedValue = EncryptedSeed
export const encryptedSeedSchema = 'id'
interface Account extends AccountInfoCache {
index: number
precomputedWork: string | null
}
export type AccountsKey = number
export type AccountsValue = Account
export const accountsSchema = 'index,address'
export type CryptoAssetId = 'challenge' | 'credentialId'
interface CryptoAsset {
id: CryptoAssetId
cryptoAsset: Uint8Array
}
export type CryptoAssetKey = CryptoAssetId
export type CryptoAssetValue = CryptoAsset
export const cryptoAssetSchema = 'id'
export enum ShowCurrencyPreference {
Xno = 'xno',
Both = 'both',
None = 'none',
}
export interface PreferenceTypes {
darkMode: boolean | undefined
biometricsAuth: boolean | undefined
leftHanded: boolean | undefined
showCurrencyDash: ShowCurrencyPreference | undefined
}
export type PreferenceName = keyof PreferenceTypes
interface Preference {
name: PreferenceName
value: string
}
export type PreferenceKey = PreferenceName
export type PreferenceValue = Preference
export const preferenceSchema = 'name'

View file

@ -2,6 +2,7 @@ import { computeWork, hashBlock } from 'nanocurrency'
import { useCallback } from 'react'
import { useAccount } from '../context/accountContext'
import { consumeWork } from '../db/accounts'
import fetcher from '../fetcher'
import receiveNano from '../nano/receiveNano'
@ -22,7 +23,7 @@ const useReceiveNano = () => {
account.frontier ??
'0000000000000000000000000000000000000000000000000000000000000000',
amountRaw: amount,
work: (await computeWork(account.frontier ?? account.publicKey))!,
work: await consumeWork(account.address),
},
account.index
)

View file

@ -1,8 +1,7 @@
import { computeWork } from 'nanocurrency'
import { useCallback } from 'react'
import { useAccount } from '../context/accountContext'
import fetcher from '../fetcher'
import { consumePrecomputedWork, getPrecomputedWork } from '../db/accounts'
import sendNano from '../nano/sendNano'
const useSendNano = () => {
@ -17,7 +16,7 @@ const useSendNano = () => {
account.frontier === null
)
return
const signedBlock = sendNano(
const signedBlock = await sendNano(
{
walletBalanceRaw: account.balance,
fromAddress: account.address,
@ -25,11 +24,11 @@ const useSendNano = () => {
representativeAddress: account.representative,
frontier: account.frontier,
amountRaw: amount,
work: (await computeWork(account.frontier))!,
work: (await getPrecomputedWork(account.address)) ?? undefined,
},
account.index
)
return fetcher('https://mynano.ninja/api/node', {
return fetch('https://mynano.ninja/api/node', {
method: 'POST',
headers: [['Content-Type', 'application/json']],
body: JSON.stringify({
@ -39,6 +38,14 @@ const useSendNano = () => {
block: signedBlock,
}),
})
.then(res => {
if (!res.ok) throw new Error()
return res.json()
})
.then(data => {
if ('error' in data) throw new Error()
consumePrecomputedWork(account.address)
})
},
[account]
)

View file

@ -13,13 +13,18 @@ const useSetupAccounts = (skip?: boolean) => {
const dbAccount = await getAccount(0)
if (dbAccount !== undefined) {
setAccounts({ 0: dbAccount })
const infoRes = await fetchAccountInfo(dbAccount.address)
const infoResponse = await fetchAccountInfo(dbAccount.address)
const freshAccountInfo = {
...dbAccount,
frontier: 'error' in infoRes ? null : infoRes.confirmed_frontier,
frontier:
'error' in infoResponse ? null : infoResponse.confirmed_frontier,
representative:
'error' in infoRes ? null : infoRes.confirmed_representative,
'error' in infoResponse
? null
: infoResponse.confirmed_representative,
balance:
'error' in infoResponse ? null : infoResponse.confirmed_balance,
}
setAccounts({ 0: freshAccountInfo })
putAccount(dbAccount.index, freshAccountInfo)

View file

@ -1,15 +1,13 @@
import { AES } from 'crypto-js'
import { wallet } from 'nanocurrency-web'
import { useRouter } from 'next/router'
import { useCallback, useState } from 'react'
import { checkBiometrics, registerBiometrics } from '../biometrics'
import { registerBiometrics } from '../biometrics'
import { useAccounts } from '../context/accountContext'
import { addAccount } from '../db/accounts'
import { addEncryptedSeed, hasEncryptedSeed } from '../db/encryptedSeeds'
import encryptSeed from '../encryptSeed'
import accountAtIndex from '../nano/accountAtIndex'
import fetchAccountInfo from '../nano/fetchAccountInfo'
import useSetup from './useSetup'
const useSetupSeed = (skip?: boolean) => {
@ -33,12 +31,9 @@ const useSetupSeed = (skip?: boolean) => {
])
const { address, publicKey } = accountAtIndex(generatedSeed, 0)
const infoRes = await fetchAccountInfo(address)
const account = {
frontier: 'error' in infoRes ? null : infoRes.confirmed_frontier,
representative:
'error' in infoRes ? null : infoRes.confirmed_representative,
frontier: null,
representative: address,
balance: '0',
index: 0,
address,

3
lib/nano/isTxnUrl.ts Normal file
View file

@ -0,0 +1,3 @@
const isTxnUrl = (urlOrAddress: string) => urlOrAddress.startsWith('nano:')
export default isTxnUrl

View file

@ -1,3 +1,4 @@
import { hashBlock } from 'nanocurrency'
import { block } from 'nanocurrency-web'
import decryptSeed from '../decryptSeed'

View file

@ -1,56 +0,0 @@
import { computeWork, hashBlock } from 'nanocurrency'
import { block, wallet } from 'nanocurrency-web'
import decryptSeed from '../decryptSeed'
import fetcher from '../fetcher'
const receiveNano = async (myAddress: string, hash: string, amount: string) => {
const accountInfo: any = await fetcher('https://mynano.ninja/api/node', {
method: 'POST',
headers: [['Content-Type', 'application/json']],
body: JSON.stringify({
action: 'account_info',
account: myAddress,
}),
})
if ('error' in accountInfo) {
const seed = await decryptSeed('os')
console.log('start work')
const work = (await computeWork(wallet.accounts(seed, 0, 0)[0].publicKey, {
workThreshold: 'fffffe0000000000',
}))!
console.log('end work')
const data = {
walletBalanceRaw: '0',
toAddress: myAddress,
representativeAddress: myAddress,
frontier:
'0000000000000000000000000000000000000000000000000000000000000000',
amountRaw: amount,
transactionHash: hash,
work,
}
console.log(seed)
const signedBlock = block.receive(
data,
wallet.accounts(seed, 0, 0)[0].privateKey
)
console.log(signedBlock)
const res: any = await fetcher('https://mynano.ninja/api/node', {
method: 'POST',
headers: [['Content-Type', 'application/json']],
body: JSON.stringify({
action: 'process',
json_block: 'true',
subtype: 'open',
block: signedBlock,
}),
})
console.log(res)
}
}
export default receiveNano

View file

@ -0,0 +1,8 @@
const txnUrlToParts = (url: string) => {
console.log(url)
const [address] = url.split('nano:')[1].split('?')
const [amount] = url.split('amount')[1].split('&')
return { address, amount }
}
export default txnUrlToParts

8
lib/workComputer.ts Normal file
View file

@ -0,0 +1,8 @@
import { computeWork } from 'nanocurrency'
onmessage = async ev => {
console.time(`worker${ev.data.id}`)
const work = await computeWork(ev.data.frontier)
console.timeEnd(`worker${ev.data.id}`)
postMessage(work)
}

View file

@ -13,6 +13,7 @@
"format": "prettier --write .",
"gen:component": "hygen component new",
"gen:apiHandler": "hygen apiHandler new",
"gen:dataStore": "hygen dataStore new",
"gen:page": "hygen page new",
"gen:hook": "hygen hook new"
},

View file

@ -4,7 +4,7 @@ import { SWRConfig } from 'swr'
import 'tailwindcss/tailwind.css'
import Layout from '../components/Layout'
import { AddressProvider } from '../lib/context/accountContext'
import { AccountProvider } from '../lib/context/accountContext'
import { PreferencesProvider } from '../lib/context/preferencesContext'
import fetcher from '../lib/fetcher'
import useDarkMode from '../lib/hooks/useDarkMode'
@ -27,13 +27,13 @@ const MyApp: FC<AppProps> = ({ Component, pageProps }) => {
return (
<SWRConfig value={{ fetcher, provider: () => new Map() }}>
<AddressProvider initialAccounts={accounts} initialAccountIndex={0}>
<AccountProvider initialAccounts={accounts} initialAccountIndex={0}>
<PreferencesProvider>
<Layout>
<Component {...pageProps} />
</Layout>
</PreferencesProvider>
</AddressProvider>
</AccountProvider>
</SWRConfig>
)
}

24
pages/api/computeWork.ts Normal file
View file

@ -0,0 +1,24 @@
import { computeWork } from 'nanocurrency'
import { NextApiHandler } from 'next'
const get: NextApiHandler<string> = async (req, res) => {
if (req.method !== 'GET') {
res.status(405).end()
return
}
const { frontier } = req.query
console.time('computeWork')
const work = await computeWork(frontier as string)
console.timeEnd('computeWork')
if (work === null) {
res.status(500).send(JSON.stringify({ error: "Couldn't compute work" }))
return
}
res.status(200).send(JSON.stringify({ work }))
}
export default get

View file

@ -1,7 +1,7 @@
import type { NextPage } from 'next'
import { useAccount } from '../lib/context/accountContext'
import useDrawQrCode from '../lib/hooks/useDrawQrCode'
import { useAccount } from '../../lib/context/accountContext'
import useDrawQrCode from '../../lib/hooks/useDrawQrCode'
const MyQrCode: NextPage = () => {
const account = useAccount()

57
pages/send/index.tsx Normal file
View file

@ -0,0 +1,57 @@
import { UploadIcon } from '@heroicons/react/solid'
import { Unit, convert } from 'nanocurrency'
import type { NextPage } from 'next'
import { useRouter } from 'next/router'
import { useState } from 'react'
import useSendNano from '../../lib/hooks/useSendNano'
const Send: NextPage = () => {
const { query } = useRouter()
const { address, amount } = query
const hasAmount = amount !== undefined
const [xnoToSend, setXnoToSend] = useState('')
const send = useSendNano()
return (
<div>
{hasAmount ? (
<>
<h1>you&apos;re sending</h1>
<h2>
Ӿ {convert(amount as string, { from: Unit.raw, to: Unit.Nano })}
</h2>
<span>to</span>
<h2>{address}</h2>
</>
) : (
<>
<h1>send</h1>
<h2>Ӿ</h2>
<input
className="text-gray-900"
value={xnoToSend}
onChange={({ target: { value } }) => setXnoToSend(value)}
/>
<span>to</span>
<h2>{address}</h2>
</>
)}
<button
onClick={() =>
send(
address as string,
(amount as string) ??
convert(xnoToSend, { from: Unit.Nano, to: Unit.raw })
)
}
>
send <UploadIcon />
</button>
</div>
)
}
export default Send

View file

@ -1,13 +1,23 @@
import clsx from 'clsx'
import type { NextPage } from 'next'
import { useRouter } from 'next/router'
import { useCallback } from 'react'
import useReadQrFromVideo from '../lib/hooks/useReadQrFromVideo'
import useReadQrFromVideo from '../../lib/hooks/useReadQrFromVideo'
import isTxnUrl from '../../lib/nano/isTxnUrl'
import txnUrlToParts from '../../lib/nano/txnUrlToParts'
const ReadQrCode: NextPage = () => {
const onQrCodeRead = useCallback((url: string) => {
console.log(url)
}, [])
const { push } = useRouter()
const onQrCodeRead = useCallback(
(urlOrAddress: string) => {
if (isTxnUrl(urlOrAddress)) {
const { address, amount } = txnUrlToParts(urlOrAddress)
push(`/send?address=${address}&amount=${amount}`)
} else push(`/send?address=${urlOrAddress}`)
},
[push]
)
const { videoLive, videoRef } = useReadQrFromVideo(onQrCodeRead)
return (

View file

@ -9,13 +9,13 @@ const New: NextPage = () => {
const { push } = useRouter()
const [storing, setStoring] = useState(false)
useEffect(() => {
const copyAndGoToStore = async () => {
if (seed !== undefined) {
const copyAndGoToStore = async () => {
await navigator.clipboard.writeText(seed.mnemonic)
setStoring(true)
}
}
copyAndGoToStore()
}
}, [seed, push])
const onStoreClick = async () => {

View file

@ -1,3 +1,4 @@
[
"expand target gospel goose oppose acquire genius hurdle trade size huge pact square silk canal bar curve shallow pistol push crowd glory slice news"
"expand target gospel goose oppose acquire genius hurdle trade size huge pact square silk canal bar curve shallow pistol push crowd glory slice news",
"81631b9773ab0f4ff0d61a0ae092ec564acc01912aedbd849c420e10b2c38c7edc2c5494f778d3122a5933261c3a09b17f1b1be33d3b068d3228529a24bf94b3"
]