{t('overview.recent-activities')}
diff --git a/packages/neuron-ui/src/components/Receive/index.tsx b/packages/neuron-ui/src/components/Receive/index.tsx
index 3935bc7e4b..e5e27d3047 100644
--- a/packages/neuron-ui/src/components/Receive/index.tsx
+++ b/packages/neuron-ui/src/components/Receive/index.tsx
@@ -1,18 +1,12 @@
import React, { useState, useCallback, useMemo } from 'react'
import { RouteComponentProps } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
-import { Stack, Text, TextField, TooltipHost, Modal } from 'office-ui-fabric-react'
+import { Stack, Text, TextField, TooltipHost, Modal, FontSizes } from 'office-ui-fabric-react'
import { StateWithDispatch } from 'states/stateProvider/reducer'
import QRCode from 'widgets/QRCode'
import { Copy } from 'grommet-icons'
-declare global {
- interface Window {
- clipboard: any
- }
-}
-
const Receive = ({
wallet: { addresses = [] },
match: { params },
@@ -37,24 +31,35 @@ const Receive = ({
}
return (
-
- setShowLargeQRCode(true)} style={{ alignSelf: 'center' }}>
-
-
-
-
-
-
-
-
-
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ setShowLargeQRCode(true)} size={256} exportable />
+
+
setShowLargeQRCode(false)}>
-
+ >
)
}
diff --git a/packages/neuron-ui/src/components/Send/index.tsx b/packages/neuron-ui/src/components/Send/index.tsx
index a4c81d9277..bcf1f0e421 100644
--- a/packages/neuron-ui/src/components/Send/index.tsx
+++ b/packages/neuron-ui/src/components/Send/index.tsx
@@ -20,6 +20,7 @@ import { StateWithDispatch } from 'states/stateProvider/reducer'
import appState from 'states/initStates/app'
import { PlaceHolders, CapacityUnit } from 'utils/const'
+import { shannonToCKBFormatter } from 'utils/formatters'
import { useInitialize } from './hooks'
@@ -143,7 +144,7 @@ const Send = ({
- {`${t('send.balance')}: ${balance}`}
+ {`${t('send.balance')}: ${shannonToCKBFormatter(balance)} CKB`}
diff --git a/packages/neuron-ui/src/components/TransactionList/index.tsx b/packages/neuron-ui/src/components/TransactionList/index.tsx
index d4608b88ab..1a20ec5d0a 100644
--- a/packages/neuron-ui/src/components/TransactionList/index.tsx
+++ b/packages/neuron-ui/src/components/TransactionList/index.tsx
@@ -1,6 +1,7 @@
import React, { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import {
+ Stack,
Text,
DetailsList,
TextField,
@@ -8,16 +9,19 @@ import {
IGroup,
CheckboxVisibility,
ITextFieldStyleProps,
+ getTheme,
} from 'office-ui-fabric-react'
import { StateDispatch } from 'states/stateProvider/reducer'
import { appCalls } from 'services/UILayer'
import { useLocalDescription } from 'utils/hooks'
+import { shannonToCKBFormatter } from 'utils/formatters'
const timeFormatter = new Intl.DateTimeFormat('en-GB')
+const theme = getTheme()
-const MIN_CELL_WIDTH = 70
+const MIN_CELL_WIDTH = 50
interface FormatTransaction extends State.Transaction {
date: string
@@ -25,7 +29,20 @@ interface FormatTransaction extends State.Transaction {
const onRenderHeader = ({ group }: any) => {
const { name } = group
- return {name}
+ return (
+
+ {name}
+
+ )
}
const TransactionList = ({
@@ -56,13 +73,13 @@ const TransactionList = ({
const transactionColumns: IColumn[] = useMemo(
(): IColumn[] =>
[
- { name: t('history.type'), key: 'type', fieldName: 'type', minWidth: MIN_CELL_WIDTH, maxWidth: 150 },
+ { name: t('history.type'), key: 'type', fieldName: 'type', minWidth: MIN_CELL_WIDTH, maxWidth: 50 },
{
name: t('history.timestamp'),
key: 'timestamp',
fieldName: 'timestamp',
- minWidth: MIN_CELL_WIDTH,
- maxWidth: 150,
+ minWidth: 80,
+ maxWidth: 80,
onRender: (item?: FormatTransaction) => {
return item ? {new Date(+(item.timestamp || item.createdAt)).toLocaleTimeString()} : null
},
@@ -71,8 +88,18 @@ const TransactionList = ({
name: t('history.transaction-hash'),
key: 'hash',
fieldName: 'hash',
- minWidth: 300,
- maxWidth: 450,
+ minWidth: 100,
+ maxWidth: 500,
+ onRender: (item?: FormatTransaction) => {
+ if (item) {
+ return (
+
+ {item.hash}
+
+ )
+ }
+ return '-'
+ },
},
{ name: t('history.status'), key: 'status', fieldName: 'status', minWidth: MIN_CELL_WIDTH, maxWidth: 50 },
{
@@ -102,7 +129,23 @@ const TransactionList = ({
) : null
},
},
- { name: t('history.amount'), key: 'value', fieldName: 'value', minWidth: MIN_CELL_WIDTH, maxWidth: 300 },
+ {
+ name: t('history.amount'),
+ key: 'value',
+ fieldName: 'value',
+ minWidth: 100,
+ maxWidth: 300,
+ onRender: (item?: FormatTransaction) => {
+ if (item) {
+ return (
+
+ {`${shannonToCKBFormatter(item.value)} CKB`}
+
+ )
+ }
+ return '-'
+ },
+ },
].map(
(col): IColumn => ({ fieldName: col.key, ariaLabel: col.name, isResizable: true, isCollapsable: false, ...col })
),
@@ -161,6 +204,10 @@ const TransactionList = ({
display: 'flex',
alignItems: 'center',
},
+ '.text-overflow': {
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ },
},
},
}}
diff --git a/packages/neuron-ui/src/components/WalletSetting/index.tsx b/packages/neuron-ui/src/components/WalletSetting/index.tsx
index 697fb3b3f6..fe2c74a9e4 100644
--- a/packages/neuron-ui/src/components/WalletSetting/index.tsx
+++ b/packages/neuron-ui/src/components/WalletSetting/index.tsx
@@ -59,7 +59,6 @@ const WalletSetting = ({
key: wallet.id,
text: wallet.name,
checked: wallet.id === currentID,
- disabled: wallet.id === currentID,
onRenderLabel: ({ text }: IChoiceGroupOption) => {
return (
diff --git a/packages/neuron-ui/src/containers/Main/hooks.ts b/packages/neuron-ui/src/containers/Main/hooks.ts
index 4cbdf93224..4c2e1c44f3 100644
--- a/packages/neuron-ui/src/containers/Main/hooks.ts
+++ b/packages/neuron-ui/src/containers/Main/hooks.ts
@@ -1,14 +1,31 @@
/* globals BigInt */
import { useEffect } from 'react'
-import UILayer, { AppMethod, ChainMethod, NetworksMethod, TransactionsMethod, WalletsMethod } from 'services/UILayer'
-import { ckbCore, getTipBlockNumber } from 'services/chain'
-import { Routes, Channel, ConnectStatus } from 'utils/const'
import { WalletWizardPath } from 'components/WalletWizard'
import { NeuronWalletActions, StateDispatch, AppActions } from 'states/stateProvider/reducer'
import { actionCreators } from 'states/stateProvider/actionCreators'
import initStates from 'states/initStates'
+import UILayer, {
+ AppMethod,
+ ChainMethod,
+ NetworksMethod,
+ TransactionsMethod,
+ WalletsMethod,
+ walletsCall,
+ transactionsCall,
+ networksCall,
+} from 'services/UILayer'
+import { ckbCore, getTipBlockNumber } from 'services/chain'
+import { Routes, Channel, ConnectStatus } from 'utils/const'
+import {
+ wallets as walletsCache,
+ networks as networksCache,
+ addresses as addressesCache,
+ currentNetworkID as currentNetworkIDCache,
+ currentWallet as currentWalletCache,
+} from 'utils/localCache'
+
let timer: NodeJS.Timeout
const SYNC_INTERVAL_TIME = 10000
@@ -16,8 +33,70 @@ const addressesToBalance = (addresses: State.Address[] = []) => {
return addresses.reduce((total, addr) => total + BigInt(addr.balance || 0), BigInt(0)).toString()
}
-export const useChannelListeners = (i18n: any, history: any, chain: State.Chain, dispatch: StateDispatch) =>
+export const useChannelListeners = ({
+ walletID,
+ chain,
+ dispatch,
+ history,
+ i18n,
+}: {
+ walletID: string
+ chain: State.Chain
+ dispatch: StateDispatch
+ history: any
+ i18n: any
+}) =>
useEffect(() => {
+ UILayer.on(
+ Channel.DataUpdate,
+ (
+ _e: Event,
+ _actionType: 'create' | 'update' | 'delete',
+ dataType: 'address' | 'transaction' | 'wallet' | 'network'
+ ) => {
+ switch (dataType) {
+ case 'address': {
+ walletsCall.getAllAddresses(walletID)
+ break
+ }
+ case 'transaction': {
+ transactionsCall.getAllByKeywords({
+ walletID,
+ keywords: chain.transactions.keywords,
+ pageNo: chain.transactions.pageNo,
+ pageSize: chain.transactions.pageSize,
+ })
+ transactionsCall.get(walletID, chain.transaction.hash)
+ break
+ }
+ case 'wallet': {
+ walletsCall.getAll()
+ walletsCall.getCurrent()
+ break
+ }
+ case 'network': {
+ networksCall.getAll()
+ networksCall.currentID()
+ break
+ }
+ default: {
+ walletsCall.getCurrent()
+ walletsCall.getAll()
+ walletsCall.getAllAddresses(walletID)
+ networksCall.currentID()
+ networksCall.getAll()
+ transactionsCall.getAllByKeywords({
+ walletID,
+ keywords: chain.transactions.keywords,
+ pageNo: chain.transactions.pageNo,
+ pageSize: chain.transactions.pageSize,
+ })
+ transactionsCall.get(walletID, chain.transaction.hash)
+ break
+ }
+ }
+ }
+ )
UILayer.on(
Channel.Initiate,
(
@@ -69,6 +148,12 @@ export const useChannelListeners = (i18n: any, history: any, chain: State.Chain,
transactions: { ...chain.transactions, ...transactions },
},
})
+
+ currentWalletCache.save(wallet)
+ currentNetworkIDCache.save(networkID)
+ walletsCache.save(wallets)
+ addressesCache.save(addresses)
+ networksCache.save(networks)
} else {
/* eslint-disable no-alert */
// TODO: better prompt, prd required
@@ -221,6 +306,7 @@ export const useChannelListeners = (i18n: any, history: any, chain: State.Chain,
type: NeuronWalletActions.Settings,
payload: { wallets: args.result },
})
+ walletsCache.save(args.result)
if (!args.result.length) {
history.push(`${Routes.WalletWizard}${WalletWizardPath.Welcome}`)
}
@@ -231,6 +317,7 @@ export const useChannelListeners = (i18n: any, history: any, chain: State.Chain,
type: NeuronWalletActions.Wallet,
payload: args.result,
})
+ currentWalletCache.save(args.result)
break
}
case WalletsMethod.SendCapacity: {
@@ -257,6 +344,7 @@ export const useChannelListeners = (i18n: any, history: any, chain: State.Chain,
balance: addressesToBalance(addresses),
},
})
+ addressesCache.save(addresses)
break
}
case WalletsMethod.RequestPassword: {
@@ -298,8 +386,9 @@ export const useChannelListeners = (i18n: any, history: any, chain: State.Chain,
case NetworksMethod.GetAll: {
dispatch({
type: NeuronWalletActions.Settings,
- payload: { networks: args.result },
+ payload: { networks: args.result || [] },
})
+ networksCache.save(args.result || [])
break
}
case NetworksMethod.CurrentID: {
@@ -307,6 +396,7 @@ export const useChannelListeners = (i18n: any, history: any, chain: State.Chain,
type: NeuronWalletActions.Chain,
payload: { networkID: args.result },
})
+ currentNetworkIDCache.save(args.result)
break
}
case NetworksMethod.Create:
@@ -336,7 +426,7 @@ export const useChannelListeners = (i18n: any, history: any, chain: State.Chain,
})
}
})
- }, [i18n, chain, dispatch, history])
+ }, [walletID, i18n, chain, dispatch, history])
export const useSyncTipBlockNumber = ({
networks,
@@ -374,7 +464,27 @@ export const useSyncTipBlockNumber = ({
}, [networks, networkID, dispatch])
}
+export const useOnCurrentWalletChange = ({ walletID, chain }: { walletID: string; chain: State.Chain }) => {
+ useEffect(() => {
+ walletsCall.getAllAddresses(walletID)
+ transactionsCall.getAllByKeywords({
+ walletID,
+ keywords: chain.transactions.keywords,
+ pageNo: chain.transactions.pageNo,
+ pageSize: chain.transactions.pageSize,
+ })
+ transactionsCall.get(walletID, chain.transaction.hash)
+ }, [
+ walletID,
+ chain.transactions.pageNo,
+ chain.transactions.pageSize,
+ chain.transactions.keywords,
+ chain.transaction.hash,
+ ])
+}
+
export default {
useChannelListeners,
useSyncTipBlockNumber,
+ useOnCurrentWalletChange,
}
diff --git a/packages/neuron-ui/src/containers/Main/index.tsx b/packages/neuron-ui/src/containers/Main/index.tsx
index 3fb4cb5f2d..d1766f7049 100644
--- a/packages/neuron-ui/src/containers/Main/index.tsx
+++ b/packages/neuron-ui/src/containers/Main/index.tsx
@@ -20,7 +20,7 @@ import PasswordRequest from 'components/PasswordRequest'
import { Routes } from 'utils/const'
-import { useChannelListeners, useSyncTipBlockNumber } from './hooks'
+import { useChannelListeners, useSyncTipBlockNumber, useOnCurrentWalletChange } from './hooks'
export const mainContents: CustomRouter.Route[] = [
{
@@ -108,12 +108,22 @@ const MainContent = ({
}: React.PropsWithoutRef<{ dispatch: StateDispatch } & RouteComponentProps>) => {
const neuronWalletState = useState()
const [, i18n] = useTranslation()
- useChannelListeners(i18n, history, neuronWalletState.chain, dispatch)
+ useChannelListeners({
+ walletID: neuronWalletState.wallet.id,
+ chain: neuronWalletState.chain,
+ dispatch,
+ history,
+ i18n,
+ })
useSyncTipBlockNumber({
networkID: neuronWalletState.chain.networkID,
networks: neuronWalletState.settings.networks,
dispatch,
})
+ useOnCurrentWalletChange({
+ walletID: neuronWalletState.wallet.id,
+ chain: neuronWalletState.chain,
+ })
return (
<>
{mainContents.map(container => (
diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json
index f321655dd1..3528caf284 100644
--- a/packages/neuron-ui/src/locales/en.json
+++ b/packages/neuron-ui/src/locales/en.json
@@ -71,8 +71,8 @@
"receive": {
"click-to-copy": "Click to copy the address",
"address-not-found": "Address not found",
- "prompt": "Neuron will update the receiving address after transaction for the security sake. Please go to the advance view if used receiving addresses are needed",
- "address-qrcode": "Address QRCode"
+ "prompt": "Neuron picks a new receiving address for better privacy. Please go to the Address Book if you want to use a previously used receiving addresses.",
+ "address-qrcode": "Address QR Code"
},
"history": {
"meta": "Meta",
@@ -160,6 +160,10 @@
"title": "Backup the {{name}} wallet"
}
},
+ "qrcode": {
+ "copy": "Copy image",
+ "save": "Save image"
+ },
"footer": {
"fail-to-fetch-tip-block-number": "Cannot fetch tip block number"
},
diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json
index 5627620fc5..41cc71b1c9 100644
--- a/packages/neuron-ui/src/locales/zh.json
+++ b/packages/neuron-ui/src/locales/zh.json
@@ -71,7 +71,7 @@
"receive": {
"click-to-copy": "点击复制地址",
"address-not-found": "未找到地址",
- "prompt": "为了保护隐私,Neuron 会在每次收到转账后更新收款地址。如果您想引用用旧的收款地址,请访问高级页面。",
+ "prompt": "为了保护隐私,Neuron 会怎么选择一个新收款地址。如果您想使用旧的收款地址,请访问地址管理页面。",
"address-qrcode": "地址二维码"
},
"history": {
@@ -160,6 +160,10 @@
"title": "备份钱包 {{name}}"
}
},
+ "qrcode": {
+ "copy": "复制图片",
+ "save": "保存图片"
+ },
"footer": {
"fail-to-fetch-tip-block-number": "无法获取最新高度"
},
diff --git a/packages/neuron-ui/src/services/UILayer.ts b/packages/neuron-ui/src/services/UILayer.ts
index 4b9c8164c4..1e30928ee2 100644
--- a/packages/neuron-ui/src/services/UILayer.ts
+++ b/packages/neuron-ui/src/services/UILayer.ts
@@ -32,6 +32,7 @@ export enum WalletsMethod {
AllAddresses = 'allAddresses',
UpdateAddressDescription = 'updateAddressDescription',
RequestPassword = 'requestPassword',
+ GetAllAddresses = 'getAllAddresses',
}
export enum NetworksMethod {
@@ -104,7 +105,7 @@ export const networksCall = instantiateMethodCall(networks) as {
create: (network: State.NetworkProperty) => void
update: (id: string, options: Partial) => void
delete: (id: string) => void
- currentOne: () => void
+ currentID: () => void
activate: (id: string) => void
}
@@ -163,6 +164,7 @@ export const walletsCall = instantiateMethodCall(wallets) as {
fee: string
description: string
}) => void
+ getAllAddresses: (id: string) => void
updateAddressDescription: (params: { walletID: string; address: string; description: string }) => void
}
diff --git a/packages/neuron-ui/src/states/initStates/chain.ts b/packages/neuron-ui/src/states/initStates/chain.ts
index 9d0ff5e592..d177d00ed2 100644
--- a/packages/neuron-ui/src/states/initStates/chain.ts
+++ b/packages/neuron-ui/src/states/initStates/chain.ts
@@ -1,5 +1,7 @@
+import { currentNetworkID } from 'utils/localCache'
+
const chainState: State.Chain = {
- networkID: '',
+ networkID: currentNetworkID.load(),
connectStatus: 'offline',
tipBlockNumber: '',
transaction: {
diff --git a/packages/neuron-ui/src/states/initStates/settings.ts b/packages/neuron-ui/src/states/initStates/settings.ts
index f675580f64..3b1bb12a22 100644
--- a/packages/neuron-ui/src/states/initStates/settings.ts
+++ b/packages/neuron-ui/src/states/initStates/settings.ts
@@ -1,9 +1,9 @@
-import { addressBook } from 'utils/localCache'
+import { addressBook, wallets, networks } from 'utils/localCache'
export const settingsState: State.Settings = {
showAddressBook: addressBook.isVisible(),
- networks: [],
- wallets: [],
+ networks: networks.load(),
+ wallets: wallets.load(),
}
export default settingsState
diff --git a/packages/neuron-ui/src/states/initStates/wallet.ts b/packages/neuron-ui/src/states/initStates/wallet.ts
index 86cf813c87..7ce6e47bbb 100644
--- a/packages/neuron-ui/src/states/initStates/wallet.ts
+++ b/packages/neuron-ui/src/states/initStates/wallet.ts
@@ -1,8 +1,12 @@
+import { addresses, currentWallet } from 'utils/localCache'
+
+const wallet = currentWallet.load()
+
export const walletState: State.Wallet = {
- name: '',
- id: '',
+ name: wallet.name || '',
+ id: wallet.id || '',
balance: '0',
- addresses: [],
+ addresses: addresses.load(),
sending: false,
}
diff --git a/packages/neuron-ui/src/types/global/index.d.ts b/packages/neuron-ui/src/types/global/index.d.ts
index fbb57324b7..fe2d5e238f 100644
--- a/packages/neuron-ui/src/types/global/index.d.ts
+++ b/packages/neuron-ui/src/types/global/index.d.ts
@@ -3,6 +3,7 @@ declare interface Window {
remote: any
require: any
bridge: any
+ nativeImage: any
}
declare module '*.json' {
diff --git a/packages/neuron-ui/src/utils/const.ts b/packages/neuron-ui/src/utils/const.ts
index d3f059d778..6e680b32a4 100644
--- a/packages/neuron-ui/src/utils/const.ts
+++ b/packages/neuron-ui/src/utils/const.ts
@@ -22,6 +22,7 @@ export enum Channel {
Transactions = 'transactions',
Wallets = 'wallets',
Helpers = 'helpers',
+ DataUpdate = 'dataUpdate',
}
export enum Routes {
diff --git a/packages/neuron-ui/src/utils/formatters.test.ts b/packages/neuron-ui/src/utils/formatters.test.ts
index 072bcdb303..8b0c218954 100644
--- a/packages/neuron-ui/src/utils/formatters.test.ts
+++ b/packages/neuron-ui/src/utils/formatters.test.ts
@@ -1,49 +1,48 @@
import { CapacityUnit } from 'utils/const'
-import { currencyFormatter, currencyCode, CKBToShannonFormatter } from 'utils/formatters'
+import { currencyFormatter, currencyCode, CKBToShannonFormatter, shannonToCKBFormatter } from 'utils/formatters'
-describe('formatters', () => {
- it('currencyFormatter', () => {
+describe(`formatters`, () => {
+ it(`currencyFormatter`, () => {
const fixtures = [
{
source: {
- shannons: '1234567890',
- unit: 'CKB' as currencyCode,
- exchange: '0.000000001',
+ shannons: `1234567890`,
+ unit: `CKB` as currencyCode,
+ exchange: `0.000000001`,
},
- target: '1.23456789 CKB',
+ target: `1.23456789 CKB`,
},
{
source: {
- shannons: '1234567890',
- unit: 'CKB' as currencyCode,
- exchange: '0.00065',
+ shannons: `1234567890`,
+ unit: `CKB` as currencyCode,
+ exchange: `0.00065`,
},
- target: '802,469.1285 CKB',
+ target: `802,469.1285 CKB`,
},
{
source: {
- shannons: '1234567890',
- unit: 'CNY' as currencyCode,
- exchange: '0.00065',
+ shannons: `1234567890`,
+ unit: `CNY` as currencyCode,
+ exchange: `0.00065`,
},
- target: '802,469.1285 CNY',
+ target: `802,469.1285 CNY`,
},
{
source: {
- shannons: '1234567890123456789012345678901234567890123456789012345678901234567890',
- unit: 'CNY' as currencyCode,
- exchange: '0.65',
+ shannons: `1234567890123456789012345678901234567890123456789012345678901234567890`,
+ unit: `CNY` as currencyCode,
+ exchange: `0.65`,
},
- target: '802,469,128,580,246,912,858,024,691,285,802,469,128,580,246,912,858,024,691,285,802,469,128.5 CNY',
+ target: `802,469,128,580,246,912,858,024,691,285,802,469,128,580,246,912,858,024,691,285,802,469,128.5 CNY`,
},
{
source: {
- shannons: '12345678901234567890123456789012345678901234567890123456789012345678901234',
- unit: 'CNY' as currencyCode,
- exchange: '0.65',
+ shannons: `12345678901234567890123456789012345678901234567890123456789012345678901234`,
+ unit: `CNY` as currencyCode,
+ exchange: `0.65`,
},
- target:
- '8,024,691,285,802,469,128,580,246,912,858,024,691,285,802,469,128,580,246,912,858,024,691,285,802.1 CNY',
+ target: `8,024,691,285,802,469,128,580,246,912,858,024,691,285,802,469,128,580,246,912,858,024,691,285,802.1 CNY`,
},
]
fixtures.forEach(fixture => {
@@ -52,75 +51,110 @@ describe('formatters', () => {
})
})
- it('CKB Formatter', () => {
- const fixtures = [
- {
- source: {
- amount: '1.234',
- uint: CapacityUnit.CKB,
+ describe(`CKB Formatter`, () => {
+ it(`CKB to Shannon`, () => {
+ const fixtures = [
+ {
+ source: {
+ amount: `1.234`,
+ uint: CapacityUnit.CKB,
+ },
+ target: `123400000`,
},
- target: '123400000',
- },
- {
- source: {
- amount: '1.23456789',
- uint: CapacityUnit.CKB,
+ {
+ source: {
+ amount: `1.23456789`,
+ uint: CapacityUnit.CKB,
+ },
+ target: `123456789`,
},
- target: '123456789',
- },
- {
- source: {
- amount: '1.0',
- uint: CapacityUnit.CKB,
+ {
+ source: {
+ amount: `1.0`,
+ uint: CapacityUnit.CKB,
+ },
+ target: `100000000`,
},
- target: '100000000',
- },
- {
- source: {
- amount: '1.',
- uint: CapacityUnit.CKB,
+ {
+ source: {
+ amount: `1.`,
+ uint: CapacityUnit.CKB,
+ },
+ target: `100000000`,
},
- target: '100000000',
- },
- {
- source: {
- amount: '0.123',
- uint: CapacityUnit.CKB,
+ {
+ source: {
+ amount: `0.123`,
+ uint: CapacityUnit.CKB,
+ },
+ target: `12300000`,
},
- target: '12300000',
- },
- {
- source: {
- amount: '.123',
- uint: CapacityUnit.CKB,
+ {
+ source: {
+ amount: `.123`,
+ uint: CapacityUnit.CKB,
+ },
+ target: `12300000`,
},
- target: '12300000',
- },
- {
- source: {
- amount: '12345678901234567890123456789012345678901234567890123456789012345678901234',
- uint: CapacityUnit.CKB,
+ {
+ source: {
+ amount: `12345678901234567890123456789012345678901234567890123456789012345678901234`,
+ uint: CapacityUnit.CKB,
+ },
+ target: `1234567890123456789012345678901234567890123456789012345678901234567890123400000000`,
},
- target: '1234567890123456789012345678901234567890123456789012345678901234567890123400000000',
- },
- {
- source: {
- amount: '12345678901234567890123456789012345678901234567890123456789012345678901234',
- uint: CapacityUnit.CKKB,
+ {
+ source: {
+ amount: `12345678901234567890123456789012345678901234567890123456789012345678901234`,
+ uint: CapacityUnit.CKKB,
+ },
+ target: `1234567890123456789012345678901234567890123456789012345678901234567890123400000000000`,
},
- target: '1234567890123456789012345678901234567890123456789012345678901234567890123400000000000',
- },
- {
- source: {
- amount: '12345678901234567890123456789012345678901234567890123456789012345678901234',
- uint: CapacityUnit.CKGB,
+ {
+ source: {
+ amount: `12345678901234567890123456789012345678901234567890123456789012345678901234`,
+ uint: CapacityUnit.CKGB,
+ },
+ target: `1234567890123456789012345678901234567890123456789012345678901234567890123400000000000000000`,
},
- target: '1234567890123456789012345678901234567890123456789012345678901234567890123400000000000000000',
- },
- ]
+ ]
- fixtures.forEach(fixture => {
- expect(CKBToShannonFormatter(fixture.source.amount, fixture.source.uint)).toBe(fixture.target)
+ fixtures.forEach(fixture => {
+ expect(CKBToShannonFormatter(fixture.source.amount, fixture.source.uint)).toBe(fixture.target)
+ })
+ })
+
+ it(`shannon to CKB`, () => {
+ const fixtures = [
+ {
+ source: `123`,
+ target: `0.00000123`,
+ },
+ {
+ source: `12300000`,
+ target: `0.123`,
+ },
+ {
+ source: `123000000`,
+ target: `1.23`,
+ },
+ {
+ source: `1234567890123456789012345678901234567890123456789012345678901234567890123400000000`,
+ target: `12345678901234567890123456789012345678901234567890123456789012345678901234`,
+ },
+ {
+ source: `12345678901234567890123456789012345678901234567890123456789012345678901234`,
+ target: `123456789012345678901234567890123456789012345678901234567890123456.78901234`,
+ },
+ {
+ source: `1234567890123456789012345678901234567890123456789012345678901234567890123400`,
+ target: `12345678901234567890123456789012345678901234567890123456789012345678.901234`,
+ },
+ ]
+
+ fixtures.forEach(fixture => {
+ expect(shannonToCKBFormatter(fixture.source)).toBe(fixture.target)
+ })
})
})
})
diff --git a/packages/neuron-ui/src/utils/formatters.ts b/packages/neuron-ui/src/utils/formatters.ts
index ffd7d20425..9091d202d1 100644
--- a/packages/neuron-ui/src/utils/formatters.ts
+++ b/packages/neuron-ui/src/utils/formatters.ts
@@ -72,6 +72,21 @@ export const CKBToShannonFormatter = (amount: string, uint: CapacityUnit) => {
}
}
+export const shannonToCKBFormatter = (shannon: string) => {
+ let ckbStr = [...shannon.padStart(8, '0')]
+ .map((char, idx) => {
+ if (idx === Math.max(shannon.length - 8, 0)) {
+ return `.${char}`
+ }
+ return char
+ })
+ .join('')
+ if (ckbStr.startsWith('.')) {
+ ckbStr = `0${ckbStr}`
+ }
+ return ckbStr.replace(/\.?0+$/, '')
+}
+
export const localNumberFormatter = (num: string | number = 0) => {
return numberFormatter.format(+num)
}
@@ -80,5 +95,6 @@ export default {
queryFormatter,
currencyFormatter,
CKBToShannonFormatter,
+ shannonToCKBFormatter,
localNumberFormatter,
}
diff --git a/packages/neuron-ui/src/utils/localCache.ts b/packages/neuron-ui/src/utils/localCache.ts
index 59cd751ca6..f8426fbe25 100644
--- a/packages/neuron-ui/src/utils/localCache.ts
+++ b/packages/neuron-ui/src/utils/localCache.ts
@@ -1,5 +1,10 @@
export enum LocalCacheKey {
AddressBookVisibility = 'address-book-visibility',
+ Addresses = 'addresses',
+ Networks = 'networks',
+ Wallets = 'wallets',
+ CurrentWallet = 'currentWallet',
+ CurrentNetworkID = 'currentNetworkID',
}
enum AddressBookVisibility {
Invisible = '0',
@@ -20,7 +25,111 @@ export const addressBook = {
},
}
+export const addresses = {
+ save: (addressList: State.Address[]) => {
+ if (!Array.isArray(addressList)) {
+ return false
+ }
+ const addressesStr = JSON.stringify(addressList)
+ window.localStorage.setItem(LocalCacheKey.Addresses, addressesStr)
+ return true
+ },
+ load: () => {
+ const addressesStr = window.localStorage.getItem(LocalCacheKey.Addresses) || `[]`
+ try {
+ const addressList = JSON.parse(addressesStr)
+ if (!Array.isArray(addressList)) {
+ throw new TypeError(`Addresses should be type fo Address[]`)
+ }
+ return addressList
+ } catch (err) {
+ console.error(err)
+ return []
+ }
+ },
+}
+
+export const networks = {
+ save: (networkList: State.Network[]) => {
+ if (!Array.isArray(networkList)) {
+ return false
+ }
+ const networksStr = JSON.stringify(networkList)
+ window.localStorage.setItem(LocalCacheKey.Networks, networksStr)
+ return true
+ },
+ load: () => {
+ const networksStr = window.localStorage.getItem(LocalCacheKey.Networks) || `[]`
+ try {
+ const networkList = JSON.parse(networksStr)
+ if (!Array.isArray(networkList)) {
+ throw new TypeError(`Networks should be type of Network[]`)
+ }
+ return networkList
+ } catch (err) {
+ console.error(err)
+ return []
+ }
+ },
+}
+
+export const wallets = {
+ save: (walletList: State.WalletIdentity[]) => {
+ if (!Array.isArray(walletList)) {
+ return false
+ }
+ const walletsStr = JSON.stringify(walletList)
+ window.localStorage.setItem(LocalCacheKey.Wallets, walletsStr)
+ return true
+ },
+ load: () => {
+ const walletsStr = window.localStorage.getItem(LocalCacheKey.Wallets) || `[]`
+ try {
+ const walletList = JSON.parse(walletsStr)
+ if (!Array.isArray(walletList)) {
+ throw new TypeError(`Wallets should be type of WalletIdentity[]`)
+ }
+ return walletList
+ } catch (err) {
+ console.error(err)
+ return []
+ }
+ },
+}
+
+export const currentWallet = {
+ save: (wallet: State.WalletIdentity | null) => {
+ const walletStr = JSON.stringify({ id: '', name: '', ...wallet })
+ window.localStorage.setItem(LocalCacheKey.CurrentWallet, walletStr)
+ return true
+ },
+ load: (): { [index: string]: string } => {
+ const walletStr = window.localStorage.getItem(LocalCacheKey.CurrentWallet) || '{}'
+ try {
+ return JSON.parse(walletStr)
+ } catch (err) {
+ console.error(`Cannot parse current wallet`)
+ return {}
+ }
+ },
+}
+
+export const currentNetworkID = {
+ save: (networkID: string = '') => {
+ window.localStorage.setItem(LocalCacheKey.CurrentNetworkID, networkID)
+ return true
+ },
+ load: () => {
+ return window.localStorage.getItem(LocalCacheKey.CurrentNetworkID) || ''
+ },
+}
+
export default {
LocalCacheKey,
addressBook,
+ addresses,
+ networks,
+ wallets,
+ currentWallet,
+ currentNetworkID,
}
diff --git a/packages/neuron-ui/src/widgets/QRCode/index.tsx b/packages/neuron-ui/src/widgets/QRCode/index.tsx
index 60c539ab58..6b77bd126f 100644
--- a/packages/neuron-ui/src/widgets/QRCode/index.tsx
+++ b/packages/neuron-ui/src/widgets/QRCode/index.tsx
@@ -1,5 +1,8 @@
/* eslint-disable no-bitwise */
-import React from 'react'
+import React, { useEffect, useRef, useCallback } from 'react'
+import canvg from 'canvg'
+import { Stack, DefaultButton } from 'office-ui-fabric-react'
+import { useTranslation } from 'react-i18next'
const QRCodeImpl = require('qr.js/lib/QRCode')
@@ -70,36 +73,89 @@ const generatePath = (cells: boolean[][], margin: number = 0): string => {
const QRCode = ({
value,
size = 128,
+ scale = 4,
level = ErrorCorrectLevel.Q,
bgColor = '#FFF',
fgColor = '#000',
+ onQRCodeClick,
includeMargin = false,
- ...otherProps
+ exportable = false,
}: {
value: string
size: number
+ scale?: number
level?: ErrorCorrectLevel
bgColor?: string
fgColor?: string
+ onQRCodeClick?: React.MouseEventHandler
includeMargin?: boolean
+ exportable?: boolean
}) => {
+ const [t] = useTranslation()
const qrcode = new QRCodeImpl(-1, level)
+ const canvasRef = useRef(null)
qrcode.addData(convertStr(value))
qrcode.make()
- const cells = qrcode.modules
- if (cells === null) {
- return null
- }
+ const cells = qrcode.modules || []
const margin = includeMargin ? 4 : 0
const fgPath = generatePath(cells, margin)
const numCells = cells.length + margin * 2
+
+ const svgStr = ``
+
+ const onDownload = useCallback(() => {
+ if (canvasRef.current === null) {
+ return
+ }
+ const dataURL = canvasRef.current.toDataURL('image/png')
+ const downloadLink = document.createElement('a')
+ downloadLink.download = 'Receive'
+ downloadLink.href = dataURL
+ window.document.body.appendChild(downloadLink)
+ downloadLink.click()
+ window.document.body.removeChild(downloadLink)
+ }, [])
+
+ const onCopy = useCallback(() => {
+ if (canvasRef.current === null) {
+ return
+ }
+ const dataURL = canvasRef.current.toDataURL('image/png')
+ const img = window.nativeImage.createFromDataURL(dataURL)
+ window.clipboard.writeImage(img)
+ }, [])
+
+ useEffect(() => {
+ if (canvasRef.current !== null) {
+ canvg(canvasRef.current, svgStr, {
+ enableRedraw: false,
+ ignoreMouse: true,
+ renderCallback: () => {
+ if (canvasRef.current) {
+ canvasRef.current.setAttribute(`style`, `width:${size}p;height:${size}px`)
+ }
+ },
+ })
+ }
+ }, [svgStr, size])
+
return (
-
+
+
+
+
+ {exportable ? (
+
+ {t('qrcode.copy')}
+ {t('qrcode.save')}
+
+ ) : null}
+
)
}
+QRCode.display = 'QRCode'
+
export default QRCode
diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json
index 16713d5f3d..9a2281feda 100644
--- a/packages/neuron-wallet/package.json
+++ b/packages/neuron-wallet/package.json
@@ -3,7 +3,7 @@
"productName": "Neuron",
"description": "CKB Neuron Wallet",
"homepage": "https://www.nervos.org/",
- "version": "0.1.0-alpha.7",
+ "version": "0.1.0-alpha.8",
"private": true,
"author": {
"name": "Nervos Core Dev",
@@ -48,7 +48,7 @@
},
"devDependencies": {
"@nervosnetwork/ckb-types": "0.15.1",
- "@nervosnetwork/neuron-ui": "0.1.0-alpha.7",
+ "@nervosnetwork/neuron-ui": "0.1.0-alpha.8",
"@types/async": "3.0.0",
"@types/electron-devtools-installer": "2.2.0",
"@types/elliptic": "6.4.8",
diff --git a/packages/neuron-wallet/src/controllers/index.ts b/packages/neuron-wallet/src/controllers/index.ts
index f4b2b33132..4ba08e834c 100644
--- a/packages/neuron-wallet/src/controllers/index.ts
+++ b/packages/neuron-wallet/src/controllers/index.ts
@@ -3,6 +3,7 @@ import NetworksController from './networks'
import WalletsController from './wallets'
import TransactionsController from './transactions'
import HelpersController from './helpers'
+import SyncInfoController from './sync-info'
export default {
AppController,
@@ -10,4 +11,5 @@ export default {
WalletsController,
TransactionsController,
HelpersController,
+ SyncInfoController,
}
diff --git a/packages/neuron-wallet/src/controllers/sync-info.ts b/packages/neuron-wallet/src/controllers/sync-info.ts
new file mode 100644
index 0000000000..80519b4e7a
--- /dev/null
+++ b/packages/neuron-wallet/src/controllers/sync-info.ts
@@ -0,0 +1,18 @@
+import { CatchControllerError } from '../decorators'
+import BlockNumber from '../services/sync/block-number'
+import { ResponseCode } from '../utils/const'
+
+export default class SyncInfoController {
+ @CatchControllerError
+ public static async currentBlockNumber() {
+ const blockNumber = new BlockNumber()
+ const current: bigint = await blockNumber.getCurrent()
+
+ return {
+ status: ResponseCode.Success,
+ result: {
+ currentBlockNumber: current.toString(),
+ },
+ }
+ }
+}
diff --git a/packages/neuron-wallet/src/controllers/wallets/index.ts b/packages/neuron-wallet/src/controllers/wallets/index.ts
index 9730a203fc..3e61c64a99 100644
--- a/packages/neuron-wallet/src/controllers/wallets/index.ts
+++ b/packages/neuron-wallet/src/controllers/wallets/index.ts
@@ -274,6 +274,15 @@ export default class WalletsController {
})
}
+ @CatchControllerError
+ public static async getCurrent() {
+ const currentWallet = WalletsService.getInstance().getCurrent() || null
+ return {
+ status: ResponseCode.Success,
+ result: currentWallet,
+ }
+ }
+
@CatchControllerError
public static async activate(id: string) {
const walletsService = WalletsService.getInstance()
diff --git a/packages/neuron-wallet/src/database/chain/entities/transaction.ts b/packages/neuron-wallet/src/database/chain/entities/transaction.ts
index 5b36c9e66f..8b0a664167 100644
--- a/packages/neuron-wallet/src/database/chain/entities/transaction.ts
+++ b/packages/neuron-wallet/src/database/chain/entities/transaction.ts
@@ -90,12 +90,14 @@ export default class Transaction extends BaseEntity {
outputs!: OutputEntity[]
public toInterface(): TransactionInterface {
+ const inputs = this.inputs ? this.inputs.map(input => input.toInterface()) : []
+ const outputs = this.outputs ? this.outputs.map(output => output.toInterface()) : []
return {
hash: this.hash,
version: this.version,
deps: this.deps,
- inputs: this.inputs.map(input => input.toInterface()),
- outputs: this.outputs.map(output => output.toInterface()),
+ inputs,
+ outputs,
timestamp: this.timestamp,
blockNumber: this.blockNumber,
blockHash: this.blockHash,
diff --git a/packages/neuron-wallet/src/models/subjects/data-update.ts b/packages/neuron-wallet/src/models/subjects/data-update.ts
new file mode 100644
index 0000000000..636e5ab53c
--- /dev/null
+++ b/packages/neuron-wallet/src/models/subjects/data-update.ts
@@ -0,0 +1,13 @@
+import { Subject } from 'rxjs'
+import windowManager from '../window-manager'
+
+const DataUpdateSubject = new Subject<{
+ dataType: 'address' | 'transaction' | 'wallet' | 'network'
+ actionType: 'create' | 'update' | 'delete'
+}>()
+
+DataUpdateSubject.subscribe(({ dataType, actionType }) => {
+ windowManager.broadcastDataUpdateMessage(actionType, dataType)
+})
+
+export default DataUpdateSubject
diff --git a/packages/neuron-wallet/src/models/subjects/networks.ts b/packages/neuron-wallet/src/models/subjects/networks.ts
index ad1d58d566..56dcf54594 100644
--- a/packages/neuron-wallet/src/models/subjects/networks.ts
+++ b/packages/neuron-wallet/src/models/subjects/networks.ts
@@ -1,6 +1,6 @@
import { Subject } from 'rxjs'
import { debounceTime } from 'rxjs/operators'
-import { broadcastCurrentNetworkID, broadcastNetworkList } from '../../utils/broadcast'
+import DataUpdateSubject from './data-update'
const DEBOUNCE_TIME = 50
@@ -9,14 +9,12 @@ export const NetworkListSubject = new Subject<{
}>()
export const CurrentNetworkIDSubject = new Subject<{ currentNetworkID: Controller.NetworkID }>()
-NetworkListSubject.pipe(debounceTime(DEBOUNCE_TIME)).subscribe(
- ({ currentNetworkList = [] }: { currentNetworkList: Controller.Network[] }) => {
- broadcastNetworkList(currentNetworkList)
- }
-)
+NetworkListSubject.pipe(debounceTime(DEBOUNCE_TIME)).subscribe(() => {
+ DataUpdateSubject.next({ dataType: 'network', actionType: 'update' })
+})
-CurrentNetworkIDSubject.pipe(debounceTime(DEBOUNCE_TIME)).subscribe(({ currentNetworkID = '' }) => {
- broadcastCurrentNetworkID(currentNetworkID)
+CurrentNetworkIDSubject.pipe(debounceTime(DEBOUNCE_TIME)).subscribe(() => {
+ DataUpdateSubject.next({ dataType: 'network', actionType: 'update' })
})
export default {
diff --git a/packages/neuron-wallet/src/models/subjects/tx-db-changed-subject.ts b/packages/neuron-wallet/src/models/subjects/tx-db-changed-subject.ts
index 982e7d053e..3d84741f79 100644
--- a/packages/neuron-wallet/src/models/subjects/tx-db-changed-subject.ts
+++ b/packages/neuron-wallet/src/models/subjects/tx-db-changed-subject.ts
@@ -1,11 +1,6 @@
import { ReplaySubject } from 'rxjs'
-import { delay } from 'rxjs/operators'
-import { ResponseCode, Channel } from '../../utils/const'
import { Transaction } from '../../types/cell-types'
-import windowManager from '../window-manager'
-import TransactionsService from '../../services/transactions'
-import AddressService from '../../services/addresses'
-import LockUtils from '../lock-utils'
+import DataUpdateSubject from './data-update'
export interface TransactionChangedMessage {
event: string
@@ -27,24 +22,10 @@ export class TxDbChangedSubject {
}
static subscribe = () => {
- // TODO: since typeorm not provide afterCommit hooks, delay for wait transaction committed
- TxDbChangedSubject.subject.pipe(delay(100)).subscribe(async ({ tx }) => {
- const transaction = await TransactionsService.get(tx.hash)
- if (!transaction) {
- return
- }
- const blake160s = TransactionsService.blake160sOfTx(transaction)
- const addresses = blake160s.map(blake160 => LockUtils.blake160ToAddress(blake160))
- const addrs = await AddressService.findByAddresses(addresses)
- const walletIDs = addrs.map(addr => addr.walletId)
- const uniqueWalletIDs = [...new Set(walletIDs)]
- const result = {
- tx: transaction,
- walletIDs: JSON.stringify(uniqueWalletIDs),
- }
- windowManager.broadcast(Channel.Transactions, 'transactionUpdated', {
- status: ResponseCode.Success,
- result,
+ TxDbChangedSubject.subject.subscribe(() => {
+ DataUpdateSubject.next({
+ dataType: 'transaction',
+ actionType: 'update',
})
})
}
diff --git a/packages/neuron-wallet/src/models/subjects/wallets.ts b/packages/neuron-wallet/src/models/subjects/wallets.ts
index 5d6ba19ed1..8e04885f46 100644
--- a/packages/neuron-wallet/src/models/subjects/wallets.ts
+++ b/packages/neuron-wallet/src/models/subjects/wallets.ts
@@ -1,12 +1,7 @@
import { Subject } from 'rxjs'
import { debounceTime } from 'rxjs/operators'
import { updateApplicationMenu } from '../../utils/application-menu'
-import {
- broadcastCurrentWallet,
- broadcastWalletList,
- broadcastAddressList,
- broadcastTransactions,
-} from '../../utils/broadcast'
+import dataUpdateSubject from './data-update'
const DEBOUNCE_TIME = 50
@@ -24,18 +19,16 @@ export const CurrentWalletSubject = new Subject<{
WalletListSubject.pipe(debounceTime(DEBOUNCE_TIME)).subscribe(({ currentWallet = null, currentWalletList = [] }) => {
const walletList = currentWalletList.map(({ id, name }) => ({ id, name }))
const currentWalletId = currentWallet ? currentWallet.id : null
- broadcastWalletList(walletList)
+ dataUpdateSubject.next({ dataType: 'wallet', actionType: 'update' })
updateApplicationMenu(walletList, currentWalletId)
})
CurrentWalletSubject.pipe(debounceTime(DEBOUNCE_TIME)).subscribe(async ({ currentWallet = null, walletList = [] }) => {
- broadcastCurrentWallet(currentWallet)
updateApplicationMenu(walletList, currentWallet ? currentWallet.id : null)
if (!currentWallet) {
return
}
- broadcastAddressList(currentWallet.id)
- broadcastTransactions(currentWallet.id)
+ dataUpdateSubject.next({ dataType: 'wallet', actionType: 'update' })
})
export default {
diff --git a/packages/neuron-wallet/src/models/window-manager.ts b/packages/neuron-wallet/src/models/window-manager.ts
index 5cd1c692cd..3efe4c079a 100644
--- a/packages/neuron-wallet/src/models/window-manager.ts
+++ b/packages/neuron-wallet/src/models/window-manager.ts
@@ -8,7 +8,7 @@ interface SendMessage {
(channel: Channel.App, method: Controller.AppMethod, params: any): void
(
channel: Channel.Wallets,
- method: Controller.WalletsMethod | 'allAddresses' | 'sendingStatus' | 'getCurrent' | 'requestPassword',
+ method: Controller.WalletsMethod | 'allAddresses' | 'sendingStatus' | 'requestPassword',
params: any
): void
(channel: Channel.Transactions, method: Controller.TransactionsMethod | 'transactionUpdated', params: any): void
@@ -31,6 +31,21 @@ export default class WindowManager {
})
}
+ public static broadcastDataUpdateMessage = (
+ actionType: 'create' | 'update' | 'delete',
+ dataType: 'address' | 'transaction' | 'wallet' | 'network'
+ ) => {
+ if (!BrowserWindow) {
+ logger.log(error)
+ return
+ }
+ BrowserWindow.getAllWindows().forEach(window => {
+ if (window && window.webContents) {
+ window.webContents.send(Channel.DataUpdate, actionType, dataType)
+ }
+ })
+ }
+
public static sendToFocusedWindow: SendMessage = (channel: Channel, method: string, params: any): void => {
if (!BrowserWindow) {
logger.log(error)
diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts
index 2916d4dc55..83a8483131 100644
--- a/packages/neuron-wallet/src/services/wallets.ts
+++ b/packages/neuron-wallet/src/services/wallets.ts
@@ -17,9 +17,9 @@ import Keychain from '../models/keys/keychain'
import AddressDbChangedSubject from '../models/subjects/address-db-changed-subject'
import AddressesUsedSubject from '../models/subjects/addresses-used-subject'
import { WalletListSubject, CurrentWalletSubject } from '../models/subjects/wallets'
-import { broadcastAddressList } from '../utils/broadcast'
import { Channel, ResponseCode } from '../utils/const'
import windowManager from '../models/window-manager'
+import dataUpdateSubject from '../models/subjects/data-update'
const { core } = NodeService.getInstance()
const fileService = FileService.getInstance()
@@ -139,11 +139,11 @@ export default class WalletService {
AddressDbChangedSubject.getSubject()
.pipe(debounceTime(DEBOUNCE_TIME))
- .subscribe(async () => {
- const currentWallet = this.getCurrent()
- if (currentWallet) {
- broadcastAddressList(currentWallet.id)
- }
+ .subscribe(() => {
+ dataUpdateSubject.next({
+ dataType: 'address',
+ actionType: 'update',
+ })
})
}
diff --git a/packages/neuron-wallet/src/startup/preload.ts b/packages/neuron-wallet/src/startup/preload.ts
index 20d040a98b..c4c3549ea4 100644
--- a/packages/neuron-wallet/src/startup/preload.ts
+++ b/packages/neuron-wallet/src/startup/preload.ts
@@ -1,9 +1,10 @@
-import { ipcRenderer, clipboard } from 'electron'
+import { ipcRenderer, clipboard, nativeImage } from 'electron'
declare global {
interface Window {
bridge: any
clipboard: Electron.Clipboard
+ nativeImage: any
}
}
@@ -32,3 +33,4 @@ if (process.env.NODE_ENV === 'development') {
window.clipboard = clipboard
window.bridge = window.bridge || bridge
+window.nativeImage = nativeImage
diff --git a/packages/neuron-wallet/src/startup/sync-block-task/create.ts b/packages/neuron-wallet/src/startup/sync-block-task/create.ts
index 89c4b6960b..d9724cf2a1 100644
--- a/packages/neuron-wallet/src/startup/sync-block-task/create.ts
+++ b/packages/neuron-wallet/src/startup/sync-block-task/create.ts
@@ -4,16 +4,13 @@ import path from 'path'
import { networkSwitchSubject, NetworkWithID } from '../../services/networks'
import env from '../../env'
import genesisBlockHash from './genesis'
-import CellsService from '../../services/cells'
-import LockUtils from '../../models/lock-utils'
import AddressService from '../../services/addresses'
import initDatabase from './init-database'
export { genesisBlockHash }
const updateAllAddressesTxCount = async () => {
- const blake160s: string[] = await CellsService.allBlake160s()
- const addresses = blake160s.map(blake160 => LockUtils.blake160ToAddress(blake160))
+ const addresses = (await AddressService.allAddresses()).map(addr => addr.address)
await AddressService.updateTxCountAndBalances(addresses)
}
diff --git a/packages/neuron-wallet/src/utils/broadcast.ts b/packages/neuron-wallet/src/utils/broadcast.ts
deleted file mode 100644
index 673528293e..0000000000
--- a/packages/neuron-wallet/src/utils/broadcast.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import windowManager from '../models/window-manager'
-import AddressService from '../services/addresses'
-import TransactionsService from '../services/transactions'
-import { Channel, ResponseCode } from './const'
-
-export const broadcastWalletList = (walletList: Controller.Wallet[]) => {
- windowManager.broadcast(Channel.Wallets, 'getAll', {
- status: ResponseCode.Success,
- result: walletList,
- })
-}
-
-export const broadcastCurrentWallet = (wallet: Controller.Wallet | null) => {
- const currentWallet = wallet ? { id: wallet.id, name: wallet.name } : null
- windowManager.broadcast(Channel.Wallets, 'getCurrent', {
- status: ResponseCode.Success,
- result: currentWallet,
- })
-}
-
-export const broadcastAddressList = async (currentID: string) => {
- const addresses = await AddressService.allAddressesByWalletId(currentID).then(addrs =>
- addrs.map(({ address, blake160: identifier, addressType: type, txCount, balance, description = '' }) => ({
- address,
- identifier,
- type,
- txCount,
- description,
- balance,
- }))
- )
- windowManager.broadcast(Channel.Wallets, 'allAddresses', {
- status: ResponseCode.Success,
- result: addresses,
- })
-}
-
-export const broadcastTransactions = async (currentID: string) => {
- const addresses = await AddressService.usedAddresses(currentID)
- const params = {
- pageNo: 1,
- pageSize: 100,
- addresses: addresses.map(addr => addr.address),
- }
- const transactions = await TransactionsService.getAllByAddresses(params)
- windowManager.broadcast(Channel.Transactions, 'getAllByAddresses', {
- status: ResponseCode.Success,
- result: { ...params, keywords: '', ...transactions },
- })
-}
-
-export const broadcastNetworkList = async (networkList: Controller.Network[]) => {
- windowManager.broadcast(Channel.Networks, 'getAll', {
- status: ResponseCode.Success,
- result: networkList,
- })
-}
-
-export const broadcastCurrentNetworkID = async (id: Controller.NetworkID) => {
- windowManager.broadcast(Channel.Networks, 'currentID', {
- status: ResponseCode.Success,
- result: id,
- })
-}
-
-export default {
- broadcastCurrentWallet,
- broadcastWalletList,
- broadcastAddressList,
- broadcastTransactions,
- broadcastNetworkList,
- broadcastCurrentNetworkID,
-}
diff --git a/packages/neuron-wallet/src/utils/const.ts b/packages/neuron-wallet/src/utils/const.ts
index 5a8b30b1b2..949c7172fc 100644
--- a/packages/neuron-wallet/src/utils/const.ts
+++ b/packages/neuron-wallet/src/utils/const.ts
@@ -9,6 +9,7 @@ export enum Channel {
Wallets = 'wallets',
Transactions = 'transactions',
Helpers = 'helpers',
+ DataUpdate = 'dataUpdate',
}
export enum ResponseCode {
diff --git a/packages/neuron-wallet/tests/services/networks.test.ts b/packages/neuron-wallet/tests/services/networks.test.ts
index fdac1ce302..bee217f537 100644
--- a/packages/neuron-wallet/tests/services/networks.test.ts
+++ b/packages/neuron-wallet/tests/services/networks.test.ts
@@ -1,5 +1,4 @@
import NetworksService, { NetworkWithID } from '../../src/services/networks'
-
import env from '../../src/env'
import i18n from '../../src/utils/i18n'
@@ -12,26 +11,38 @@ const ERROR_MESSAGE = {
const {
presetNetworks: { current, list },
} = env
-
-const newNetwork: NetworkWithID = {
- name: `new network`,
- remote: `http://new-network.localhost.com`,
- type: 0,
- id: '',
-}
-
-const newNetworkWithDefaultTypeOf1 = {
- name: `new network with the default type of 1`,
- remote: `http://test.localhost.com`,
- id: '',
-}
-
-describe(`networks service`, () => {
- const service = new NetworksService()
- afterAll(() => {
+const [testnetNetwork, localNetwork] = list
+
+describe(`Unit tests of networks service`, () => {
+ const newNetwork: NetworkWithID = {
+ name: `new network`,
+ remote: `http://new-network.localhost.com`,
+ type: 0,
+ id: '',
+ }
+
+ const newNetworkWithDefaultTypeOf1 = {
+ name: `new network with the default type of 1`,
+ remote: `http://test.localhost.com`,
+ id: '',
+ }
+
+ let service: NetworksService = new NetworksService()
+
+ beforeEach(done => {
+ service = new NetworksService()
+ setTimeout(() => {
+ done()
+ }, 1000)
+ })
+ afterEach(done => {
service.clear()
+ setTimeout(() => {
+ done()
+ }, 1000)
})
- describe(`operations on networks succeed`, () => {
+
+ describe(`success cases`, () => {
it(`get all networks`, async () => {
const networks = await service.getAll()
expect(Array.isArray(networks)).toBe(true)
@@ -42,14 +53,20 @@ describe(`networks service`, () => {
expect(networks).toEqual(list)
})
- it(`has a default current network`, async () => {
- const currentNetworkID = await service.getCurrentID()
- expect(currentNetworkID).toBe(current)
+ it(`get the default network`, async () => {
+ const network = await service.defaultOne()
+ expect(network && network.type).toBe(0)
})
- it(`has testnet as the default current network`, async () => {
+ it(`testnet should be type of default network`, async () => {
const defaultNetwork = await service.defaultOne()
- expect(defaultNetwork).toEqual(list[0])
+ expect(defaultNetwork).toEqual(testnetNetwork)
+ })
+
+ it(`testnet should be the current one by default`, async () => {
+ const currentNetworkID = await service.getCurrentID()
+ expect(currentNetworkID).toBe(current)
+ expect(currentNetworkID).toBe(testnetNetwork.id)
})
it(`get network by id ${current}`, async () => {
@@ -57,94 +74,90 @@ describe(`networks service`, () => {
expect(currentNetwork).toEqual(list.find(network => network.id === current))
})
- it(`get network by id which not exists`, async () => {
+ it(`getting a non-exsiting network should return null`, async () => {
const id = `not-existing-id`
const network = await service.get(id)
expect(network).toBeNull()
})
- it(`create new network with ${JSON.stringify(newNetwork)}`, async () => {
+ it(`create a new network with ${JSON.stringify(newNetwork)}`, async () => {
const res = await service.create(newNetwork.name, newNetwork.remote, newNetwork.type)
- newNetwork.id = res.id
- expect(res).toMatchObject(newNetwork)
+ expect(res).toMatchObject({ ...newNetwork, id: res.id })
const created = await service.get(res.id)
expect(created).toEqual(res)
})
- it(`create new network with default type of 1`, async () => {
+ it(`create a new network with default type of 1`, async () => {
const res = await service.create(newNetworkWithDefaultTypeOf1.name, newNetworkWithDefaultTypeOf1.remote)
- newNetworkWithDefaultTypeOf1.id = res.id
expect(res.type).toBe(1)
})
- it(`update new network's name`, async () => {
- const name = `updated network name`
- await service.update(newNetwork.id, { name })
- const network = await service.get(newNetwork.id)
+ it(`update the local networks's name`, async () => {
+ const name = `new local network name`
+ await service.update(localNetwork.id, { name })
+ const network = await service.get(localNetwork.id)
expect(network && network.name).toBe(name)
})
- it(`update network address`, async () => {
+ it(`update the local network address`, async () => {
const addr = `http://updated-address.com`
- await service.update(newNetwork.id, { remote: addr })
- const network = await service.get(newNetwork.id)
+ await service.update(localNetwork.id, { remote: addr })
+ const network = await service.get(localNetwork.id)
expect(network && network.remote).toBe(addr)
})
- it(`update network type`, async () => {
+ it(`update the local network type to 1`, async () => {
const type = 1
- await service.update(newNetwork.id, { type })
- const network = await service.get(newNetwork.id)
+ await service.update(localNetwork.id, { type })
+ const network = await service.get(localNetwork.id)
expect(network && network.type).toBe(type)
})
- it(`activate the second network`, async () => {
- const { id } = list[1]
- await service.activate(id)
+ it(`set the local network to be the current one`, async () => {
+ await service.activate(localNetwork.id)
const currentNetworkID = await service.getCurrentID()
- expect(currentNetworkID).toBe(id)
+ expect(currentNetworkID).toBe(localNetwork.id)
})
- it(`delete inactive network`, async () => {
- const prevCurrentID = await service.getCurrentID()
+ it(`delete an inactive network`, async () => {
+ const inactiveNetwork = localNetwork
+ const prevCurrentID = (await service.getCurrentID()) || ''
const prevNetworks = await service.getAll()
- await service.delete(newNetwork.id)
+ await service.delete(inactiveNetwork.id)
const currentID = await service.getCurrentID()
const currentNetworks = await service.getAll()
- expect(currentNetworks).toEqual(prevNetworks.filter(n => n.id !== newNetwork.id))
+ expect(currentNetworks).toEqual(prevNetworks.filter(n => n.id !== inactiveNetwork.id))
expect(currentID).toBe(prevCurrentID)
})
- it(`delete current network and switch to the default one`, async () => {
- const { id } = list[1]
- const defaultNetwork = list[0]
- await service.activate(id)
+ it(`activate the local network and delete it, the current networks should switch to the testnet network`, async () => {
+ await service.activate(localNetwork.id)
const prevCurrentID = await service.getCurrentID()
const prevNetworks = await service.getAll()
- await service.delete(id)
+ expect(prevCurrentID).toBe(localNetwork.id)
+ expect(prevNetworks).toEqual(list)
+ await service.delete(prevCurrentID || '')
const currentNetworks = await service.getAll()
- expect(currentNetworks).toEqual(prevNetworks.filter(n => n.id !== id))
- expect(prevCurrentID).not.toBe(defaultNetwork.id)
+ expect(currentNetworks).toEqual(prevNetworks.filter(n => n.id !== prevCurrentID))
const currentID = await new Promise(resolve => {
setTimeout(() => {
service.getCurrentID().then(cID => resolve(cID))
}, 500)
})
- expect(currentID).toBe(defaultNetwork.id)
+ expect(currentID).toBe(testnetNetwork.id)
})
- it(`get the default network`, async () => {
- const network = await service.defaultOne()
- expect(network && network.type).toBe(0)
- })
-
- it(`reset netowrks`, async () => {
+ it(`reset the netowrks`, async () => {
+ await service.create(newNetwork.name, newNetwork.remote)
+ const newNetworkList = await service.getAll()
+ expect(newNetworkList.length).toBe(list.length + 1)
service.clear()
const networks = await service.getAll()
expect(networks.length).toBe(list.length)
})
})
- describe(`operations on networks throw errors`, () => {
+
+ describe(`validation on parameters`, () => {
describe(`validation on parameters`, () => {
it(`service.get requires id`, () => {
expect(service.get(undefined as any)).rejects.toThrowError(i18n.t(ERROR_MESSAGE.MISSING_ARG))
diff --git a/yarn.lock b/yarn.lock
index fb1e6e5d16..f9987d1158 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5174,6 +5174,14 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000939, caniuse-lite@^1.0.30000955, can
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000974.tgz#b7afe14ee004e97ce6dc73e3f878290a12928ad8"
integrity sha512-xc3rkNS/Zc3CmpMKuczWEdY2sZgx09BkAxfvkxlAEBTqcMHeL8QnPqhKse+5sRTi3nrw2pJwToD2WvKn1Uhvww==
+canvg@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/canvg/-/canvg-2.0.0.tgz#9ff77bf8837106e358a12fb3d2bf11aeb60e5b46"
+ integrity sha512-PiKa+sjzzAv8HONsBaJZRhZ3eCM5uJkpFgF0rSzcamOrdXdls81ukjNxtz7JYyxucj6WpIkZwk9j7Jku0+ivqQ==
+ dependencies:
+ rgbcolor "^1.0.1"
+ stackblur-canvas "^2.0.0"
+
capture-exit@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
@@ -14756,6 +14764,11 @@ rgba-regex@^1.0.0:
resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
+rgbcolor@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/rgbcolor/-/rgbcolor-1.0.1.tgz#d6505ecdb304a6595da26fa4b43307306775945d"
+ integrity sha1-1lBezbMEplldom+ktDMHMGd1lF0=
+
right-pad@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.1.tgz#8ca08c2cbb5b55e74dafa96bf7fd1a27d568c8d0"
@@ -15552,6 +15565,11 @@ stack-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8"
integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==
+stackblur-canvas@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/stackblur-canvas/-/stackblur-canvas-2.2.0.tgz#cacc5924a0744b3e183eb2e6c1d8559c1a17c26e"
+ integrity sha512-5Gf8dtlf8k6NbLzuly2NkGrkS/Ahh+I5VUjO7TnFizdJtgpfpLLEdQlLe9umbcnZlitU84kfYjXE67xlSXfhfQ==
+
staged-git-files@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-1.1.2.tgz#4326d33886dc9ecfa29a6193bf511ba90a46454b"