feat: :(
This commit is contained in:
parent
3e271c68d7
commit
a92cafff92
|
@ -19,6 +19,3 @@ const handler: NextApiHandler<ResponseBody> = (req, res) => {
|
|||
}
|
||||
|
||||
export default handler
|
||||
|
||||
|
||||
|
||||
|
|
15
_templates/dataStore/new/dataStore.ejs.t
Normal file
15
_templates/dataStore/new/dataStore.ejs.t
Normal 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'
|
||||
|
|
@ -8,6 +8,3 @@ const <%= name %> = () => {
|
|||
}
|
||||
|
||||
export default <%= name %>
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
45
lib/computeWorkAsync.ts
Normal 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
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
37
lib/db/database.ts
Normal 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
|
|
@ -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 })
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
59
lib/db/types.ts
Normal 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'
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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]
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
3
lib/nano/isTxnUrl.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
const isTxnUrl = (urlOrAddress: string) => urlOrAddress.startsWith('nano:')
|
||||
|
||||
export default isTxnUrl
|
|
@ -1,3 +1,4 @@
|
|||
import { hashBlock } from 'nanocurrency'
|
||||
import { block } from 'nanocurrency-web'
|
||||
|
||||
import decryptSeed from '../decryptSeed'
|
||||
|
|
|
@ -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
|
8
lib/nano/txnUrlToParts.ts
Normal file
8
lib/nano/txnUrlToParts.ts
Normal 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
8
lib/workComputer.ts
Normal 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)
|
||||
}
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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
24
pages/api/computeWork.ts
Normal 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
|
|
@ -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
57
pages/send/index.tsx
Normal 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'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
|
|
@ -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 (
|
|
@ -9,13 +9,13 @@ const New: NextPage = () => {
|
|||
const { push } = useRouter()
|
||||
const [storing, setStoring] = useState(false)
|
||||
useEffect(() => {
|
||||
const copyAndGoToStore = async () => {
|
||||
if (seed !== undefined) {
|
||||
if (seed !== undefined) {
|
||||
const copyAndGoToStore = async () => {
|
||||
await navigator.clipboard.writeText(seed.mnemonic)
|
||||
setStoring(true)
|
||||
}
|
||||
copyAndGoToStore()
|
||||
}
|
||||
copyAndGoToStore()
|
||||
}, [seed, push])
|
||||
|
||||
const onStoreClick = async () => {
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
|
|
Reference in a new issue