diff --git a/CHANGELOG.md b/CHANGELOG.md index 28d0b2fe98..e195f0def8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +## [0.23.1](https://github.com/nervosnetwork/neuron/compare/v0.23.0...v0.23.1) (2019-10-28) + + +### Bug Fixes + +* break => continue ([05ae69e](https://github.com/nervosnetwork/neuron/commit/05ae69e)) +* return => break ([97b8ea4](https://github.com/nervosnetwork/neuron/commit/97b8ea4)) + + +### Features + +* Add a few db indices ([83dffb5](https://github.com/nervosnetwork/neuron/commit/83dffb5)) +* display disconnection errors and dismiss them on getting connec… ([#1019](https://github.com/nervosnetwork/neuron/issues/1019)) ([e866c66](https://github.com/nervosnetwork/neuron/commit/e866c66)) +* Optimize output db query ([6e36550](https://github.com/nervosnetwork/neuron/commit/6e36550)) +* stringify the result from api controller ([1f4d4ea](https://github.com/nervosnetwork/neuron/commit/1f4d4ea)) +* **neuron-ui:** add a tooltip to display synchronized block number and the tip block number ([9e52fef](https://github.com/nervosnetwork/neuron/commit/9e52fef)) +* **neuron-ui:** display confirmations of pending transactions in the recent activity list ([9aebede](https://github.com/nervosnetwork/neuron/commit/9aebede)) +* **neuron-ui:** update history list to make it more compact ([35d8b87](https://github.com/nervosnetwork/neuron/commit/35d8b87)) +* **neuron-ui:** update the Send View according to the new transaction fee model. ([dc415f3](https://github.com/nervosnetwork/neuron/commit/dc415f3)) + + + # [0.23.0](https://github.com/nervosnetwork/neuron/compare/v0.22.2...v0.23.0) (2019-10-23) diff --git a/lerna.json b/lerna.json index 5ca6b56c1f..2dda7cfd60 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "0.23.0", + "version": "0.23.1", "npmClient": "yarn", "useWorkspaces": true } diff --git a/package.json b/package.json index 4a3bc1a13d..81517a7c77 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "neuron", "productName": "Neuron", "description": "CKB Neuron Wallet", - "version": "0.23.0", + "version": "0.23.1", "private": true, "author": { "name": "Nervos Core Dev", diff --git a/packages/neuron-ui/package.json b/packages/neuron-ui/package.json index b6f9ba3d03..7167113c56 100644 --- a/packages/neuron-ui/package.json +++ b/packages/neuron-ui/package.json @@ -1,6 +1,6 @@ { "name": "neuron-ui", - "version": "0.23.0", + "version": "0.23.1", "private": true, "author": { "name": "Nervos Core Dev", diff --git a/packages/neuron-ui/src/components/CustomRows/ActivityRow.tsx b/packages/neuron-ui/src/components/CustomRows/ActivityRow.tsx index 692d395c89..7b83f0be21 100644 --- a/packages/neuron-ui/src/components/CustomRows/ActivityRow.tsx +++ b/packages/neuron-ui/src/components/CustomRows/ActivityRow.tsx @@ -34,13 +34,13 @@ const ActivityRow = (props?: ActivityRowProps) => { return (
{`${typeLabel} ${shannonToCKBFormatter(value)} CKB`}
{statusLabel}
{time}
-
{status === 'success' ? confirmations : ''}
+
{confirmations}
) } diff --git a/packages/neuron-ui/src/components/CustomRows/GroupHeader.tsx b/packages/neuron-ui/src/components/CustomRows/GroupHeader.tsx new file mode 100644 index 0000000000..29d727052b --- /dev/null +++ b/packages/neuron-ui/src/components/CustomRows/GroupHeader.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import { Stack, Text, getTheme } from 'office-ui-fabric-react' + +const { + palette: { neutralLighterAlt, neutralSecondary, neutralLighter }, +} = getTheme() + +const GroupHeader = ({ group }: any) => { + const { name } = group + return ( + + {name} + + ) +} +export default GroupHeader diff --git a/packages/neuron-ui/src/components/CustomRows/HistoryRow.tsx b/packages/neuron-ui/src/components/CustomRows/HistoryRow.tsx new file mode 100644 index 0000000000..3a3e0a4539 --- /dev/null +++ b/packages/neuron-ui/src/components/CustomRows/HistoryRow.tsx @@ -0,0 +1,22 @@ +import React from 'react' +import { DetailsRow, IDetailsRowProps } from 'office-ui-fabric-react' + +const HistoryRow = (props?: IDetailsRowProps) => { + return props ? ( + + ) : null +} + +export default HistoryRow diff --git a/packages/neuron-ui/src/components/Overview/index.tsx b/packages/neuron-ui/src/components/Overview/index.tsx index bbc376b1e3..8694c28e1f 100644 --- a/packages/neuron-ui/src/components/Overview/index.tsx +++ b/packages/neuron-ui/src/components/Overview/index.tsx @@ -118,7 +118,7 @@ const Overview = ({ const activityItems: ActivityItem[] = useMemo( () => items.map(item => { - let confirmations = '(-)' + let confirmations = '' let typeLabel: string = item.type let { status } = item if (item.blockNumber !== undefined) { @@ -147,7 +147,7 @@ const Overview = ({ status, statusLabel: t(`overview.statusLabel.${status}`), value: item.value.replace(/^-/, ''), - confirmations: item.status === 'success' ? confirmations : '', + confirmations: ['success', 'pending'].includes(item.status) ? confirmations : '', typeLabel: t(`overview.${typeLabel}`), } }), diff --git a/packages/neuron-ui/src/components/PasswordRequest/index.tsx b/packages/neuron-ui/src/components/PasswordRequest/index.tsx index b892f50401..89a2885a44 100644 --- a/packages/neuron-ui/src/components/PasswordRequest/index.tsx +++ b/packages/neuron-ui/src/components/PasswordRequest/index.tsx @@ -4,11 +4,10 @@ import { useTranslation } from 'react-i18next' import { Stack, Text, Label, Modal, TextField, PrimaryButton, DefaultButton } from 'office-ui-fabric-react' import { StateWithDispatch, AppActions } from 'states/stateProvider/reducer' import { sendTransaction, deleteWallet, backupWallet } from 'states/stateProvider/actionCreators' -import { priceToFee, CKBToShannonFormatter } from 'utils/formatters' const PasswordRequest = ({ app: { - send: { txID, outputs, description, price, cycles }, + send: { description, generatedTx }, loadings: { sending: isSending = false }, passwordRequest: { walletID = '', actionType = null, password = '' }, }, @@ -32,15 +31,10 @@ const PasswordRequest = ({ break } sendTransaction({ - id: txID, walletID, - items: outputs.map(output => ({ - address: output.address, - capacity: CKBToShannonFormatter(output.amount, output.unit), - })), + tx: generatedTx, description, password, - fee: priceToFee(price, cycles), })(dispatch, history) break } @@ -62,7 +56,7 @@ const PasswordRequest = ({ break } } - }, [dispatch, walletID, password, actionType, txID, description, outputs, cycles, price, history, isSending]) + }, [dispatch, walletID, password, actionType, description, history, isSending, generatedTx]) const onChange = useCallback( (_e, value?: string) => { diff --git a/packages/neuron-ui/src/components/Send/hooks.ts b/packages/neuron-ui/src/components/Send/hooks.ts index 6263d4a792..1f1bad2e5e 100644 --- a/packages/neuron-ui/src/components/Send/hooks.ts +++ b/packages/neuron-ui/src/components/Send/hooks.ts @@ -1,15 +1,16 @@ import React, { useState, useCallback, useEffect, useMemo } from 'react' import { AppActions, StateDispatch } from 'states/stateProvider/reducer' -import { calculateCycles } from 'services/remote/wallets' +import { generateTx } from 'services/remote/wallets' -import { outputsToTotalAmount, priceToFee } from 'utils/formatters' +import { outputsToTotalAmount, CKBToShannonFormatter } from 'utils/formatters' import { verifyAddress, verifyAmount, verifyAmountRange, verifyTransactionOutputs } from 'utils/validators' -import { ErrorCode } from 'utils/const' -import { MAX_DECIMAL_DIGITS } from '../../utils/const' +import { ErrorCode, MAX_DECIMAL_DIGITS } from 'utils/const' +import calculateFee from 'utils/calculateFee' + import { TransactionOutput } from '.' -let cyclesTimer: ReturnType +let generateTxTimer: ReturnType const useUpdateTransactionOutput = (dispatch: StateDispatch) => useCallback( @@ -49,49 +50,47 @@ const useRemoveTransactionOutput = (dispatch: StateDispatch) => const useOnTransactionChange = ( walletID: string, items: TransactionOutput[], + price: string, dispatch: StateDispatch, setIsTransactionValid: Function, setTotalAmount: Function ) => { useEffect(() => { - clearTimeout(cyclesTimer) - cyclesTimer = setTimeout(() => { + clearTimeout(generateTxTimer) + generateTxTimer = setTimeout(() => { + dispatch({ + type: AppActions.UpdateGeneratedTx, + payload: null, + }) if (verifyTransactionOutputs(items)) { setIsTransactionValid(true) const totalAmount = outputsToTotalAmount(items) setTotalAmount(totalAmount) - calculateCycles({ + const realParams = { walletID, - capacities: totalAmount, - }) - .then(response => { - if (response.status) { - if (Number.isNaN(+response.result)) { - throw new Error('Invalid Cycles') - } + items: items.map(item => ({ + address: item.address, + capacity: CKBToShannonFormatter(item.amount, item.unit), + })), + feeRate: price, + } + generateTx(realParams) + .then((res: any) => { + if (res.status === 1) { dispatch({ - type: AppActions.UpdateSendCycles, - payload: response.result, + type: AppActions.UpdateGeneratedTx, + payload: res.result, }) - } else { - throw new Error('Cycles Not Calculated') } }) - .catch(() => { - dispatch({ - type: AppActions.UpdateSendCycles, - payload: '0', - }) + .catch((err: Error) => { + console.error(err) }) } else { setIsTransactionValid(false) - dispatch({ - type: AppActions.UpdateSendCycles, - payload: '0', - }) } }, 300) - }, [walletID, items, dispatch, setIsTransactionValid, setTotalAmount]) + }, [walletID, items, price, dispatch, setIsTransactionValid, setTotalAmount]) } const useOnSubmit = (items: TransactionOutput[], dispatch: StateDispatch) => @@ -170,12 +169,12 @@ const useClear = (dispatch: StateDispatch) => useCallback(() => clear(dispatch), export const useInitialize = ( items: TransactionOutput[], - price: string, - cycles: string, + generatedTx: any | null, dispatch: React.Dispatch, t: any ) => { - const fee = useMemo(() => priceToFee(price, cycles), [price, cycles]) // in shannon + const fee = useMemo(() => calculateFee(generatedTx), [generatedTx]) + const [isTransactionValid, setIsTransactionValid] = useState(false) const [totalAmount, setTotalAmount] = useState('0') diff --git a/packages/neuron-ui/src/components/Send/index.tsx b/packages/neuron-ui/src/components/Send/index.tsx index f6a23e66b5..6784299e9a 100644 --- a/packages/neuron-ui/src/components/Send/index.tsx +++ b/packages/neuron-ui/src/components/Send/index.tsx @@ -57,8 +57,8 @@ const Send = ({ onGetAddressErrorMessage, onGetAmountErrorMessage, onClear, - } = useInitialize(send.outputs, send.price, send.cycles, dispatch, t) - useOnTransactionChange(walletID, send.outputs, dispatch, setIsTransactionValid, setTotalAmount) + } = useInitialize(send.outputs, send.generatedTx, dispatch, t) + useOnTransactionChange(walletID, send.outputs, send.price, dispatch, setIsTransactionValid, setTotalAmount) const leftStackWidth = '70%' const labelWidth = '140px' @@ -211,12 +211,7 @@ const Send = ({ - + diff --git a/packages/neuron-ui/src/components/Transaction/index.tsx b/packages/neuron-ui/src/components/Transaction/index.tsx index 4d07dcbdf8..54b83c0b54 100644 --- a/packages/neuron-ui/src/components/Transaction/index.tsx +++ b/packages/neuron-ui/src/components/Transaction/index.tsx @@ -210,8 +210,15 @@ const Transaction = () => { const currentWallet = currentWalletCache.load() if (currentWallet) { const hash = window.location.href.split('/').pop() + if (!hash) { + showErrorMessage( + t(`messages.error`), + t(`messages.codes.${ErrorCode.FieldNotFound}`, { fieldName: 'transaction hash' }) + ) + return + } getTransaction({ hash, walletID: currentWallet.id }) - .then(res => { + .then((res: any) => { if (res.status) { setTransaction(res.result) } else { diff --git a/packages/neuron-ui/src/components/TransactionFeePanel/index.tsx b/packages/neuron-ui/src/components/TransactionFeePanel/index.tsx index 948b395ce2..a37d689ad8 100644 --- a/packages/neuron-ui/src/components/TransactionFeePanel/index.tsx +++ b/packages/neuron-ui/src/components/TransactionFeePanel/index.tsx @@ -4,30 +4,24 @@ import { useTranslation } from 'react-i18next' interface TransactionFee { fee: string - cycles: string price: string onPriceChange: any } const calculateSpeed = (price: number) => { - if (price >= 160) { - return '180' + if (price >= 16000) { + return '18000' } - if (price >= 40) { - return '60' + if (price >= 4000) { + return '6000' } - if (price >= 20) { - return '30' + if (price >= 2000) { + return '3000' } return '0' } -const TransactionFee: React.FunctionComponent = ({ - cycles, - price, - fee, - onPriceChange, -}: TransactionFee) => { +const TransactionFee: React.FunctionComponent = ({ price, fee, onPriceChange }: TransactionFee) => { const [t] = useTranslation() const [showDetail, setShowDetail] = useState(false) const leftStackWidth = '70%' @@ -48,7 +42,18 @@ const TransactionFee: React.FunctionComponent = ({ - + {actionSpacer} @@ -79,7 +84,7 @@ const TransactionFee: React.FunctionComponent = ({ - + {actionSpacer} @@ -93,10 +98,10 @@ const TransactionFee: React.FunctionComponent = ({ dropdownWidth={140} selectedKey={selectedSpeed} options={[ - { key: '180', text: 'immediately' }, - { key: '60', text: '~ 30s' }, - { key: '30', text: '~ 1min' }, - { key: '0', text: '~ 3min' }, + { key: '18000', text: 'immediately' }, + { key: '6000', text: '~ 10 blocks' }, + { key: '3000', text: '~ 100 blocks' }, + { key: '0', text: '~ 500 blocks' }, ]} onRenderCaretDown={() => { return @@ -110,15 +115,6 @@ const TransactionFee: React.FunctionComponent = ({ /> - - - - - - - {cycles} - - ) diff --git a/packages/neuron-ui/src/components/TransactionList/index.tsx b/packages/neuron-ui/src/components/TransactionList/index.tsx index b3c34caf5c..174df1fbf0 100644 --- a/packages/neuron-ui/src/components/TransactionList/index.tsx +++ b/packages/neuron-ui/src/components/TransactionList/index.tsx @@ -1,17 +1,19 @@ import React, { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { - Stack, - Text, ShimmeredDetailsList, TextField, IconButton, IColumn, IGroup, CheckboxVisibility, + CollapseAllVisibility, getTheme, } from 'office-ui-fabric-react' +import GroupHeader from 'components/CustomRows/GroupHeader' +import HistoryRow from 'components/CustomRows/HistoryRow' + import { StateDispatch } from 'states/stateProvider/reducer' import { contextMenu, showTransactionDetails } from 'services/remote' @@ -22,7 +24,6 @@ import { uniformTimeFormatter, localNumberFormatter, } from 'utils/formatters' -import { onRenderRow } from 'utils/fabricUIRender' import { CONFIRMATION_THRESHOLD } from 'utils/const' const theme = getTheme() @@ -32,24 +33,6 @@ interface FormatTransaction extends State.Transaction { date: string } -const onRenderHeader = ({ group }: any) => { - const { name } = group - return ( - - {name} - - ) -} - const TransactionList = ({ isLoading = false, items = [], @@ -185,6 +168,10 @@ const TransactionList = ({ borderless readOnly={!isSelected} styles={{ + root: { + flex: '1', + paddingRight: '15px', + }, fieldGroup: { backgroundColor: isSelected ? '#fff' : 'transparent', borderColor: 'transparent', @@ -268,9 +255,10 @@ const TransactionList = ({ enableShimmer={isLoading} columns={transactionColumns} items={txs} - groups={groups.filter(group => group.count !== 0)} + groups={groups} groupProps={{ - onRenderHeader, + collapseAllVisibility: CollapseAllVisibility.hidden, + onRenderHeader: GroupHeader, }} checkboxVisibility={CheckboxVisibility.hidden} onItemInvoked={item => { @@ -282,7 +270,7 @@ const TransactionList = ({ } }} className="listWithDesc" - onRenderRow={onRenderRow} + onRenderRow={HistoryRow} /> ) } diff --git a/packages/neuron-ui/src/containers/Footer/index.tsx b/packages/neuron-ui/src/containers/Footer/index.tsx index 3cece43d72..1ac36af58f 100644 --- a/packages/neuron-ui/src/containers/Footer/index.tsx +++ b/packages/neuron-ui/src/containers/Footer/index.tsx @@ -34,7 +34,7 @@ export const SyncStatus = ({ const percentage = +syncedBlockNumber / +tipBlockNumber return ( -
+
{+syncedBlockNumber + bufferBlockNumber < +tipBlockNumber ? ( <> {t('sync.syncing')} diff --git a/packages/neuron-ui/src/containers/Main/hooks.ts b/packages/neuron-ui/src/containers/Main/hooks.ts index 1dae29b28e..f696bea215 100644 --- a/packages/neuron-ui/src/containers/Main/hooks.ts +++ b/packages/neuron-ui/src/containers/Main/hooks.ts @@ -21,7 +21,7 @@ import { Command as CommandSubject, } from 'services/subjects' import { ckbCore, getTipBlockNumber, getBlockchainInfo } from 'services/chain' -import { ConnectionStatus } from 'utils/const' +import { ConnectionStatus, ErrorCode } from 'utils/const' import { networks as networksCache, currentNetworkID as currentNetworkIDCache, @@ -40,6 +40,10 @@ export const useSyncChainData = ({ chainURL, dispatch }: { chainURL: string; dis type: AppActions.UpdateTipBlockNumber, payload: BigInt(tipBlockNumber).toString(), }) + dispatch({ + type: AppActions.ClearNotificationsOfCode, + payload: ErrorCode.NodeDisconnected, + }) }) .catch((err: Error) => { if (process.env.NODE_ENV === 'development') { diff --git a/packages/neuron-ui/src/services/remote/apiMethodWrapper.ts b/packages/neuron-ui/src/services/remote/apiMethodWrapper.ts index ddf5a26940..d21f8db749 100644 --- a/packages/neuron-ui/src/services/remote/apiMethodWrapper.ts +++ b/packages/neuron-ui/src/services/remote/apiMethodWrapper.ts @@ -1,4 +1,3 @@ -// TODO: use error code interface SuccessFromController { status: 1 result: any @@ -12,6 +11,7 @@ interface FailureFromController { meta?: { [key: string]: string } } } + export type ControllerResponse = SuccessFromController | FailureFromController export const RemoteNotLoadError = { @@ -21,17 +21,9 @@ export const RemoteNotLoadError = { }, } -export const apiMethodWrapper = ( - callControllerMethod: ( - controller: any - ) => ( - params: any - ) => Promise<{ - status: any - result: any - message: { code?: number; content?: string; meta?: { [key: string]: string } } - }> -) => async (realParams?: any): Promise => { +export const apiMethodWrapper = ( + callControllerMethod: (controller: any) => (params: T) => Promise +) => async (realParams: T): Promise => { if (!window.remote) { return RemoteNotLoadError } @@ -44,7 +36,16 @@ export const apiMethodWrapper = ( }, } } - const res = await callControllerMethod(controller)(realParams) + + const res: SuccessFromController | FailureFromController = await callControllerMethod(controller)(realParams) + .then(stringifiedRes => (stringifiedRes ? JSON.parse(stringifiedRes) : stringifiedRes)) + .catch(() => ({ + status: 0, + message: { + content: 'Invalid response format', + }, + })) + if (process.env.NODE_ENV === 'development' && window.localStorage.getItem('log-response')) { console.group('api controller') console.info(JSON.stringify(res, null, 2)) diff --git a/packages/neuron-ui/src/services/remote/app.ts b/packages/neuron-ui/src/services/remote/app.ts index 5a7856d64a..6b31ba7645 100644 --- a/packages/neuron-ui/src/services/remote/app.ts +++ b/packages/neuron-ui/src/services/remote/app.ts @@ -1,11 +1,11 @@ import { apiMethodWrapper } from './apiMethodWrapper' -export const getNeuronWalletState = apiMethodWrapper(controller => () => controller.loadInitData()) +export const getNeuronWalletState = apiMethodWrapper(controller => () => controller.loadInitData()) -export const handleViewError = apiMethodWrapper(controller => (errorMessage: string) => +export const handleViewError = apiMethodWrapper(controller => errorMessage => controller.handleViewError(errorMessage) ) -export const contextMenu = apiMethodWrapper(controller => (params: { type: string; id: string }) => +export const contextMenu = apiMethodWrapper<{ type: string; id: string }>(controller => params => controller.contextMenu(params) ) diff --git a/packages/neuron-ui/src/services/remote/networks.ts b/packages/neuron-ui/src/services/remote/networks.ts index 9ae86fe729..f45ef7382f 100644 --- a/packages/neuron-ui/src/services/remote/networks.ts +++ b/packages/neuron-ui/src/services/remote/networks.ts @@ -1,22 +1,22 @@ import { apiMethodWrapper } from './apiMethodWrapper' -export const setCurrentNetowrk = apiMethodWrapper((api: any) => (networkID: string) => { +export const setCurrentNetowrk = apiMethodWrapper(api => networkID => { return api.setCurrentNetowrk(networkID) }) -export const createNetwork = apiMethodWrapper(api => (params: Controller.CreateNetworkParams) => { +export const createNetwork = apiMethodWrapper(api => params => { return api.createNetwork(params) }) -export const updateNetwork = apiMethodWrapper(api => ({ networkID, options }: Controller.UpdateNetworkParams) => { +export const updateNetwork = apiMethodWrapper(api => ({ networkID, options }) => { return api.updateNetwork(networkID, options) }) -export const getAllNetworks = apiMethodWrapper(api => () => { +export const getAllNetworks = apiMethodWrapper(api => () => { return api.getAllNetworks() }) -export const getCurrentNetworkID = apiMethodWrapper(api => () => { +export const getCurrentNetworkID = apiMethodWrapper(api => () => { return api.getCurrentNetworkID() }) diff --git a/packages/neuron-ui/src/services/remote/wallets.ts b/packages/neuron-ui/src/services/remote/wallets.ts index 594f7d8870..2ab31713d1 100644 --- a/packages/neuron-ui/src/services/remote/wallets.ts +++ b/packages/neuron-ui/src/services/remote/wallets.ts @@ -2,9 +2,9 @@ import { apiMethodWrapper } from './apiMethodWrapper' export const updateWallet = apiMethodWrapper(api => (params: Controller.UpdateWalletParams) => api.updateWallet(params)) -export const getCurrentWallet = apiMethodWrapper(api => () => api.getCurrentWallet()) +export const getCurrentWallet = apiMethodWrapper(api => () => api.getCurrentWallet()) -export const getWalletList = apiMethodWrapper(api => () => api.getAllWallets()) +export const getWalletList = apiMethodWrapper(api => () => api.getAllWallets()) export const createWallet = apiMethodWrapper(api => (params: Controller.CreateWalletParams) => api.createWallet(params)) @@ -24,7 +24,11 @@ export const setCurrentWallet = apiMethodWrapper(api => (id: Controller.SetCurre api.setCurrentWallet(id) ) -export const sendCapacity = apiMethodWrapper(api => (params: Controller.SendTransaction) => api.sendCapacity(params)) +export const generateTx = apiMethodWrapper(api => (params: Controller.GenerateTransactionParams) => + api.generateTx(params) +) + +export const sendTx = apiMethodWrapper(api => (params: Controller.SendTransactionParams) => api.sendTx(params)) export const getAddressesByWalletID = apiMethodWrapper(api => (walletID: Controller.GetAddressesByWalletIDParams) => api.getAddressesByWalletID(walletID) @@ -34,8 +38,6 @@ export const updateAddressDescription = apiMethodWrapper(api => (params: Control api.updateAddressDescription(params) ) -export const calculateCycles = apiMethodWrapper(api => (params: Controller.ComputeCycles) => api.computeCycles(params)) - export default { updateWallet, getWalletList, @@ -45,8 +47,8 @@ export default { deleteWallet, backupWallet, getCurrentWallet, - sendCapacity, - calculateCycles, + generateTx, + sendTx, getAddressesByWalletID, updateAddressDescription, } diff --git a/packages/neuron-ui/src/states/initStates/app.ts b/packages/neuron-ui/src/states/initStates/app.ts index c6eca66d88..c4532a26db 100644 --- a/packages/neuron-ui/src/states/initStates/app.ts +++ b/packages/neuron-ui/src/states/initStates/app.ts @@ -14,9 +14,9 @@ const appState: State.App = { unit: CapacityUnit.CKB, }, ], - price: '0', - cycles: '0', + price: '1000', description: '', + generatedTx: '', }, passwordRequest: { actionType: null, diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts index 2efad915fa..1111c41d9d 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts @@ -76,9 +76,6 @@ export const addPopup = (text: string) => (dispatch: StateDispatch) => { } export const addNotification = (message: State.Message) => (dispatch: StateDispatch) => { - if (message && message.code === ErrorCode.NodeDisconnected) { - return - } dispatch({ type: AppActions.AddNotification, payload: message, diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts index 9aaf4757d9..4e59511fbf 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts @@ -7,7 +7,7 @@ import { getCurrentWallet, updateWallet, setCurrentWallet as setRemoteCurrentWallet, - sendCapacity, + sendTx, getAddressesByWalletID, updateAddressDescription as updateRemoteAddressDescription, deleteWallet as deleteRemoteWallet, @@ -141,7 +141,10 @@ export const setCurrentWallet = (id: string) => (dispatch: StateDispatch) => { }) } -export const sendTransaction = (params: Controller.SendTransaction) => (dispatch: StateDispatch, history: any) => { +export const sendTransaction = (params: Controller.SendTransactionParams) => ( + dispatch: StateDispatch, + history: any +) => { dispatch({ type: AppActions.UpdateLoadings, payload: { @@ -149,7 +152,7 @@ export const sendTransaction = (params: Controller.SendTransaction) => (dispatch }, }) setTimeout(() => { - sendCapacity(params) + sendTx(params) .then(res => { if (res.status === 1) { dispatch({ diff --git a/packages/neuron-ui/src/states/stateProvider/reducer.ts b/packages/neuron-ui/src/states/stateProvider/reducer.ts index a040a2eeb7..5f20b4d629 100644 --- a/packages/neuron-ui/src/states/stateProvider/reducer.ts +++ b/packages/neuron-ui/src/states/stateProvider/reducer.ts @@ -28,8 +28,8 @@ export enum AppActions { RemoveSendOutput = 'removeSendOutput', UpdateSendOutput = 'updateSendOutput', UpdateSendPrice = 'updateSendPrice', - UpdateSendCycles = 'updateSendCycles', UpdateSendDescription = 'updateSendDescription', + UpdateGeneratedTx = 'updateGeneratedTx', ClearSendState = 'clearSendState', UpdateMessage = 'updateMessage', AddNotification = 'addNotification', @@ -357,9 +357,9 @@ export const reducer = ( }, } } - case AppActions.UpdateSendCycles: { + case AppActions.UpdateSendDescription: { /** - * payload: new cycles + * payload: new description */ return { ...state, @@ -367,22 +367,19 @@ export const reducer = ( ...app, send: { ...app.send, - cycles: payload, + description: payload, }, }, } } - case AppActions.UpdateSendDescription: { - /** - * payload: new description - */ + case AppActions.UpdateGeneratedTx: { return { ...state, app: { ...app, send: { ...app.send, - description: payload, + generatedTx: payload || null, }, }, } diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts index ac4fd95909..6fbf936369 100644 --- a/packages/neuron-ui/src/types/App/index.d.ts +++ b/packages/neuron-ui/src/types/App/index.d.ts @@ -63,8 +63,8 @@ declare namespace State { txID: string outputs: Output[] price: string - cycles: string description: string + generatedTx: any | null } interface Popup { diff --git a/packages/neuron-ui/src/types/Controller/index.d.ts b/packages/neuron-ui/src/types/Controller/index.d.ts index 5f1f5ae489..b7d56a6a98 100644 --- a/packages/neuron-ui/src/types/Controller/index.d.ts +++ b/packages/neuron-ui/src/types/Controller/index.d.ts @@ -33,16 +33,21 @@ declare namespace Controller { } type SetCurrentWalletParams = string - interface SendTransaction { - id: string + + interface SendTransactionParams { + walletID: string + tx: string + password: string + description?: string + } + + interface GenerateTransactionParams { walletID: string items: { address: string capacity: string }[] - password: string - fee: string - description: string + feeRate: string } interface ComputeCycles { diff --git a/packages/neuron-ui/src/utils/calculateFee.ts b/packages/neuron-ui/src/utils/calculateFee.ts new file mode 100644 index 0000000000..e36fa958e5 --- /dev/null +++ b/packages/neuron-ui/src/utils/calculateFee.ts @@ -0,0 +1,15 @@ +export default (tx: any) => { + if (!tx) { + return '0' + } + const inputCapacities = tx.inputs.reduce( + (result: bigint, input: { capacity: string }) => result + BigInt(input.capacity), + BigInt(0) + ) + const outputCapacities = tx.outputs.reduce( + (result: bigint, output: { capacity: string }) => result + BigInt(output.capacity), + BigInt(0) + ) + + return (inputCapacities - outputCapacities).toString() +} diff --git a/packages/neuron-ui/src/utils/formatters.ts b/packages/neuron-ui/src/utils/formatters.ts index 5b11497609..a39bbc2835 100644 --- a/packages/neuron-ui/src/utils/formatters.ts +++ b/packages/neuron-ui/src/utils/formatters.ts @@ -145,14 +145,6 @@ export const uniformTimeFormatter = (time: string | number | Date) => { return timeFormatter.format(+time).replace(/\//g, '-') } -export const priceToFee = (price: string, cycles: string) => { - if (Number.isNaN(+price)) { - console.warn(`Price is not a valid number`) - return `0` - } - return (BigInt(price) * BigInt(cycles)).toString() -} - export const addressesToBalance = (addresses: State.Address[] = []) => { return addresses .reduce((total, addr) => { @@ -191,7 +183,6 @@ export default { shannonToCKBFormatter, localNumberFormatter, uniformTimeFormatter, - priceToFee, addressesToBalance, outputsToTotalAmount, failureResToNotification, diff --git a/packages/neuron-wallet/.eslintrc.js b/packages/neuron-wallet/.eslintrc.js index 8eae8dced0..0e675fe66c 100644 --- a/packages/neuron-wallet/.eslintrc.js +++ b/packages/neuron-wallet/.eslintrc.js @@ -14,13 +14,14 @@ module.exports = { "@typescript-eslint/no-unused-vars": ["error", { "vars": "local", "args": "after-used", - "ignoreRestSiblings": false + "ignoreRestSiblings": false, + "argsIgnorePattern": "^_" }], "curly": [2, "all"], "implicit-arrow-linebreak": "off", "arrow-parens": [2, "as-needed"], "max-len": [2, { - "code": 120, + "code": 140, "ignoreComments": true, "ignoreTrailingComments": true, "ignoreUrls": true, diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json index 78109fab77..1c2ee19132 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.23.0", + "version": "0.23.1", "private": true, "author": { "name": "Nervos Core Dev", @@ -47,7 +47,7 @@ "rxjs": "6.5.3", "sha3": "2.0.7", "sqlite3": "4.1.0", - "typeorm": "0.2.19", + "typeorm": "0.2.20", "uuid": "3.3.3" }, "devDependencies": { @@ -64,7 +64,7 @@ "electron-devtools-installer": "2.2.4", "electron-notarize": "0.1.1", "lint-staged": "9.2.5", - "neuron-ui": "0.23.0", + "neuron-ui": "0.23.1", "rimraf": "3.0.0", "spectron": "8.0.0", "ts-transformer-imports": "0.4.3", diff --git a/packages/neuron-wallet/src/controllers/api.ts b/packages/neuron-wallet/src/controllers/api.ts index 9638e00c3f..8187715338 100644 --- a/packages/neuron-wallet/src/controllers/api.ts +++ b/packages/neuron-wallet/src/controllers/api.ts @@ -10,14 +10,14 @@ import { SyncInfoController, SkipDataAndTypeController, NetworksController - } from 'controllers' +} from 'controllers' import { NetworkType, NetworkID, Network } from 'types/network' import NetworksService from 'services/networks' import WalletsService from 'services/wallets' import SkipDataAndType from 'services/settings/skip-data-and-type' import { ConnectionStatusSubject } from 'models/subjects/node' import { SystemScriptSubject } from 'models/subjects/system-script' -import { CatchControllerError } from 'decorators/errors' +import { MapApiResponse } from 'decorators' import { ResponseCode } from 'utils/const' import { TransactionWithoutHash } from 'types/cell-types' @@ -27,7 +27,9 @@ import { TransactionWithoutHash } from 'types/cell-types' */ export default class ApiController { // App - public static loadInitData = async () => { + + @MapApiResponse + public static async loadInitData() { const walletsService = WalletsService.getInstance() const networksService = NetworksService.getInstance() const [ @@ -114,69 +116,71 @@ export default class ApiController { return { status: ResponseCode.Success, result: initState } } - public static handleViewError = (error: string) => { + @MapApiResponse + public static handleViewError(error: string) { if (env.isDevMode) { console.error(error) } } + @MapApiResponse public static async contextMenu(params: { type: string; id: string }) { return popContextMenu(params) } // Wallets - @CatchControllerError + @MapApiResponse public static async getAllWallets() { return WalletsController.getAll() } - @CatchControllerError + @MapApiResponse public static async getCurrentWallet() { return WalletsController.getCurrent() } - @CatchControllerError - public static async importMnemonic(params: { name: string, password: string, mnemonic: string }) { + @MapApiResponse + public static async importMnemonic(params: { name: string; password: string; mnemonic: string }) { return WalletsController.importMnemonic(params) } - @CatchControllerError - public static async importKeystore(params: { name: string, password: string, keystorePath: string }) { + @MapApiResponse + public static async importKeystore(params: { name: string; password: string; keystorePath: string }) { return WalletsController.importKeystore(params) } - @CatchControllerError - public static async createWallet(params: { name: string, password: string, mnemonic: string }) { + @MapApiResponse + public static async createWallet(params: { name: string; password: string; mnemonic: string }) { return WalletsController.create(params) } - @CatchControllerError - public static async updateWallet(params: { id: string, password: string, name: string, newPassword?: string }) { - WalletsController.update(params) + @MapApiResponse + public static async updateWallet(params: { id: string; password: string; name: string; newPassword?: string }) { + return WalletsController.update(params) } - @CatchControllerError + @MapApiResponse public static async deleteWallet({ id = '', password = '' }) { return WalletsController.delete({ id, password }) } - @CatchControllerError + @MapApiResponse public static async backupWallet({ id = '', password = '' }) { return WalletsController.backup({ id, password }) } - @CatchControllerError + @MapApiResponse public static async setCurrentWallet(id: string) { return WalletsController.activate(id) } - @CatchControllerError + @MapApiResponse public static async getAddressesByWalletID(id: string) { return WalletsController.getAllAddresses(id) } - @CatchControllerError + @MapApiResponse public static async sendCapacity(params: { id: string walletID: string @@ -191,20 +195,18 @@ export default class ApiController { return WalletsController.sendCapacity(params) } - @CatchControllerError + @MapApiResponse public static async sendTx(params: { - id: string walletID: string - tx: TransactionWithoutHash, + tx: TransactionWithoutHash password: string description?: string }) { return WalletsController.sendTx(params) } - @CatchControllerError + @MapApiResponse public static async generateTx(params: { - id: string walletID: string items: { address: string @@ -216,20 +218,12 @@ export default class ApiController { return WalletsController.generateTx(params) } - @CatchControllerError - public static async calculateFee(params: { - id: string - tx: TransactionWithoutHash - }) { - return WalletsController.calculateFee(params) - } - - @CatchControllerError + @MapApiResponse public static async computeCycles(params: { walletID: string; capacities: string }) { return WalletsController.computeCycles(params) } - @CatchControllerError + @MapApiResponse public static async updateAddressDescription(params: { walletID: string address: string @@ -240,46 +234,46 @@ export default class ApiController { // Networks - @CatchControllerError + @MapApiResponse public static async getAllNetworks() { return NetworksController.getAll() } - @CatchControllerError + @MapApiResponse public static async createNetwork({ name, remote, type = NetworkType.Normal, chain = 'ckb' }: Network) { return NetworksController.create({ name, remote, type, chain }) } - @CatchControllerError + @MapApiResponse public static async updateNetwork(id: NetworkID, options: Partial) { return NetworksController.update(id, options) } - @CatchControllerError + @MapApiResponse public static async getCurrentNetworkID() { return NetworksController.currentID() } - @CatchControllerError + @MapApiResponse public static async setCurrentNetowrk(id: NetworkID) { return NetworksController.activate(id) } // Transactions - @CatchControllerError + @MapApiResponse public static async getTransactionList( params: Controller.Params.TransactionsByKeywords, ) { return TransactionsController.getAllByKeywords(params) } - @CatchControllerError + @MapApiResponse public static async getTransaction(walletID: string, hash: string) { return TransactionsController.get(walletID, hash) } - @CatchControllerError + @MapApiResponse public static async updateTransactionDescription(params: { hash: string; description: string }) { return TransactionsController.updateDescription(params) } @@ -290,7 +284,7 @@ export default class ApiController { // Misc - @CatchControllerError + @MapApiResponse public static async updateSkipDataAndType(skip: boolean) { return SkipDataAndTypeController.update(skip) } diff --git a/packages/neuron-wallet/src/controllers/networks.ts b/packages/neuron-wallet/src/controllers/networks.ts index 4e0c718de0..a7bc506784 100644 --- a/packages/neuron-wallet/src/controllers/networks.ts +++ b/packages/neuron-wallet/src/controllers/networks.ts @@ -1,13 +1,11 @@ import { NetworkType, NetworkID, Network } from 'types/network' import NetworksService from 'services/networks' -import { CatchControllerError } from 'decorators' import { ResponseCode } from 'utils/const' import { IsRequired, InvalidName, NetworkNotFound, CurrentNetworkNotSet } from 'exceptions' const networksService = NetworksService.getInstance() export default class NetworksController { - @CatchControllerError public static async getAll() { const networks = await networksService.getAll() return { @@ -16,7 +14,6 @@ export default class NetworksController { } } - @CatchControllerError public static async get(id: NetworkID) { if (typeof id === 'undefined') { throw new IsRequired('ID') @@ -33,7 +30,6 @@ export default class NetworksController { } } - @CatchControllerError public static async create({ name, remote, type = NetworkType.Normal }: Network) { if (!name || !remote) { throw new IsRequired('Name and address') @@ -49,7 +45,6 @@ export default class NetworksController { } } - @CatchControllerError public static async update(id: NetworkID, options: Partial) { if (options.name && options.name === 'error') { throw new InvalidName('Network') @@ -62,7 +57,6 @@ export default class NetworksController { } } - @CatchControllerError public static async delete(id: NetworkID) { await networksService.delete(id) @@ -72,7 +66,6 @@ export default class NetworksController { } } - @CatchControllerError public static async currentID() { const currentID = await networksService.getCurrentID() if (currentID) { @@ -84,7 +77,6 @@ export default class NetworksController { throw new CurrentNetworkNotSet() } - @CatchControllerError public static async activate(id: NetworkID) { await networksService.activate(id) return { @@ -93,7 +85,6 @@ export default class NetworksController { } } - @CatchControllerError public static async clear() { await networksService.clear() return { diff --git a/packages/neuron-wallet/src/controllers/skip-data-and-type.ts b/packages/neuron-wallet/src/controllers/skip-data-and-type.ts index 5357dc450d..9b88d1d19f 100644 --- a/packages/neuron-wallet/src/controllers/skip-data-and-type.ts +++ b/packages/neuron-wallet/src/controllers/skip-data-and-type.ts @@ -1,9 +1,7 @@ -import { CatchControllerError } from 'decorators/errors' import { ResponseCode } from 'utils/const' import SkipDataAndType from 'services/settings/skip-data-and-type' export default class SkipDataAndTypeController { - @CatchControllerError public static async update(skip: boolean): Promise> { SkipDataAndType.getInstance().update(skip) @@ -13,7 +11,6 @@ export default class SkipDataAndTypeController { } } - @CatchControllerError public static async get(): Promise> { const skip = SkipDataAndType.getInstance().get() diff --git a/packages/neuron-wallet/src/controllers/sync-info.ts b/packages/neuron-wallet/src/controllers/sync-info.ts index fc94e804b2..786380441e 100644 --- a/packages/neuron-wallet/src/controllers/sync-info.ts +++ b/packages/neuron-wallet/src/controllers/sync-info.ts @@ -1,9 +1,7 @@ -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() diff --git a/packages/neuron-wallet/src/controllers/transactions.ts b/packages/neuron-wallet/src/controllers/transactions.ts index 3781bc94fb..b2e1a0a18c 100644 --- a/packages/neuron-wallet/src/controllers/transactions.ts +++ b/packages/neuron-wallet/src/controllers/transactions.ts @@ -4,7 +4,6 @@ import { TransactionsService, PaginationResult, TransactionsByLockHashesParam } import AddressesService from 'services/addresses' import WalletsService from 'services/wallets' -import { CatchControllerError } from 'decorators' import { ResponseCode } from 'utils/const' import { TransactionNotFound, CurrentWalletNotSet, ServiceHasNoResponse } from 'exceptions' import LockUtils from 'models/lock-utils' @@ -12,7 +11,6 @@ import LockUtils from 'models/lock-utils' const CELL_COUNT_THRESHOLD = 10 export default class TransactionsController { - @CatchControllerError public static async getAll( params: TransactionsByLockHashesParam, ): Promise>> { @@ -28,7 +26,6 @@ export default class TransactionsController { } } - @CatchControllerError public static async getAllByKeywords( params: Controller.Params.TransactionsByKeywords, ): Promise & Controller.Params.TransactionsByKeywords>> { @@ -52,7 +49,6 @@ export default class TransactionsController { } } - @CatchControllerError public static async getAllByAddresses( params: Controller.Params.TransactionsByAddresses, ): Promise & Controller.Params.TransactionsByAddresses>> { @@ -82,7 +78,6 @@ export default class TransactionsController { } } - @CatchControllerError public static async get( walletID: string, hash: string, @@ -132,7 +127,6 @@ export default class TransactionsController { } } - @CatchControllerError public static async updateDescription({ hash, description }: { hash: string; description: string }) { await TransactionsService.updateDescription(hash, description) diff --git a/packages/neuron-wallet/src/controllers/wallets.ts b/packages/neuron-wallet/src/controllers/wallets.ts index eacaa5d865..f194b7ec31 100644 --- a/packages/neuron-wallet/src/controllers/wallets.ts +++ b/packages/neuron-wallet/src/controllers/wallets.ts @@ -5,7 +5,6 @@ import Keystore from 'models/keys/keystore' import Keychain from 'models/keys/keychain' import { validateMnemonic, mnemonicToSeedSync } from 'models/keys/mnemonic' import { AccountExtendedPublicKey, ExtendedPrivateKey } from 'models/keys/key' -import { CatchControllerError } from 'decorators' import { ResponseCode } from 'utils/const' import { CurrentWalletNotSet, @@ -20,11 +19,9 @@ import { import i18n from 'utils/i18n' import AddressService from 'services/addresses' import WalletCreatedSubject from 'models/subjects/wallet-created-subject' -import logger from 'utils/logger' -import { TransactionWithoutHash } from 'types/cell-types'; +import { TransactionWithoutHash } from 'types/cell-types' export default class WalletsController { - @CatchControllerError public static async getAll(): Promise[]>> { const walletsService = WalletsService.getInstance() const wallets = walletsService.getAll() @@ -53,7 +50,6 @@ export default class WalletsController { } } - @CatchControllerError public static async get(id: string): Promise> { const walletsService = WalletsService.getInstance() if (typeof id === 'undefined') { @@ -70,7 +66,6 @@ export default class WalletsController { } } - @CatchControllerError public static async importMnemonic({ name, password, @@ -92,7 +87,6 @@ export default class WalletsController { return result } - @CatchControllerError public static async create({ name, password, @@ -165,7 +159,6 @@ export default class WalletsController { } } - @CatchControllerError public static async importKeystore({ name, password, @@ -193,7 +186,7 @@ export default class WalletsController { const accountKeychain = masterKeychain.derivePath(AccountExtendedPublicKey.ckbAccountPath) const accountExtendedPublicKey = new AccountExtendedPublicKey( accountKeychain.publicKey.toString('hex'), - accountKeychain.chainCode.toString('hex') + accountKeychain.chainCode.toString('hex'), ) const walletsService = WalletsService.getInstance() @@ -214,7 +207,7 @@ export default class WalletsController { } // TODO: update addresses? - @CatchControllerError + public static async update({ id, name, @@ -249,7 +242,6 @@ export default class WalletsController { } } - @CatchControllerError public static async delete({ id = '', password = '', @@ -268,7 +260,6 @@ export default class WalletsController { } } - @CatchControllerError public static async backup({ id = '', password = '', @@ -291,12 +282,10 @@ export default class WalletsController { result: true, }) } - } - ) + }) }) } - @CatchControllerError public static async getCurrent() { const currentWallet = WalletsService.getInstance().getCurrent() || null return { @@ -305,7 +294,6 @@ export default class WalletsController { } } - @CatchControllerError public static async activate(id: string) { const walletsService = WalletsService.getInstance() walletsService.setCurrent(id) @@ -319,7 +307,6 @@ export default class WalletsController { } } - @CatchControllerError public static async getAllAddresses(id: string) { const addresses = await AddressService.allAddressesByWalletId(id).then(addrs => addrs.map( @@ -348,7 +335,6 @@ export default class WalletsController { } } - @CatchControllerError public static async sendCapacity(params: { id: string walletID: string @@ -368,32 +354,22 @@ export default class WalletsController { if (!params.fee || params.fee === '0') { feeRate = '1000' } - try { - const walletsService = WalletsService.getInstance() - const hash = await walletsService.sendCapacity( - params.walletID, - params.items, - params.password, - params.fee, - feeRate, - params.description - ) - return { - status: ResponseCode.Success, - result: hash, - } - } catch (err) { - logger.error(`sendCapacity:`, err) - return { - status: err.code || ResponseCode.Fail, - message: `Error: "${err.message}"`, - } + const walletsService = WalletsService.getInstance() + const hash = await walletsService.sendCapacity( + params.walletID, + params.items, + params.password, + params.fee, + feeRate, + params.description + ) + return { + status: ResponseCode.Success, + result: hash, } } - @CatchControllerError public static async sendTx(params: { - id: string walletID: string tx: TransactionWithoutHash password: string @@ -402,30 +378,20 @@ export default class WalletsController { if (!params) { throw new IsRequired('Parameters') } - try { - const walletsService = WalletsService.getInstance() - const hash = await walletsService.sendTx( - params.walletID, - params.tx, - params.password, - params.description - ) - return { - status: ResponseCode.Success, - result: hash, - } - } catch (err) { - logger.error(`sendTx:`, err) - return { - status: err.code || ResponseCode.Fail, - message: `Error: "${err.message}"`, - } + const walletsService = WalletsService.getInstance() + const hash = await walletsService.sendTx( + params.walletID, + params.tx, + params.password, + params.description + ) + return { + status: ResponseCode.Success, + result: hash, } } - @CatchControllerError public static async generateTx(params: { - id: string walletID: string items: { address: string @@ -437,72 +403,31 @@ export default class WalletsController { if (!params) { throw new IsRequired('Parameters') } - try { - const walletsService = WalletsService.getInstance() - const tx = await walletsService.generateTx( - params.walletID, - params.items, - params.fee, - params.feeRate, - ) - return { - status: ResponseCode.Success, - result: tx, - } - } catch (err) { - logger.error(`generateTx:`, err) - return { - status: err.code || ResponseCode.Fail, - message: `Error: "${err.message}"`, - } - } - } - - @CatchControllerError - public static async calculateFee(params: { - id: string - tx: TransactionWithoutHash - }) { - if (!params) { - throw new IsRequired('Parameters') - } - try { - const walletsService = WalletsService.getInstance() - const fee = await walletsService.calculateFee(params.tx) - return { - status: ResponseCode.Success, - result: fee, - } - } catch (err) { - logger.error(`calculateFee:`, err) - return { - status: err.code || ResponseCode.Fail, - message: `Error: "${err.message}"`, - } + const walletsService = WalletsService.getInstance() + const tx = await walletsService.generateTx( + params.walletID, + params.items, + params.fee, + params.feeRate, + ) + return { + status: ResponseCode.Success, + result: tx, } } - @CatchControllerError public static async computeCycles(params: { walletID: string; capacities: string }) { if (!params) { throw new IsRequired('Parameters') } - try { - const walletsService = WalletsService.getInstance() - const cycles = await walletsService.computeCycles(params.walletID, params.capacities) - return { - status: ResponseCode.Success, - result: cycles, - } - } catch (err) { - return { - status: ResponseCode.Fail, - message: `Error: "${err.message}"`, - } + const walletsService = WalletsService.getInstance() + const cycles = await walletsService.computeCycles(params.walletID, params.capacities) + return { + status: ResponseCode.Success, + result: cycles, } } - @CatchControllerError public static async updateAddressDescription({ walletID, address, diff --git a/packages/neuron-wallet/src/database/chain/entities/input.ts b/packages/neuron-wallet/src/database/chain/entities/input.ts index b72bb12f30..4c4fb018c4 100644 --- a/packages/neuron-wallet/src/database/chain/entities/input.ts +++ b/packages/neuron-wallet/src/database/chain/entities/input.ts @@ -2,7 +2,6 @@ import { Entity, BaseEntity, Column, ManyToOne, PrimaryGeneratedColumn } from 't import { OutPoint, Input as InputInterface, Script } from 'types/cell-types' import Transaction from './transaction' -/* eslint @typescript-eslint/no-unused-vars: "warn" */ // cellbase input may have same OutPoint @Entity() export default class Input extends BaseEntity { diff --git a/packages/neuron-wallet/src/database/chain/entities/output.ts b/packages/neuron-wallet/src/database/chain/entities/output.ts index 8ec0559ebd..33a39688ff 100644 --- a/packages/neuron-wallet/src/database/chain/entities/output.ts +++ b/packages/neuron-wallet/src/database/chain/entities/output.ts @@ -2,7 +2,6 @@ import { Entity, BaseEntity, Column, PrimaryColumn, ManyToOne } from 'typeorm' import { Script, OutPoint, Cell } from 'types/cell-types' import TransactionEntity from './transaction' -/* eslint @typescript-eslint/no-unused-vars: "warn" */ @Entity() export default class Output extends BaseEntity { @PrimaryColumn({ diff --git a/packages/neuron-wallet/src/database/chain/entities/transaction.ts b/packages/neuron-wallet/src/database/chain/entities/transaction.ts index 3cbcae9063..3ec73f0077 100644 --- a/packages/neuron-wallet/src/database/chain/entities/transaction.ts +++ b/packages/neuron-wallet/src/database/chain/entities/transaction.ts @@ -21,7 +21,6 @@ const txDbChangedSubject = isRenderer ? remote.require('./models/subjects/tx-db-changed-subject').default.getSubject() : TxDbChangedSubject.getSubject() -/* eslint @typescript-eslint/no-unused-vars: "warn" */ @Entity() export default class Transaction extends BaseEntity { @PrimaryColumn({ @@ -125,6 +124,9 @@ export default class Transaction extends BaseEntity { updateCreatedAt() { this.createdAt = Date.now().toString() this.updatedAt = this.createdAt + if (!this.timestamp) { + this.timestamp = this.createdAt + } } @BeforeUpdate() diff --git a/packages/neuron-wallet/src/database/chain/migrations/1572006450765-AddIndices.ts b/packages/neuron-wallet/src/database/chain/migrations/1572006450765-AddIndices.ts new file mode 100644 index 0000000000..7c6b4ba054 --- /dev/null +++ b/packages/neuron-wallet/src/database/chain/migrations/1572006450765-AddIndices.ts @@ -0,0 +1,17 @@ +import {MigrationInterface, QueryRunner, TableIndex} from "typeorm"; + +export class AddIndices1572006450765 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createIndex("input", new TableIndex({ columnNames: ["transactionHash", "lockHash"] })) + await queryRunner.createIndex("output", new TableIndex({ columnNames: ["transactionHash", "lockHash"] })) + await queryRunner.createIndex("transaction", new TableIndex({ columnNames: ["status"] })) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropIndex("input", new TableIndex({ columnNames: ["transactionHash", "lockHash"] })) + await queryRunner.dropIndex("output", new TableIndex({ columnNames: ["transactionHash", "lockHash"] })) + await queryRunner.dropIndex("transaction", new TableIndex({ columnNames: ["status"] })) + } + +} diff --git a/packages/neuron-wallet/src/database/chain/migrations/1572137226866-AddIndexToTxTimestamp.ts b/packages/neuron-wallet/src/database/chain/migrations/1572137226866-AddIndexToTxTimestamp.ts new file mode 100644 index 0000000000..4d9e78704b --- /dev/null +++ b/packages/neuron-wallet/src/database/chain/migrations/1572137226866-AddIndexToTxTimestamp.ts @@ -0,0 +1,13 @@ +import {MigrationInterface, QueryRunner, TableIndex} from "typeorm"; + +export class AddIndexToTxTimestamp1572137226866 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createIndex("transaction", new TableIndex({ columnNames: ["timestamp"] })) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropIndex("transaction", new TableIndex({ columnNames: ["timestamp"] })) + } + +} diff --git a/packages/neuron-wallet/src/database/chain/migrations/1572226722928-AddOutputIndex.ts b/packages/neuron-wallet/src/database/chain/migrations/1572226722928-AddOutputIndex.ts new file mode 100644 index 0000000000..1efc10a913 --- /dev/null +++ b/packages/neuron-wallet/src/database/chain/migrations/1572226722928-AddOutputIndex.ts @@ -0,0 +1,13 @@ +import {MigrationInterface, QueryRunner, TableIndex} from "typeorm"; + +export class AddOutputIndex1572226722928 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createIndex("output", new TableIndex({ columnNames: ["lockHash", "status", "hasData", "typeScript"] })) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropIndex("output", new TableIndex({ columnNames: ["lockHash", "status", "hasData", "typeScript"] })) + } + +} diff --git a/packages/neuron-wallet/src/database/chain/ormconfig.ts b/packages/neuron-wallet/src/database/chain/ormconfig.ts index d71471384b..75b405fe6e 100644 --- a/packages/neuron-wallet/src/database/chain/ormconfig.ts +++ b/packages/neuron-wallet/src/database/chain/ormconfig.ts @@ -13,6 +13,9 @@ import { InitMigration1566959757554 } from './migrations/1566959757554-InitMigra import { AddTypeAndHasData1567144517514 } from './migrations/1567144517514-AddTypeAndHasData' import { ChangeHasDataDefault1568621556467 } from './migrations/1568621556467-ChangeHasDataDefault' import { AddLockToInput1570522869590 } from './migrations/1570522869590-AddLockToInput' +import { AddIndices1572006450765 } from './migrations/1572006450765-AddIndices' +import { AddIndexToTxTimestamp1572137226866 } from './migrations/1572137226866-AddIndexToTxTimestamp' +import { AddOutputIndex1572226722928 } from './migrations/1572226722928-AddOutputIndex' export const CONNECTION_NOT_FOUND_NAME = 'ConnectionNotFoundError' @@ -26,7 +29,7 @@ const connectOptions = async (genesisBlockHash: string): Promise { - await getConnection().manager.query(`PRAGMA busy_timeout = 3000;`) -} - export const initConnection = async (genesisBlockHash: string) => { // try to close connection, if not exist, will throw ConnectionNotFoundError when call getConnection() try { @@ -58,7 +61,8 @@ export const initConnection = async (genesisBlockHash: string) => { try { await createConnection(connectionOptions) - await setBusyTimeout() + await getConnection().manager.query(`PRAGMA busy_timeout = 3000;`) + await getConnection().manager.query(`PRAGMA temp_store = MEMORY;`) } catch (err) { logger.error(err.message) } diff --git a/packages/neuron-wallet/src/decorators/index.ts b/packages/neuron-wallet/src/decorators/index.ts index 55b5deea58..3581bbe329 100644 --- a/packages/neuron-wallet/src/decorators/index.ts +++ b/packages/neuron-wallet/src/decorators/index.ts @@ -1,10 +1,10 @@ -import errorDecorators from './errors' +import mappers from './mappers' import validatorDecorators from './validators' -export const { CatchControllerError } = errorDecorators +export const { MapApiResponse } = mappers export const { Validate, Password, Required } = validatorDecorators export default { - ...errorDecorators, + ...mappers, ...validatorDecorators, } diff --git a/packages/neuron-wallet/src/decorators/errors.ts b/packages/neuron-wallet/src/decorators/mappers.ts similarity index 56% rename from packages/neuron-wallet/src/decorators/errors.ts rename to packages/neuron-wallet/src/decorators/mappers.ts index d61af6c823..8907e5dc63 100644 --- a/packages/neuron-wallet/src/decorators/errors.ts +++ b/packages/neuron-wallet/src/decorators/mappers.ts @@ -3,27 +3,27 @@ import logger from 'utils/logger' const NODE_DISCONNECTED_CODE = 104 -export const CatchControllerError = (_target: any, _name: string, descriptor: PropertyDescriptor) => { +export const MapApiResponse = (target: any, name: string, descriptor: PropertyDescriptor) => { const originalMethod = descriptor.value return { ...descriptor, - async value(...args: any[]) { + async value(...args: any[]): Promise { try { - return await originalMethod(...args) + const res = await originalMethod(...args) + return JSON.stringify(res) } catch (err) { - logger.error(`CatchControllerError:`, err) + logger.error(`${target.name}.${name}:`, err) if (err.code === 'ECONNREFUSED') { err.code = NODE_DISCONNECTED_CODE } - return { + const res = { status: err.code || ResponseCode.Fail, message: typeof err.message === 'string' ? { content: err.message } : err.message, } + return JSON.stringify(res) } }, } } -export default { - CatchControllerError, -} +export default { MapApiResponse } diff --git a/packages/neuron-wallet/src/listeners/tx-status.ts b/packages/neuron-wallet/src/listeners/tx-status.ts index 3f9be3f9e8..b33499320d 100644 --- a/packages/neuron-wallet/src/listeners/tx-status.ts +++ b/packages/neuron-wallet/src/listeners/tx-status.ts @@ -36,8 +36,6 @@ const getTransactionStatus = async (hash: string) => { } } -/* eslint no-await-in-loop: "off" */ -/* eslint no-restricted-syntax: "off" */ const trackingStatus = async () => { const pendingTransactions = await FailedTransaction.pendings() if (!pendingTransactions.length) { diff --git a/packages/neuron-wallet/src/services/addresses.ts b/packages/neuron-wallet/src/services/addresses.ts index 552d8472ae..717d88b50d 100644 --- a/packages/neuron-wallet/src/services/addresses.ts +++ b/packages/neuron-wallet/src/services/addresses.ts @@ -95,8 +95,6 @@ export default class AddressService { ) } - /* eslint no-await-in-loop: "off" */ - /* eslint no-restricted-syntax: "off" */ public static updateTxCountAndBalances = async ( addresses: string[], url: string = NodeService.getInstance().core.rpc.node.url diff --git a/packages/neuron-wallet/src/services/cells.ts b/packages/neuron-wallet/src/services/cells.ts index 28ad494d26..c8db467daa 100644 --- a/packages/neuron-wallet/src/services/cells.ts +++ b/packages/neuron-wallet/src/services/cells.ts @@ -7,9 +7,6 @@ import SkipDataAndType from './settings/skip-data-and-type' export const MIN_CELL_CAPACITY = '6100000000' -/* eslint @typescript-eslint/no-unused-vars: "warn" */ -/* eslint no-await-in-loop: "warn" */ -/* eslint no-restricted-syntax: "warn" */ export default class CellsService { // exclude hasData = true and typeScript != null public static getBalance = async ( @@ -31,9 +28,16 @@ export default class CellsService { const cells: OutputEntity[] = await getConnection() .getRepository(OutputEntity) - .find({ - where: queryParams, - }) + .createQueryBuilder('output') + .select([ + "output.lockHash", + "output.status", + "output.hasData", + "output.typeScript", + "output.capacity" + ]) + .where(queryParams) + .getMany() const capacity: bigint = cells.map(c => BigInt(c.capacity)).reduce((result, c) => result + c, BigInt(0)) diff --git a/packages/neuron-wallet/src/services/indexer/indexer-rpc.ts b/packages/neuron-wallet/src/services/indexer/indexer-rpc.ts index be389a51cd..2abcc574b9 100644 --- a/packages/neuron-wallet/src/services/indexer/indexer-rpc.ts +++ b/packages/neuron-wallet/src/services/indexer/indexer-rpc.ts @@ -16,27 +16,15 @@ export default class IndexerRPC { return this.core.rpc.indexLockHash(lockHash, indexFrom) } - public getTransactionByLockHash = async ( - lockHash: string, - page: string, - per: string, - reverseOrder: boolean = false - ) => { - const result = await this.core.rpc.getTransactionsByLockHash(lockHash, page, per, reverseOrder) - return result + public getTransactionsByLockHash = async (lockHash: string, page: string, per: string, reverseOrder: boolean = false) => { + return await this.core.rpc.getTransactionsByLockHash(lockHash, page, per, reverseOrder) } public getLockHashIndexStates = async () => { return this.core.rpc.getLockHashIndexStates() } - public getLiveCellsByLockHash = async ( - lockHash: string, - page: string, - per: string, - reverseOrder: boolean = false - ) => { - const result = await this.core.rpc.getLiveCellsByLockHash(lockHash, page, per, reverseOrder) - return result + public getLiveCellsByLockHash = async (lockHash: string, page: string, per: string, reverseOrder: boolean = false) => { + return await this.core.rpc.getLiveCellsByLockHash(lockHash, page, per, reverseOrder) } } diff --git a/packages/neuron-wallet/src/services/indexer/queue.ts b/packages/neuron-wallet/src/services/indexer/queue.ts index cbd0487059..6391300c77 100644 --- a/packages/neuron-wallet/src/services/indexer/queue.ts +++ b/packages/neuron-wallet/src/services/indexer/queue.ts @@ -26,7 +26,6 @@ enum TxPointType { } export default class IndexerQueue { - // private lockHashes: string[] private lockHashInfos: LockHashInfo[] private indexerRPC: IndexerRPC private getBlocksService: GetBlocks @@ -48,7 +47,6 @@ export default class IndexerQueue { private url: string constructor(url: string, lockHashInfos: LockHashInfo[], tipNumberSubject: Subject) { - // this.lockHashes = lockHashes this.lockHashInfos = lockHashInfos this.url = url this.indexerRPC = new IndexerRPC(url) @@ -62,7 +60,6 @@ export default class IndexerQueue { } public setLockHashInfos = (lockHashInfos: LockHashInfo[]): void => { - // this.lockHashes = lockHashes this.lockHashInfos = lockHashInfos this.indexed = false } @@ -76,8 +73,6 @@ export default class IndexerQueue { this.resetFlag = true } - /* eslint no-await-in-loop: "off" */ - /* eslint no-restricted-syntax: "off" */ public start = async () => { while (!this.stopped) { try { @@ -148,8 +143,7 @@ export default class IndexerQueue { .map(state => HexUtils.toDecimal(state.blockNumber)) const uniqueBlockNumbers = [...new Set(blockNumbers)] const blockNumbersBigInt = uniqueBlockNumbers.map(num => BigInt(num)) - const minBlockNumber = Utils.min(blockNumbersBigInt) - return minBlockNumber + return Utils.min(blockNumbersBigInt) } public indexLockHashes = async (lockHashInfos: LockHashInfo[]) => { @@ -168,10 +162,12 @@ export default class IndexerQueue { let page = 0 let stopped = false while (!stopped) { - const txs = await this.indexerRPC.getTransactionByLockHash(lockHash, `0x${page.toString(16)}`, `0x${this.per.toString(16)}`) + const txs = await this.indexerRPC.getTransactionsByLockHash(lockHash, `0x${page.toString(16)}`, `0x${this.per.toString(16)}`) if (txs.length < this.per) { stopped = true } + logger.debug(`indexer txs for: ${type} ${lockHash}, page: ${page}, tx count: ${txs.length}`) + for (const tx of txs) { let txPoint: CKBComponents.TransactionPoint | null = null if (type === TxPointType.CreatedBy) { @@ -197,11 +193,9 @@ export default class IndexerQueue { addresses: [address], url: this.url, }) - return + continue } - logger.debug('indexer fetched tx:', type, txPoint.txHash) - // tx timestamp / blockNumber / blockHash let txEntity: TransactionEntity | undefined = await TransactionPersistor.get(transaction.hash) if (!txEntity || !txEntity.blockHash) { diff --git a/packages/neuron-wallet/src/services/sync/block-listener.ts b/packages/neuron-wallet/src/services/sync/block-listener.ts index 92932aa024..e509503e56 100644 --- a/packages/neuron-wallet/src/services/sync/block-listener.ts +++ b/packages/neuron-wallet/src/services/sync/block-listener.ts @@ -77,8 +77,6 @@ export default class BlockListener { }) } - /* eslint no-await-in-loop: "off" */ - /* eslint no-restricted-syntax: "off" */ public setToTip = async () => { const timeout = 5000 let number: bigint = BigInt(0) diff --git a/packages/neuron-wallet/src/services/sync/check-and-save/output.ts b/packages/neuron-wallet/src/services/sync/check-and-save/output.ts index 04c49684ce..7537f83dd1 100644 --- a/packages/neuron-wallet/src/services/sync/check-and-save/output.ts +++ b/packages/neuron-wallet/src/services/sync/check-and-save/output.ts @@ -18,7 +18,7 @@ export default class CheckOutput { return this.output } - public checkLockHash = async (lockHashList: string[]): Promise => { + public checkLockHash = (lockHashList: string[]): boolean => { return lockHashList.includes(this.output.lockHash!) } } diff --git a/packages/neuron-wallet/src/services/sync/check-and-save/tx.ts b/packages/neuron-wallet/src/services/sync/check-and-save/tx.ts index 537f236017..98a9b832f5 100644 --- a/packages/neuron-wallet/src/services/sync/check-and-save/tx.ts +++ b/packages/neuron-wallet/src/services/sync/check-and-save/tx.ts @@ -25,7 +25,7 @@ export default class CheckTx { } public check = async (lockHashes: string[]): Promise => { - const outputs: Cell[] = await this.filterOutputs(lockHashes) + const outputs: Cell[] = this.filterOutputs(lockHashes) const inputAddresses = await this.filterInputs(lockHashes) const outputAddresses: string[] = outputs.map(output => { @@ -50,22 +50,18 @@ export default class CheckTx { return false } - public filterOutputs = async (lockHashes: string[]) => { - const cells: Cell[] = (await Promise.all( - this.tx.outputs!.map(async output => { - const checkOutput = new CheckOutput(output) - const result = await checkOutput.checkLockHash(lockHashes) - if (result) { - return output - } - return false - }) - )).filter(cell => !!cell) as Cell[] + public filterOutputs = (lockHashes: string[]) => { + const cells: Cell[] = this.tx.outputs!.map(output => { + const checkOutput = new CheckOutput(output) + const result = checkOutput.checkLockHash(lockHashes) + if (result) { + return output + } + return false + }).filter(cell => !!cell) as Cell[] return cells } - /* eslint no-await-in-loop: "off" */ - /* eslint no-restricted-syntax: "warn" */ public filterInputs = async (lockHashes: string[]): Promise => { const inputs = this.tx.inputs! diff --git a/packages/neuron-wallet/src/services/sync/get-blocks.ts b/packages/neuron-wallet/src/services/sync/get-blocks.ts index cc162cc3f4..9fa65b663f 100644 --- a/packages/neuron-wallet/src/services/sync/get-blocks.ts +++ b/packages/neuron-wallet/src/services/sync/get-blocks.ts @@ -9,6 +9,7 @@ import CheckTx from 'services/sync/check-and-save/tx' import { TransactionPersistor } from 'services/tx' import LockUtils from 'models/lock-utils' import { addressesUsedSubject } from './renderer-params' +import logger from 'utils/logger' export default class GetBlocks { private retryTime: number @@ -34,18 +35,24 @@ export default class GetBlocks { } public getTipBlockNumber = async (): Promise => { - const tip: string = await this.core.rpc.getTipBlockNumber() - return tip + return this.core.rpc.getTipBlockNumber() } public checkAndSave = async (blocks: Block[], lockHashes: string[]): Promise => { + const cachedPreviousTxs = new Map() for (const block of blocks) { + logger.debug(`checking block #${block.header.number}, ${block.transactions.length} txs`) for (const tx of block.transactions) { const checkTx = new CheckTx(tx, this.url) const addresses = await checkTx.check(lockHashes) if (addresses.length > 0) { for (const input of tx.inputs!) { - const previousTxWithStatus = await this.getTransaction(input.previousOutput!.txHash) + const previousTxHash = input.previousOutput!.txHash + let previousTxWithStatus = cachedPreviousTxs.get(previousTxHash) + if (!previousTxWithStatus) { + previousTxWithStatus = await this.getTransaction(previousTxHash) + cachedPreviousTxs.set(previousTxHash, previousTxWithStatus) + } const previousTx = TypeConvert.toTransaction(previousTxWithStatus.transaction) const previousOutput = previousTx.outputs![+input.previousOutput!.index] input.lock = previousOutput.lock @@ -60,20 +67,19 @@ export default class GetBlocks { } } } + cachedPreviousTxs.clear() } public retryGetBlock = async (num: string): Promise => { const block: Block = await Utils.retry(this.retryTime, this.retryInterval, async () => { - const b: Block = await this.getBlockByNumber(num) - return b + return await this.getBlockByNumber(num) }) return block } public getTransaction = async (hash: string): Promise => { - const tx = await this.core.rpc.getTransaction(hash) - return tx + return await this.core.rpc.getTransaction(hash) } public getHeader = async (hash: string): Promise => { @@ -88,8 +94,7 @@ export default class GetBlocks { public genesisBlockHash = async (): Promise => { const hash: string = await Utils.retry(3, 100, async () => { - const h: string = await this.core.rpc.getBlockHash('0x0') - return h + return await this.core.rpc.getBlockHash('0x0') }) return hash diff --git a/packages/neuron-wallet/src/services/sync/queue.ts b/packages/neuron-wallet/src/services/sync/queue.ts index 68dd417d24..526daf6f65 100644 --- a/packages/neuron-wallet/src/services/sync/queue.ts +++ b/packages/neuron-wallet/src/services/sync/queue.ts @@ -46,7 +46,6 @@ export default class Queue { this.lockHashes = lockHashes } - /* eslint no-await-in-loop: "off" */ public start = async () => { while (!this.stopped) { try { @@ -136,7 +135,7 @@ export default class Queue { const range = await this.rangeForCheck.getRange() const rangeFirstBlockHeader: BlockHeader = range[0] await this.currentBlockNumber.updateCurrent(BigInt(rangeFirstBlockHeader.number)) - await this.rangeForCheck.clearRange() + this.rangeForCheck.clearRange() await TransactionPersistor.deleteWhenFork(rangeFirstBlockHeader.number) throw new Error(`chain forked: ${checkResult.type}`) } else if (checkResult.type === CheckResultType.BlockHeadersNotMatch) { diff --git a/packages/neuron-wallet/src/services/sync/utils.ts b/packages/neuron-wallet/src/services/sync/utils.ts index eeb6d3cd1f..668319ebe8 100644 --- a/packages/neuron-wallet/src/services/sync/utils.ts +++ b/packages/neuron-wallet/src/services/sync/utils.ts @@ -21,7 +21,6 @@ export default class Utils { return new Promise(resolve => setTimeout(resolve, ms)) } - /* eslint no-await-in-loop: "off" */ public static retry = async (times: number, interval: number, callback: any): Promise => { let retryTime = 0 @@ -39,7 +38,6 @@ export default class Utils { return undefined } - /* eslint no-restricted-syntax: "off" */ public static mapSeries = async (array: any[], callback: any): Promise => { const result = [] for (const item of array) { diff --git a/packages/neuron-wallet/src/services/tx/indexer-transaction.ts b/packages/neuron-wallet/src/services/tx/indexer-transaction.ts index 5beb9393d7..5255f987b3 100644 --- a/packages/neuron-wallet/src/services/tx/indexer-transaction.ts +++ b/packages/neuron-wallet/src/services/tx/indexer-transaction.ts @@ -64,7 +64,7 @@ export default class IndexerTransaction { }) .getOne() - if (output) { + if (output && output.status !== OutputStatus.Dead) { await getConnection().manager.update( InputEntity, { @@ -83,7 +83,7 @@ export default class IndexerTransaction { .getRepository(TransactionEntity) .createQueryBuilder('tx') .where({ - hash: output.outPointTxHash, + hash: txHash, }) .getOne() if (tx) { diff --git a/packages/neuron-wallet/src/services/tx/transaction-persistor.ts b/packages/neuron-wallet/src/services/tx/transaction-persistor.ts index 1f885fa49e..9b8656794d 100644 --- a/packages/neuron-wallet/src/services/tx/transaction-persistor.ts +++ b/packages/neuron-wallet/src/services/tx/transaction-persistor.ts @@ -7,9 +7,6 @@ import LockUtils from 'models/lock-utils' import { OutputStatus, TxSaveType } from './params' import Utils from 'services/sync/utils' -/* eslint @typescript-eslint/no-unused-vars: "warn" */ -/* eslint no-await-in-loop: "off" */ -/* eslint no-restricted-syntax: "off" */ export class TransactionPersistor { // After the tx is sent: // 1. If the tx is not persisted before sending, output = sent, input = pending @@ -265,11 +262,8 @@ export class TransactionPersistor { } public static get = async (txHash: string) => { - const txEntity: TransactionEntity | undefined = await getConnection() - .getRepository(TransactionEntity) - .findOne(txHash, { relations: ['inputs', 'outputs'] }) - - return txEntity + return await getConnection().getRepository(TransactionEntity) + .findOne(txHash, { relations: ['inputs'] }) } public static saveSentTx = async ( diff --git a/packages/neuron-wallet/src/services/tx/transaction-service.ts b/packages/neuron-wallet/src/services/tx/transaction-service.ts index 28022851f0..52c7e7ccbd 100644 --- a/packages/neuron-wallet/src/services/tx/transaction-service.ts +++ b/packages/neuron-wallet/src/services/tx/transaction-service.ts @@ -38,9 +38,6 @@ export enum SearchType { Unknown = 'unknown', } -/* eslint @typescript-eslint/no-unused-vars: "warn" */ -/* eslint no-await-in-loop: "off" */ -/* eslint no-restricted-syntax: "off" */ export class TransactionsService { public static filterSearchType = (value: string) => { if (value === '') { @@ -84,7 +81,7 @@ export class TransactionsService { return [ `${ base[0] - } AND (CAST(ifnull("tx"."timestamp", "tx"."createdAt") AS UNSIGNED BIG INT) >= :beginTimestamp AND CAST(ifnull("tx"."timestamp", "tx"."createdAt") AS UNSIGNED BIG INT) < :endTimestamp)`, + } AND (CAST("tx"."timestamp") AS UNSIGNED BIG INT) >= :beginTimestamp AND CAST("tx"."timestamp") AS UNSIGNED BIG INT) < :endTimestamp)`, { lockHashes: params.lockHashes, beginTimestamp, @@ -155,15 +152,14 @@ export class TransactionsService { const query = getConnection() .getRepository(TransactionEntity) .createQueryBuilder('tx') - .addSelect(`ifnull('tx'.timestamp, 'tx'.createdAt)`, 'tt') .leftJoinAndSelect('tx.inputs', 'input') .leftJoinAndSelect('tx.outputs', 'output') .where(searchParams[0], searchParams[1] as ObjectLiteral) - .orderBy(`tt`, 'DESC') const totalCount: number = await query.getCount() const transactions: TransactionEntity[] = await query + .orderBy(`tx.timestamp`, 'DESC') .skip(skip) .take(params.pageSize) .getMany() @@ -288,10 +284,8 @@ export class TransactionsService { const count: number = await getConnection() .getRepository(TransactionEntity) .createQueryBuilder('tx') - .leftJoinAndSelect('tx.inputs', 'input') - .leftJoinAndSelect('tx.outputs', 'output') .where( - `(input.lockHash IN (:...lockHashes) OR output.lockHash IN (:...lockHashes)) AND tx.status IN (:...status)`, + `tx.hash in (select output.transactionHash from output where output.lockHash in (:...lockHashes) union select input.transactionHash from input where input.lockHash in (:...lockHashes)) AND tx.status IN (:...status)`, { lockHashes, status, 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 93558e0aa3..8a19b7e5fe 100644 --- a/packages/neuron-wallet/src/startup/sync-block-task/create.ts +++ b/packages/neuron-wallet/src/startup/sync-block-task/create.ts @@ -47,7 +47,6 @@ const loadURL = `file://${path.join(__dirname, 'index.html')}` export { networkSwitchSubject } -/* eslint global-require: "off" */ // create a background task to sync transactions // this task is a renderer process const createSyncBlockTask = () => { diff --git a/packages/neuron-wallet/tests-e2e/tests/notification.ts b/packages/neuron-wallet/tests-e2e/tests/notification.ts index a6e01c329a..103e139618 100644 --- a/packages/neuron-wallet/tests-e2e/tests/notification.ts +++ b/packages/neuron-wallet/tests-e2e/tests/notification.ts @@ -2,7 +2,7 @@ import Application from '../application' import { createWallet } from '../operations' /** - * 1. check the alert, it should be empty + * 1. check the alert, it should be disconnected to the network * 2. navigate to wallet settingsState * 3. delete a wallet * 4. input a wrong password @@ -32,14 +32,15 @@ export default (app: Application) => { describe('Test alert message and notification', () => { const messages = { + disconnected: 'Fail to connect to the node', incorrectPassword: 'Password is incorrect', } - app.test('There is no alerts after the app launched', async () => { + app.test('It should have an alert message of disconnection', async () => { const { client } = app.spectron const alertComponent = await client.$('.ms-MessageBar-text') const msg = await client.elementIdText(alertComponent.value.ELEMENT) - expect(msg.value).toBe('') + expect(msg.value).toBe(messages.disconnected) }) app.test('It should have an alert message of incorrect password', async () => { @@ -56,12 +57,14 @@ export default (app: Application) => { expect(msg.value).toBe(messages.incorrectPassword) }) - app.test('It should have a message in the notification', async () => { + app.test('It should have two messages in the notification', async () => { const { client } = app.spectron const messageComponents = await client.$$('.ms-Panel-content p') expect(messageComponents.length).toBe(Object.keys(messages).length) const incorrectPasswordMsg = await client.element(`//P[text()="${messages.incorrectPassword}"]`) + const disconnectMsg = await client.element(`//P[text()="${messages.disconnected}"]`) expect(incorrectPasswordMsg.state).not.toBe('failure') + expect(disconnectMsg.state).not.toBe('failed') }) // TODO: dismiss a message @@ -76,7 +79,8 @@ export default (app: Application) => { await app.waitUntilLoaded() app.wait(4000) const alertComponent = await client.$('.ms-MessageBar--error') - expect(alertComponent.state).toBe('failure') + const msg = await client.elementIdText(alertComponent.value.ELEMENT) + expect(msg.value).toBe(messages.disconnected) }) }) } diff --git a/packages/neuron-wallet/tests-e2e/tests/sendTransaction.ts b/packages/neuron-wallet/tests-e2e/tests/sendTransaction.ts index e179b240ce..468f880932 100644 --- a/packages/neuron-wallet/tests-e2e/tests/sendTransaction.ts +++ b/packages/neuron-wallet/tests-e2e/tests/sendTransaction.ts @@ -115,36 +115,36 @@ export default (app: Application) => { await app.waitUntilLoaded() }) - app.test('default price should be 0 and default speed should be 3min', async () => { + app.test('default price should be 1000 and default speed should be 500 blocks', async () => { const { client } = app.spectron const transactionFeePanel = client.$('div[aria-label="transaction fee"]') const [, priceField] = await transactionFeePanel.$$('input') - expect((await client.elementIdAttribute(priceField.value.ELEMENT, 'value')).value).toBe('0') + expect((await client.elementIdAttribute(priceField.value.ELEMENT, 'value')).value).toBe('1000') const speedDropdown = await client.$('div[role=listbox]') - expect((await client.elementIdAttribute(speedDropdown.value.ELEMENT, 'innerText')).value).toBe('~ 3min') + expect((await client.elementIdAttribute(speedDropdown.value.ELEMENT, 'innerText')).value).toBe('~ 500 blocks') }) - app.test('Change speed to immediately and the price should be 180', async () => { + app.test('Change speed to ~ 100 blocks and the price should be 3000', async () => { const { client } = app.spectron client.click('div[role=listbox]') await app.waitUntilLoaded() - client.click('button[title=immediately]') + client.click('button[title="~ 100 blocks"]') await app.waitUntilLoaded() const transactionFeePanel = client.$('div[aria-label="transaction fee"]') const [, priceField] = await transactionFeePanel.$$('input') - expect((await client.elementIdAttribute(priceField.value.ELEMENT, 'value')).value).toBe('180') + expect((await client.elementIdAttribute(priceField.value.ELEMENT, 'value')).value).toBe('3000') }) - app.test('Change the price to 150 and the speed should switch to ~ 30s', async () => { + app.test('Change the price to 100000 and the speed should switch to immediately', async () => { const { client } = app.spectron const transactionFeePanel = client.$('div[aria-label="transaction fee"]') const [, priceField] = await transactionFeePanel.$$('input') client.elementIdClear(priceField.value.ELEMENT) await app.waitUntilLoaded() - client.elementIdValue(priceField.value.ELEMENT, '150') + client.elementIdValue(priceField.value.ELEMENT, '00') const speedDropdown = await client.$('div[role=listbox]') - expect((await client.elementIdAttribute(speedDropdown.value.ELEMENT, 'innerText')).value).toBe('~ 30s') + expect((await client.elementIdAttribute(speedDropdown.value.ELEMENT, 'innerText')).value).toBe('immediately') }) }) } diff --git a/yarn.lock b/yarn.lock index 210003eb60..6977c3373c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16613,10 +16613,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typeorm@0.2.19: - version "0.2.19" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.19.tgz#a0cff0714180e5720df157df02c5759a1a646dc3" - integrity sha512-xKVx/W41zckQ7v8WYcpRhSKpjXDKG/Jgjy0RWvYelR8ZnfyblNRL12jF4P8tIhwXv6l5t01s7HEc9lR+zb6Gtg== +typeorm@0.2.20: + version "0.2.20" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.20.tgz#efb60f2e55a7d08fc365f281ec2a71c87a9ebba5" + integrity sha512-VxB+9qH8D+PM19MIx18Zs3Fqv/ZINnnQvUGmBEiLYDrB9etdSdamgSTCIhWdFNndeJ6ldH4jbD0Z6HWsepMPlA== dependencies: app-root-path "^2.0.1" buffer "^5.1.0"