diff --git a/CHANGELOG.md b/CHANGELOG.md index 944a2d5f33..69eb28eeb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,38 @@ +## [0.25.2](https://github.com/nervosnetwork/neuron/compare/v0.25.1...v0.25.2) (2019-11-29) + + +### Bug Fixes + +* also clean lock/dao info in renderer process ([a0b2470](https://github.com/nervosnetwork/neuron/commit/a0b2470)) +* Fix the problem that balance not right if switch network from default network ([0f763a5](https://github.com/nervosnetwork/neuron/commit/0f763a5)) +* remove bufferTime for address created event ([9b0a077](https://github.com/nervosnetwork/neuron/commit/9b0a077)) +* the missing txs ([ed557b6](https://github.com/nervosnetwork/neuron/commit/ed557b6)) +* **neuron-ui:** remove www from docs.nervos.org ([3fc8154](https://github.com/nervosnetwork/neuron/commit/3fc8154)) +* balance not update after sent tx ([65e51dd](https://github.com/nervosnetwork/neuron/commit/65e51dd)) +* **neuron-ui:** show 0 if withdraw rpc returns errors ([b714376](https://github.com/nervosnetwork/neuron/commit/b714376)) +* clean lock utils info and dao utils info when switch network ([60ec486](https://github.com/nervosnetwork/neuron/commit/60ec486)) +* initialize NetworksService in renderer process ([73f1bf0](https://github.com/nervosnetwork/neuron/commit/73f1bf0)) +* network switch event broadcast twice ([f1b0f72](https://github.com/nervosnetwork/neuron/commit/f1b0f72)) +* pending in windows when network off ([67dcb79](https://github.com/nervosnetwork/neuron/commit/67dcb79)) +* sign witnesses test ([5000edd](https://github.com/nervosnetwork/neuron/commit/5000edd)) + + +### Features + +* **neuron-ui:** update the url to nervos dao rfc ([6b68ab6](https://github.com/nervosnetwork/neuron/commit/6b68ab6)) +* Add API for downloading and installing updates ([b8d24ca](https://github.com/nervosnetwork/neuron/commit/b8d24ca)) +* Add app updater subject and state ([423109d](https://github.com/nervosnetwork/neuron/commit/423109d)) +* Adding check update to settings view ([98fe06c](https://github.com/nervosnetwork/neuron/commit/98fe06c)) +* Connect updater events to UI ([b267321](https://github.com/nervosnetwork/neuron/commit/b267321)) +* Delete unused updater translations ([bcafce8](https://github.com/nervosnetwork/neuron/commit/bcafce8)) +* Different stage status of checking updates ([cd82ca4](https://github.com/nervosnetwork/neuron/commit/cd82ca4)) +* Polish updater i18n translations and UI ([af45f0c](https://github.com/nervosnetwork/neuron/commit/af45f0c)) +* Show release notes when there's update available ([8cf9581](https://github.com/nervosnetwork/neuron/commit/8cf9581)) +* Trigger check updates menu item enabling/disabling ([cd8e5d5](https://github.com/nervosnetwork/neuron/commit/cd8e5d5)) +* **neuron-ui:** add copy address and copy tx hash context menus on the tx detail view. ([7d86454](https://github.com/nervosnetwork/neuron/commit/7d86454)) + + + ## [0.25.1](https://github.com/nervosnetwork/neuron/compare/v0.25.0...v0.25.1) (2019-11-18) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6f62d0933a..e720ce4279 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -16,8 +16,12 @@ pr: include: - '*' -jobs: - - job: macOS +stages: +- stage: unit_tests + displayName: Unit Tests + jobs: + - job: mac + displayName: macOS pool: vmImage: 'macos-10.14' strategy: @@ -37,9 +41,10 @@ jobs: - script: CI=true yarn test name: Test - - job: Linux + - job: linux + displayName: Linux pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-18.04' strategy: matrix: node_12_x: @@ -60,7 +65,8 @@ jobs: CI=true yarn test name: Test - - job: Windows + - job: win + displayName: Windows pool: vmImage: 'windows-2019' strategy: @@ -84,7 +90,12 @@ jobs: yarn test name: Test +- stage: e2e_tests + displayName: Integration Tests + dependsOn: [] + jobs: - job: Integration + displayName: Integration Tests pool: vmImage: 'macos-10.14' steps: @@ -99,8 +110,12 @@ jobs: - script: yarn test:e2e name: Test - - job: Release - condition: eq(variables['build.sourceBranch'], 'refs/heads/master') +- stage: release + displayName: Release Binaries + condition: eq(variables['build.sourceBranch'], 'refs/heads/master') + jobs: + - job: release_mac + displayName: Release macOS pool: vmImage: 'macos-10.14' steps: @@ -117,19 +132,57 @@ jobs: displayName: 'Download macOS Signing Certificate' inputs: secureFile: Neuron_mac.p12 + - script: yarn release mac + name: Release + displayName: 'Sign and Release' + env: + CSC_LINK: $(macSiginingCertificate.secureFilePath) + CSC_KEY_PASSWORD: $(macSiginingCertificatePassword) + APPLE_ID: $(appleId) + APPLE_ID_PASSWORD: $(appleIdPassword) + GH_TOKEN: $(ghToken) + + - job: release_linux + displayName: Release Linux + pool: + vmImage: 'ubuntu-18.04' + steps: + - task: NodeTool@0 + inputs: + versionSpec: 12.x + displayName: 'Install Node.js' + - script: | + yarn global add lerna + yarn bootstrap + name: Bootstrap + - script: yarn release linux + name: Release + displayName: 'Sign and Release' + env: + GH_TOKEN: $(ghToken) + + - job: release_win + displayName: Release Windows + pool: + vmImage: 'macos-10.14' + steps: + - task: NodeTool@0 + inputs: + versionSpec: 12.x + displayName: 'Install Node.js' + - script: | + yarn global add lerna + yarn bootstrap + name: Bootstrap - task: DownloadSecureFile@1 name: winSiginingCertificate displayName: 'Download Windows Signing Certificate' inputs: secureFile: Neuron_win.p12 - - script: yarn release + - script: yarn release win name: Release displayName: 'Sign and Release' env: - CSC_LINK: $(macSiginingCertificate.secureFilePath) - CSC_KEY_PASSWORD: $(macSiginingCertificatePassword) WIN_CSC_LINK: $(winSiginingCertificate.secureFilePath) WIN_CSC_KEY_PASSWORD: $(winSiginingCertificatePassword) - APPLE_ID: $(appleId) - APPLE_ID_PASSWORD: $(appleIdPassword) - GH_TOKEN: $(ghToken) + GH_TOKEN: $(ghToken) \ No newline at end of file diff --git a/lerna.json b/lerna.json index 91991a3759..7079de735e 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "0.25.1", + "version": "0.25.2", "npmClient": "yarn", "useWorkspaces": true } diff --git a/package.json b/package.json index 1b7b788329..c199379971 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "neuron", "productName": "Neuron", "description": "CKB Neuron Wallet", - "version": "0.25.1", + "version": "0.25.2", "private": true, "author": { "name": "Nervos Core Dev", diff --git a/packages/neuron-ui/package.json b/packages/neuron-ui/package.json index 074933c3a6..c70162ab30 100644 --- a/packages/neuron-ui/package.json +++ b/packages/neuron-ui/package.json @@ -1,6 +1,6 @@ { "name": "neuron-ui", - "version": "0.25.1", + "version": "0.25.2", "private": true, "author": { "name": "Nervos Core Dev", @@ -43,7 +43,7 @@ "last 2 chrome versions" ], "dependencies": { - "@nervosnetwork/ckb-sdk-core": "0.25.0-alpha.0", + "@nervosnetwork/ckb-sdk-core": "0.25.0", "@uifabric/experiments": "7.16.1", "@uifabric/styling": "7.6.2", "canvg": "2.0.0", @@ -54,7 +54,7 @@ "react": "16.9.0", "react-dom": "16.9.0", "react-i18next": "11.0.1", - "react-router-dom": "5.0.1", + "react-router-dom": "5.1.2", "react-scripts": "3.2.0", "styled-components": "5.0.0-beta.0" }, @@ -72,7 +72,7 @@ "@types/node": "12.7.4", "@types/react": "16.9.2", "@types/react-dom": "16.9.0", - "@types/react-router-dom": "4.3.5", + "@types/react-router-dom": "5.1.3", "@types/storybook-react-router": "1.0.0", "@types/storybook__addon-knobs": "5.0.3", "@types/storybook__addon-storyshots": "5.1.1", diff --git a/packages/neuron-ui/src/components/GeneralSetting/index.tsx b/packages/neuron-ui/src/components/GeneralSetting/index.tsx index f535311820..98c710ecff 100644 --- a/packages/neuron-ui/src/components/GeneralSetting/index.tsx +++ b/packages/neuron-ui/src/components/GeneralSetting/index.tsx @@ -1,43 +1,159 @@ -import React, { useCallback, useState } from 'react' +import React, { useContext, useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import { Stack, PrimaryButton, Spinner, Text } from 'office-ui-fabric-react' +import { Stack, PrimaryButton, Spinner, Text, ProgressIndicator } from 'office-ui-fabric-react' +import { NeuronWalletContext } from 'states/stateProvider' import { StateWithDispatch } from 'states/stateProvider/reducer' import { addPopup } from 'states/stateProvider/actionCreators' -import { clearCellCache } from 'services/remote' +import { checkForUpdates, downloadUpdate, installUpdate, clearCellCache } from 'services/remote' +import { releaseNotesStyle } from './style.module.scss' + +const UpdateDownloadStatus = ({ + progress = 0, + newVersion = '', + releaseNotes = '', +}: React.PropsWithoutRef<{ progress: number; newVersion: string; releaseNotes: string }>) => { + const [t] = useTranslation() + const available = newVersion !== '' && progress < 0 + const downloaded = progress >= 1 + + if (available) { + const download = () => { + downloadUpdate() + } + + const releaseNotesHtml = () => { + return { __html: releaseNotes } + } + + /* eslint-disable react/no-danger */ + + return ( + + + {t('updates.updates-found-do-you-want-to-update', { version: newVersion })} + +

{t('updates.release-notes')}

+
+
+
+ + + {t('updates.download-update')} + + + + ) + } + + if (downloaded) { + const quitAndInstall = () => { + installUpdate() + } + + return ( + + + {t('updates.updates-downloaded-about-to-quit-and-install')} + + + + {t('updates.quit-and-install')} + + + + ) + } + + return ( + + ) +} const GeneralSetting = ({ dispatch }: React.PropsWithoutRef) => { const [t] = useTranslation() - const [clearing, setClearing] = useState(false) + const { updater } = useContext(NeuronWalletContext) + const [clearingCache, setClearingCache] = useState(false) + + const checkUpdates = useCallback(() => { + checkForUpdates() + }, []) const clearCache = useCallback(() => { - setClearing(true) + setClearingCache(true) setTimeout(() => { clearCellCache().finally(() => { addPopup('clear-cache-successfully')(dispatch) - setClearing(false) + setClearingCache(false) }) }, 100) }, [dispatch]) return ( - - - - {clearing ? : t('settings.general.clear-cache')} - + + + + {updater.version !== '' || updater.downloadProgress >= 0 ? ( + + ) : ( + + {updater.checking ? : t('updates.check-updates')} + + )} + + + + + + {t('settings.general.clear-cache-description')} + + + + {clearingCache ? : t('settings.general.clear-cache')} + + - - {t('settings.general.clear-cache-description')} - ) } diff --git a/packages/neuron-ui/src/components/GeneralSetting/style.module.scss b/packages/neuron-ui/src/components/GeneralSetting/style.module.scss new file mode 100644 index 0000000000..5a1025cfb1 --- /dev/null +++ b/packages/neuron-ui/src/components/GeneralSetting/style.module.scss @@ -0,0 +1,21 @@ +.releaseNotesStyle { + overflow: scroll; + height: 200px; + margin-bottom: 20px; + padding: 10px 15px 15px 15px; + border: solid 1px #ccc; + + ul { + list-style-type: disc; + padding-left: 30px; + + li { + margin: 5px 0; + } + } + + a { + text-decoration: none; + pointer-events: none; + } +} \ No newline at end of file diff --git a/packages/neuron-ui/src/components/NervosDAO/WithdrawDialog.tsx b/packages/neuron-ui/src/components/NervosDAO/WithdrawDialog.tsx index 02eac05dc7..238cd784c6 100644 --- a/packages/neuron-ui/src/components/NervosDAO/WithdrawDialog.tsx +++ b/packages/neuron-ui/src/components/NervosDAO/WithdrawDialog.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react' import { Dialog, DialogFooter, DefaultButton, PrimaryButton, DialogType, Text } from 'office-ui-fabric-react' import { useTranslation } from 'react-i18next' import { shannonToCKBFormatter, localNumberFormatter } from 'utils/formatters' -import { ckbCore } from 'services/chain' +import { calculateDaoMaximumWithdraw, getBlock } from 'services/chain' import calculateTargetEpochNumber from 'utils/calculateClaimEpochNumber' import { epochParser } from 'utils/parsers' @@ -26,8 +26,7 @@ const WithdrawDialog = ({ if (!record) { return } - ckbCore.rpc - .getBlock(record.blockHash) + getBlock(record.blockHash) .then(b => { setDepositEpoch(b.header.epoch) }) @@ -40,14 +39,13 @@ const WithdrawDialog = ({ return } - ;(ckbCore.rpc as any) - .calculateDaoMaximumWithdraw( - { - txHash: record.outPoint.txHash, - index: `0x${BigInt(record.outPoint.index).toString(16)}`, - }, - tipBlockHash - ) + calculateDaoMaximumWithdraw( + { + txHash: record.outPoint.txHash, + index: `0x${BigInt(record.outPoint.index).toString(16)}`, + }, + tipBlockHash + ) .then((res: string) => { setWithdrawValue(res) }) diff --git a/packages/neuron-ui/src/components/NervosDAO/index.tsx b/packages/neuron-ui/src/components/NervosDAO/index.tsx index 6f9ab71f59..43ad2b0ac1 100644 --- a/packages/neuron-ui/src/components/NervosDAO/index.tsx +++ b/packages/neuron-ui/src/components/NervosDAO/index.tsx @@ -15,7 +15,7 @@ import { MIN_DEPOSIT_AMOUNT, MEDIUM_FEE_RATE, SHANNON_CKB_RATIO, MAX_DECIMAL_DIG import { verifyAmount } from 'utils/validators' import { generateDepositTx, generateWithdrawTx, generateClaimTx } from 'services/remote' -import { ckbCore, getHeaderByNumber } from 'services/chain' +import { getHeaderByNumber, calculateDaoMaximumWithdraw } from 'services/chain' import { epochParser } from 'utils/parsers' import DAORecord from 'components/CustomRows/DAORecordRow' @@ -246,13 +246,13 @@ const NervosDAO = ({ const formattedDepositOutPoint = depositOutPoint ? { txHash: depositOutPoint.txHash, - index: BigInt(depositOutPoint.index), + index: `0x${BigInt(depositOutPoint.index).toString(16)}`, } : { txHash: outPoint.txHash, - index: BigInt(outPoint.index), + index: `0x${BigInt(outPoint.index).toString(16)}`, } - return (ckbCore.rpc as any).calculateDaoMaximumWithdraw(formattedDepositOutPoint, withdrawBlockHash) as string + return calculateDaoMaximumWithdraw(formattedDepositOutPoint, withdrawBlockHash).catch(() => null) }) ) .then(res => { diff --git a/packages/neuron-ui/src/components/Transaction/index.tsx b/packages/neuron-ui/src/components/Transaction/index.tsx index 1c9136507f..e73e0b0536 100644 --- a/packages/neuron-ui/src/components/Transaction/index.tsx +++ b/packages/neuron-ui/src/components/Transaction/index.tsx @@ -2,7 +2,14 @@ import React, { useEffect, useState, useMemo, useCallback } from 'react' import { useTranslation } from 'react-i18next' import { Stack, DetailsList, Text, CheckboxVisibility, IColumn, Icon } from 'office-ui-fabric-react' import { currentWallet as currentWalletCache } from 'services/localCache' -import { getTransaction, showErrorMessage, getAllNetworks, getCurrentNetworkID, openExternal } from 'services/remote' +import { + getTransaction, + showErrorMessage, + getAllNetworks, + getCurrentNetworkID, + openExternal, + openContextMenu, +} from 'services/remote' import { ckbCore } from 'services/chain' import { transactionState } from 'states/initStates/chain' @@ -111,7 +118,7 @@ const Transaction = () => { name: t('transaction.index'), minWidth: 60, maxWidth: 60, - onRender: (item?: any | State.DetailedOutput) => { + onRender: (item?: State.DetailedOutput) => { if (item) { return item.outPoint.index } @@ -216,7 +223,7 @@ const Transaction = () => { return } getTransaction({ hash, walletID: currentWallet.id }) - .then((res: any) => { + .then(res => { if (res.status) { setTransaction(res.result) } else { @@ -270,6 +277,75 @@ const Transaction = () => { [t, transaction] ) + const onBasicInfoContextMenu = useCallback( + (property: { label: string; value: string }, index?: number) => { + if (index === 0 && property && property.value) { + const menuTemplate = [ + { + label: t('common.copy-tx-hash'), + click: () => { + window.clipboard.writeText(property.value) + }, + }, + ] + openContextMenu(menuTemplate) + } + }, + [t] + ) + + const onInputContextMenu = useCallback( + (input?: State.DetailedInput) => { + if (input && input.lock && input.lock.args) { + try { + const address = ckbCore.utils.bech32Address(input.lock.args, { + prefix: addressPrefix, + type: ckbCore.utils.AddressType.HashIdx, + codeHashOrCodeHashIndex: '0x00', + }) + const menuTemplate = [ + { + label: t('common.copy-address'), + click: () => { + window.clipboard.writeText(address) + }, + }, + ] + openContextMenu(menuTemplate) + } catch (err) { + console.error(err) + } + } + }, + [addressPrefix, t] + ) + + const onOutputContextMenu = useCallback( + (output?: State.DetailedOutput) => { + if (output && output.lock && output.lock.args) { + try { + const address = ckbCore.utils.bech32Address(output.lock.args, { + prefix: addressPrefix, + type: ckbCore.utils.AddressType.HashIdx, + codeHashOrCodeHashIndex: '0x00', + }) + const menuTemplate = [ + { + label: t('common.copy-address'), + click: () => { + window.clipboard.writeText(address) + }, + }, + ] + openContextMenu(menuTemplate) + } catch (err) { + console.error(err) + } + } + }, + [addressPrefix, t] + ) + if (error.code) { return ( @@ -290,6 +366,7 @@ const Transaction = () => { checkboxVisibility={CheckboxVisibility.hidden} compact isHeaderVisible={false} + onItemContextMenu={onBasicInfoContextMenu} /> @@ -303,6 +380,7 @@ const Transaction = () => { items={transaction.inputs} columns={inputColumns} checkboxVisibility={CheckboxVisibility.hidden} + onItemContextMenu={onInputContextMenu} compact isHeaderVisible /> @@ -317,6 +395,7 @@ const Transaction = () => { items={transaction.outputs} columns={outputColumns} checkboxVisibility={CheckboxVisibility.hidden} + onItemContextMenu={onOutputContextMenu} compact isHeaderVisible /> diff --git a/packages/neuron-ui/src/containers/Main/hooks.ts b/packages/neuron-ui/src/containers/Main/hooks.ts index 1bd1a525ee..ada6f494e0 100644 --- a/packages/neuron-ui/src/containers/Main/hooks.ts +++ b/packages/neuron-ui/src/containers/Main/hooks.ts @@ -17,6 +17,7 @@ import { CurrentNetworkID as CurrentNetworkIDSubject, ConnectionStatus as ConnectionStatusSubject, SyncedBlockNumber as SyncedBlockNumberSubject, + AppUpdater as AppUpdaterSubject, Command as CommandSubject, } from 'services/subjects' import { ckbCore, getBlockchainInfo, getTipHeader } from 'services/chain' @@ -183,6 +184,13 @@ export const useSubscription = ({ }) }) + const appUpdaterSubscription = AppUpdaterSubject.subscribe(appUpdaterInfo => { + dispatch({ + type: NeuronWalletActions.UpdateAppUpdaterStatus, + payload: appUpdaterInfo, + }) + }) + const commandSubscription = CommandSubject.subscribe(({ winID, type, payload }: Subject.CommandMetaInfo) => { if (winID && getWinID() === winID) { switch (type) { @@ -223,6 +231,7 @@ export const useSubscription = ({ currentNetworkIDSubscription.unsubscribe() connectionStatusSubscription.unsubscribe() syncedBlockNumberSubscription.unsubscribe() + appUpdaterSubscription.unsubscribe() commandSubscription.unsubscribe() } }, [walletID, pageNo, pageSize, keywords, isAllowedToFetchList, history, dispatch]) diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index 7521caf19c..b2dfe7c53d 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -178,7 +178,7 @@ "network": "Network" }, "general": { - "clear-cache": "Clear cache", + "clear-cache": "Clear Cache", "clear-cache-description": "Clear cache if you encounter data sync or balance display problems. Neuron will rescan block data.", "show": "Show", "hide": "Hide" @@ -241,7 +241,9 @@ "toggle": { "on": "On", "off": "Off" - } + }, + "copy-tx-hash": "Copy Transaction Hash", + "copy-address": "Copy Address" }, "notification-panel": { "title": "Notifications" @@ -341,6 +343,16 @@ "withdraw-alert": "Hint: these are only {{epochs}} epochs (~{{hours}} hours) until the end of your current lock period. If you wish to withdraw in current lock period, please send withdraw request in time. There are {{nextLeftEpochs}} epochs(~{{days}} days) until the end of your next lock period.", "insufficient-period-alert-title": "Referenced Header is Invalid", "insufficient-period-alert-message": "Only mature header can be referenced in transactions(Matureness requires 4 epochs)" + }, + "updates": { + "check-updates": "Check for Updates", + "downloading-update": "Downloading update...", + "update-not-available": "There are currently no updates available.", + "release-notes": "Release Notes:", + "updates-found-do-you-want-to-update": "An update ({{version}}) is available, do you want to download and install now?", + "download-update": "Install Update", + "updates-downloaded-about-to-quit-and-install": "Update downloaded. Ready to install and relaunch.", + "quit-and-install": "Install and relaunch" } } } diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 4809dc1eae..ad859f0de5 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -241,7 +241,9 @@ "toggle": { "on": "开", "off": "关" - } + }, + "copy-tx-hash": "复制交易 Hash", + "copy-address": "复制地址" }, "notification-panel": { "title": "通知中心" @@ -341,6 +343,16 @@ "withdraw-alert": "提示:本补贴申请距离 Nervos DAO 规则允许的最近一个锁定周期仅剩下 {{epochs}} 个 epoch (约 {{hours}} 小时)。 如果您希望在本锁定周期取出,请及时提交取出申请,以确保取出申请能在本锁定周期结束之前上链。下一个锁定周期的结束时间预计为 {{nextLeftEpochs}} 个 epochs (约 {{days}} 天)。", "insufficient-period-alert-title": "Header 引用无效", "insufficient-period-alert-message": "交易只能引用已成熟的 Header(成熟期为 4 个 epochs)" + }, + "updates": { + "check-updates": "检查更新", + "downloading-update": "正在下载更新...", + "update-not-available": "已经在使用最新版本", + "release-notes": "Release Notes:", + "updates-found-do-you-want-to-update": "新版本 {{version}} 可供下载。要下载和安装吗?", + "download-update": "下载安装", + "updates-downloaded-about-to-quit-and-install": "下载完成。请安装新版本。", + "quit-and-install": "安装并重启应用" } } } diff --git a/packages/neuron-ui/src/services/chain.ts b/packages/neuron-ui/src/services/chain.ts index 8b2f08a3d0..e98285478a 100644 --- a/packages/neuron-ui/src/services/chain.ts +++ b/packages/neuron-ui/src/services/chain.ts @@ -1,11 +1,13 @@ import CKBCore from '@nervosnetwork/ckb-sdk-core' export const ckbCore = new CKBCore('') -export const { getBlockchainInfo, getTipHeader, getHeaderByNumber } = ckbCore.rpc +export const { getBlock, getBlockchainInfo, getTipHeader, getHeaderByNumber, calculateDaoMaximumWithdraw } = ckbCore.rpc export default { ckbCore, + getBlock, getBlockchainInfo, getTipHeader, getHeaderByNumber, + calculateDaoMaximumWithdraw, } diff --git a/packages/neuron-ui/src/services/remote/index.ts b/packages/neuron-ui/src/services/remote/index.ts index 4d0f24c5ae..5e717365d7 100644 --- a/packages/neuron-ui/src/services/remote/index.ts +++ b/packages/neuron-ui/src/services/remote/index.ts @@ -2,6 +2,7 @@ export * from './app' export * from './wallets' export * from './networks' export * from './transactions' +export * from './updater' const REMOTE_MODULE_NOT_FOUND = 'The remote module is not found, please make sure the UI is running inside the Electron App' @@ -81,6 +82,16 @@ export const openExternal = (url: string) => { } } +export const openContextMenu = (template: { label: string; click: Function }[]): void => { + if (!window.remote) { + window.alert(REMOTE_MODULE_NOT_FOUND) + } else { + const { Menu } = window.remote.require('electron') + const menu = Menu.buildFromTemplate(template) + menu.popup() + } +} + export default { getLocale, validateMnemonic, @@ -90,4 +101,5 @@ export default { showOpenDialog, getWinID, openExternal, + openContextMenu, } diff --git a/packages/neuron-ui/src/services/remote/updater.ts b/packages/neuron-ui/src/services/remote/updater.ts new file mode 100644 index 0000000000..ddf8207362 --- /dev/null +++ b/packages/neuron-ui/src/services/remote/updater.ts @@ -0,0 +1,11 @@ +import { apiMethodWrapper } from './apiMethodWrapper' + +export const checkForUpdates = apiMethodWrapper(api => () => api.checkForUpdates()) +export const downloadUpdate = apiMethodWrapper(api => () => api.downloadUpdate()) +export const installUpdate = apiMethodWrapper(api => () => api.quitAndInstall()) + +export default { + checkForUpdates, + downloadUpdate, + installUpdate, +} diff --git a/packages/neuron-ui/src/services/subjects.ts b/packages/neuron-ui/src/services/subjects.ts index dc9566c05e..3152c613b9 100644 --- a/packages/neuron-ui/src/services/subjects.ts +++ b/packages/neuron-ui/src/services/subjects.ts @@ -21,6 +21,7 @@ const SubjectConstructor = ( | 'connection-status-updated' | 'synced-block-number-updated' | 'command' + | 'app-updater-updated' ) => { return window.ipcRenderer ? { @@ -45,6 +46,7 @@ export const NetworkList = SubjectConstructor('network-list export const CurrentNetworkID = SubjectConstructor('current-network-id-updated') export const ConnectionStatus = SubjectConstructor('connection-status-updated') export const SyncedBlockNumber = SubjectConstructor('synced-block-number-updated') +export const AppUpdater = SubjectConstructor('app-updater-updated') export const Command = SubjectConstructor('command') export default { @@ -56,5 +58,6 @@ export default { CurrentNetworkID, ConnectionStatus, SyncedBlockNumber, + AppUpdater, Command, } diff --git a/packages/neuron-ui/src/states/initStates/index.ts b/packages/neuron-ui/src/states/initStates/index.ts index 9cac8f7eee..f393cf795e 100644 --- a/packages/neuron-ui/src/states/initStates/index.ts +++ b/packages/neuron-ui/src/states/initStates/index.ts @@ -3,12 +3,14 @@ import chain from './chain' import wallet from './wallet' import settings from './settings' import nervosDAO from './nervosDAO' +import updater from './updater' export * from './app' export * from './chain' export * from './wallet' export * from './settings' export * from './nervosDAO' +export * from './updater' const initStates = { app, @@ -16,6 +18,7 @@ const initStates = { wallet, settings, nervosDAO, + updater, } export default initStates diff --git a/packages/neuron-ui/src/states/initStates/updater.ts b/packages/neuron-ui/src/states/initStates/updater.ts new file mode 100644 index 0000000000..487b8769c6 --- /dev/null +++ b/packages/neuron-ui/src/states/initStates/updater.ts @@ -0,0 +1,8 @@ +const appUpdaterState: State.AppUpdater = { + checking: false, + downloadProgress: -1, + version: '', + releaseNotes: '', +} + +export default appUpdaterState diff --git a/packages/neuron-ui/src/states/stateProvider/reducer.ts b/packages/neuron-ui/src/states/stateProvider/reducer.ts index 54ed5a328b..6a71bd17bd 100644 --- a/packages/neuron-ui/src/states/stateProvider/reducer.ts +++ b/packages/neuron-ui/src/states/stateProvider/reducer.ts @@ -20,6 +20,8 @@ export enum NeuronWalletActions { UpdateSyncedBlockNumber = 'updateSyncedBlockNumber', // dao UpdateNervosDaoData = 'updateNervosDaoData', + // updater + UpdateAppUpdaterStatus = 'updateAppUpdaterStatus', } export enum AppActions { UpdateTransactionID = 'updateTransactionID', @@ -97,6 +99,12 @@ export const reducer = ( networks, wallets, }, + updater: { + checking: false, + downloadProgress: -1, + version: '', + releaseNotes: '', + }, } } case NeuronWalletActions.UpdateCodeHash: { @@ -224,6 +232,12 @@ export const reducer = ( }, } } + case NeuronWalletActions.UpdateAppUpdaterStatus: { + return { + ...state, + updater: payload, + } + } case NeuronWalletActions.UpdateNervosDaoData: { return { ...state, diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts index a8abf40fd5..227daa96b7 100644 --- a/packages/neuron-ui/src/types/App/index.d.ts +++ b/packages/neuron-ui/src/types/App/index.d.ts @@ -184,12 +184,20 @@ declare namespace State { records: NervosDAORecord[] } + interface AppUpdater { + checking: boolean + downloadProgress: number + version: string + releaseNotes: string + } + interface AppWithNeuronWallet { app: App chain: Chain settings: Settings wallet: Wallet nervosDAO: NervosDAO + updater: AppUpdater } } diff --git a/packages/neuron-ui/src/types/Subject/index.d.ts b/packages/neuron-ui/src/types/Subject/index.d.ts index b614627c1d..c141cf4b3f 100644 --- a/packages/neuron-ui/src/types/Subject/index.d.ts +++ b/packages/neuron-ui/src/types/Subject/index.d.ts @@ -29,4 +29,10 @@ declare namespace Subject { } type ConnectionStatus = boolean type BlockNumber = string + interface AppUpdater { + checking: boolean + downloadProgress: number + version: string + releaseNotes: string + } } diff --git a/packages/neuron-ui/src/utils/const.ts b/packages/neuron-ui/src/utils/const.ts index 8c41f126af..3c94a590f6 100644 --- a/packages/neuron-ui/src/utils/const.ts +++ b/packages/neuron-ui/src/utils/const.ts @@ -21,7 +21,7 @@ export const WITHDRAW_EPOCHS = 180 export const RUN_NODE_GUIDE_URL = 'https://docs.nervos.org/references/neuron-wallet-guide.html#1-run-a-ckb-mainnet-node' export const NERVOS_DAO_RFC_URL = - 'https://github.com/nervosnetwork/rfcs/tree/master/rfcs/0000-dao-deposit-withdraw/0000-dao-deposit-withdraw.md' + 'https://www.github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md' export enum ConnectionStatus { Online = 'online', diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json index a518cea225..8d9caf8255 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.25.1", + "version": "0.25.2", "private": true, "author": { "name": "Nervos Core Dev", @@ -34,8 +34,8 @@ ] }, "dependencies": { - "@nervosnetwork/ckb-sdk-core": "0.25.0-alpha.0", - "@nervosnetwork/ckb-sdk-utils": "0.25.0-alpha.0", + "@nervosnetwork/ckb-sdk-core": "0.25.0", + "@nervosnetwork/ckb-sdk-utils": "0.25.0", "bn.js": "4.11.8", "chalk": "2.4.2", "electron-log": "3.0.7", @@ -51,7 +51,7 @@ "uuid": "3.3.3" }, "devDependencies": { - "@nervosnetwork/ckb-types": "0.25.0-alpha.0", + "@nervosnetwork/ckb-types": "0.25.0", "@types/electron-devtools-installer": "2.2.0", "@types/elliptic": "6.4.9", "@types/sqlite3": "3.1.5", @@ -64,7 +64,7 @@ "electron-devtools-installer": "2.2.4", "electron-notarize": "0.1.1", "lint-staged": "9.2.5", - "neuron-ui": "0.25.1", + "neuron-ui": "0.25.2", "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 7316bfbfd4..59470a40c6 100644 --- a/packages/neuron-wallet/src/controllers/api.ts +++ b/packages/neuron-wallet/src/controllers/api.ts @@ -4,7 +4,7 @@ import env from 'env' import i18n from 'utils/i18n' import { popContextMenu } from './app/menu' import { showWindow } from './app/show-window' -import { TransactionsController, WalletsController, SyncController, NetworksController } from 'controllers' +import { TransactionsController, WalletsController, SyncController, NetworksController, UpdateController } from 'controllers' import { NetworkType, NetworkID, Network } from 'types/network' import NetworksService from 'services/networks' import WalletsService from 'services/wallets' @@ -158,21 +158,6 @@ export default class ApiController { return WalletsController.getAllAddresses(id) } - @MapApiResponse - public static async sendCapacity(params: { - id: string - walletID: string - items: { - address: string - capacity: string - }[] - password: string - fee: string - description?: string - }) { - return WalletsController.sendCapacity(params) - } - @MapApiResponse public static async sendTx(params: { walletID: string @@ -227,11 +212,6 @@ export default class ApiController { return WalletsController.withdrawFromDao(params) } - @MapApiResponse - public static async computeCycles(params: { walletID: string; capacities: string }) { - return WalletsController.computeCycles(params) - } - @MapApiResponse public static async updateAddressDescription(params: { walletID: string @@ -291,6 +271,7 @@ export default class ApiController { } // Dao + @MapApiResponse public static async getDaoCells( params: Controller.Params.GetDaoCellsParams @@ -298,7 +279,23 @@ export default class ApiController { return DaoController.getDaoCells(params) } - // settings + // Settings + + @MapApiResponse + public static async checkForUpdates() { + return new UpdateController().checkUpdates() + } + + @MapApiResponse + public static async downloadUpdate() { + return new UpdateController(false).downloadUpdate() + } + + @MapApiResponse + public static async quitAndInstall() { + return new UpdateController(false).quitAndInstall() + } + @MapApiResponse public static async clearCellCache() { await SyncController.stopSyncing() diff --git a/packages/neuron-wallet/src/controllers/app/menu.ts b/packages/neuron-wallet/src/controllers/app/menu.ts index f82ef2d9d0..b0e8a97e99 100644 --- a/packages/neuron-wallet/src/controllers/app/menu.ts +++ b/packages/neuron-wallet/src/controllers/app/menu.ts @@ -1,4 +1,4 @@ -import { app, shell, BrowserWindow, dialog, MenuItemConstructorOptions, clipboard, Menu, MenuItem, MessageBoxOptions, MessageBoxReturnValue } from 'electron' +import { app, shell, BrowserWindow, dialog, MenuItemConstructorOptions, clipboard, Menu, MessageBoxOptions, MessageBoxReturnValue } from 'electron' import { bech32Address, AddressPrefix, AddressType } from '@nervosnetwork/ckb-sdk-utils' import i18n from 'utils/i18n' import env from 'env' @@ -77,9 +77,12 @@ const updateApplicationMenu = (mainWindow: BrowserWindow | null) => { click: () => { showAbout() }, }, { - enabled: isMainWindow, label: i18n.t('application-menu.neuron.check-updates'), - click: (menuItem: MenuItem) => { new UpdateController().checkUpdates(menuItem) } + enabled: isMainWindow && !UpdateController.isChecking, + click: () => { + new UpdateController().checkUpdates() + navTo(URL.Preference) + } }, separator, { @@ -225,7 +228,11 @@ const updateApplicationMenu = (mainWindow: BrowserWindow | null) => { }) helpSubmenu.push({ label: i18n.t('application-menu.neuron.check-updates'), - click: (menuItem: MenuItem) => { new UpdateController().checkUpdates(menuItem) } + enabled: isMainWindow && !UpdateController.isChecking, + click: () => { + new UpdateController().checkUpdates() + navTo(URL.Preference) + } }) helpSubmenu.push({ id: 'about', diff --git a/packages/neuron-wallet/src/controllers/app/subscribe.ts b/packages/neuron-wallet/src/controllers/app/subscribe.ts index f0f9aa8bfd..0e7b3dd131 100644 --- a/packages/neuron-wallet/src/controllers/app/subscribe.ts +++ b/packages/neuron-wallet/src/controllers/app/subscribe.ts @@ -7,6 +7,7 @@ import { DebouncedCurrentNetworkIDSubject, DebouncedNetworkListSubject } from 'm import { SampledSyncedBlockNumberSubject, DebouncedConnectionStatusSubject } from 'models/subjects/node' import { WalletListSubject, CurrentWalletSubject } from 'models/subjects/wallets' import dataUpdateSubject from 'models/subjects/data-update' +import AppUpdaterSubject from 'models/subjects/app-updater' interface AppResponder { sendMessage: (channel: string, arg: any) => void @@ -52,4 +53,9 @@ export const subscribe = (dispatcher: AppResponder) => { dataUpdateSubject.next({ dataType: 'current-wallet', actionType: 'update' }) } }) + + AppUpdaterSubject.subscribe(params => { + dispatcher.updateMenu() + dispatcher.sendMessage('app-updater-updated', params) + }) } diff --git a/packages/neuron-wallet/src/controllers/update.ts b/packages/neuron-wallet/src/controllers/update.ts index 139027982e..acc274942a 100644 --- a/packages/neuron-wallet/src/controllers/update.ts +++ b/packages/neuron-wallet/src/controllers/update.ts @@ -1,76 +1,82 @@ import { dialog } from 'electron' import { autoUpdater, UpdateInfo } from 'electron-updater' import i18n from 'utils/i18n' +import AppUpdaterSubject from 'models/subjects/app-updater' export default class UpdateController { - sender: { enabled: boolean } | null + static isChecking = false // One instance is already running and checking - constructor() { + constructor(check: boolean = true) { autoUpdater.autoDownload = false - this.sender = null - this.bindEvents() + if (check && !UpdateController.isChecking) { + this.bindEvents() + } } - public checkUpdates(sender: { enabled: boolean }) { - this.sender = sender - this.sender.enabled = false - + public checkUpdates() { + UpdateController.isChecking = true autoUpdater.checkForUpdates() + + AppUpdaterSubject.next({ + checking: true, + downloadProgress: -1, + version: '', + releaseNotes: '' + }) + } + + public quitAndInstall() { + autoUpdater.quitAndInstall() + } + + public downloadUpdate() { + this.notify(0) + autoUpdater.downloadUpdate() } - bindEvents() { + private bindEvents() { autoUpdater.removeAllListeners() autoUpdater.on('error', error => { + UpdateController.isChecking = false + this.notify() + dialog.showErrorBox('Error', error == null ? 'unknown' : (error.stack || error).toString()) - this.enableSender() }) autoUpdater.on('update-available', (info: UpdateInfo) => { - const { version } = info - dialog - .showMessageBox({ - type: 'info', - title: version, - message: i18n.t('updater.updates-found-do-you-want-to-update', { version }), - buttons: [i18n.t('updater.update-now'), i18n.t('common.no')], - }) - .then(returnValue => { - if (returnValue.response === 0) { - autoUpdater.downloadUpdate() - } else { - this.enableSender() - } - }) + UpdateController.isChecking = false + this.notify(-1, info.version, info.releaseNotes as string) }) autoUpdater.on('update-not-available', () => { + UpdateController.isChecking = false + this.notify() + dialog.showMessageBox({ type: 'info', message: i18n.t('updater.update-not-available'), buttons: [i18n.t('common.ok')], }) - this.enableSender() + }) + + autoUpdater.on('download-progress', progress => { + this.notify(progress.percent / 100) }) autoUpdater.on('update-downloaded', () => { - dialog - .showMessageBox({ - type: 'info', - message: i18n.t('updater.updates-downloaded-about-to-quit-and-install'), - buttons: [i18n.t('common.ok')], - }) - .then(() => { - setImmediate(() => autoUpdater.quitAndInstall()) - }) + UpdateController.isChecking = false + this.notify(1) }) } - enableSender() { - if (this.sender) { - this.sender.enabled = true - } - this.sender = null + private notify(downloadProgress: number = -1, version = '', releaseNotes = '') { + AppUpdaterSubject.next({ + checking: UpdateController.isChecking, + downloadProgress, + version, + releaseNotes + }) } } diff --git a/packages/neuron-wallet/src/controllers/wallets.ts b/packages/neuron-wallet/src/controllers/wallets.ts index 51c6e8d555..d6c632e3da 100644 --- a/packages/neuron-wallet/src/controllers/wallets.ts +++ b/packages/neuron-wallet/src/controllers/wallets.ts @@ -321,56 +321,6 @@ export default class WalletsController { } } - public static async sendCapacity(params: { - id: string - walletID: string - items: { - address: string - capacity: string - }[] - password: string - fee: string - description?: string - }) { - if (!params) { - throw new IsRequired('Parameters') - } - // set default feeRate - let feeRate = '0' - if (!params.fee || params.fee === '0') { - feeRate = '1000' - } - - const isMainnet = NetworksService.getInstance().isMainnet() - params.items.forEach(item => { - if (isMainnet && !item.address.startsWith('ckb')) { - throw new MainnetAddressRequired(item.address) - } - - if (!isMainnet && !item.address.startsWith('ckt')) { - throw new TestnetAddressRequired(item.address) - } - - if (!this.verifyAddress(item.address)) { - throw new InvalidAddress(item.address) - } - }) - - 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, - } - } - public static async sendTx(params: { walletID: string tx: TransactionWithoutHash @@ -380,6 +330,7 @@ export default class WalletsController { if (!params) { throw new IsRequired('Parameters') } + const walletsService = WalletsService.getInstance() const hash = await walletsService.sendTx( params.walletID, @@ -405,6 +356,9 @@ export default class WalletsController { if (!params) { throw new IsRequired('Parameters') } + const addresses: string[] = params.items.map(i => i.address) + WalletsController.checkAddresses(addresses) + const walletsService = WalletsService.getInstance() const tx = await walletsService.generateTx( params.walletID, @@ -486,18 +440,6 @@ export default class WalletsController { } } - public static async computeCycles(params: { walletID: string; capacities: string }) { - if (!params) { - throw new IsRequired('Parameters') - } - const walletsService = WalletsService.getInstance() - const cycles = await walletsService.computeCycles(params.walletID, params.capacities) - return { - status: ResponseCode.Success, - result: cycles, - } - } - public static async updateAddressDescription({ walletID, address, @@ -522,6 +464,23 @@ export default class WalletsController { } } + private static checkAddresses = (addresses: string[]) => { + const isMainnet = NetworksService.getInstance().isMainnet() + addresses.forEach(address => { + if (isMainnet && !address.startsWith('ckb')) { + throw new MainnetAddressRequired(address) + } + + if (!isMainnet && !address.startsWith('ckt')) { + throw new TestnetAddressRequired(address) + } + + if (!WalletsController.verifyAddress(address)) { + throw new InvalidAddress(address) + } + }) + } + private static verifyAddress = (address: string): boolean => { if (typeof address !== 'string' || address.length !== 46) { return false diff --git a/packages/neuron-wallet/src/database/address/address-dao.ts b/packages/neuron-wallet/src/database/address/address-dao.ts index a748b18907..02fe31f29c 100644 --- a/packages/neuron-wallet/src/database/address/address-dao.ts +++ b/packages/neuron-wallet/src/database/address/address-dao.ts @@ -54,38 +54,35 @@ export default class AddressDao { // pendingBalance means balance of OutputStatus.Pending cells (sent from me, but not committed) // so the final balance is (liveBalance + sentBalance - pendingBalance) // balance is the balance of the cells those who don't hold data or type script - public static updateTxCountAndBalance = async ( - address: string, + public static updateTxCountAndBalances = async ( + addresses: string[], url: string = NodeService.getInstance().core.rpc.node.url ): Promise => { const all = AddressStore.getAll() const toUpdate = all.filter(value => { - return value.address === address + return addresses.includes(value.address) }) const others = all.filter(value => { - return value.address !== address + return !addresses.includes(value.address) }) - const txCount: number = await TransactionsService.getCountByAddressAndStatus(address, [ - TransactionStatus.Pending, - TransactionStatus.Success, - ], url) const lockUtils = new LockUtils(await LockUtils.systemScript(url)) - const result = await Promise.all( - toUpdate.map(async entity => { - const item = entity - item.txCount = txCount - const lockHashes: string[] = lockUtils.addressToAllLockHashes(item.address) - item.liveBalance = await CellsService.getBalance(lockHashes, OutputStatus.Live) - item.sentBalance = await CellsService.getBalance(lockHashes, OutputStatus.Sent) - item.pendingBalance = await CellsService.getBalance(lockHashes, OutputStatus.Pending) - item.balance = (BigInt(item.liveBalance) + BigInt(item.sentBalance)).toString() - return item - }) - ) + for (const addr of toUpdate) { + const txCount: number = await TransactionsService.getCountByAddressAndStatus(addr.address, [ + TransactionStatus.Pending, + TransactionStatus.Success, + ], url) + addr.txCount = txCount + + const lockHashes: string[] = lockUtils.addressToAllLockHashes(addr.address) + addr.liveBalance = await CellsService.getBalance(lockHashes, OutputStatus.Live) + addr.sentBalance = await CellsService.getBalance(lockHashes, OutputStatus.Sent) + addr.pendingBalance = await CellsService.getBalance(lockHashes, OutputStatus.Pending) + addr.balance = (BigInt(addr.liveBalance) + BigInt(addr.sentBalance)).toString() + } AddressStore.updateAll(toUpdate.concat(others)) - return result + return toUpdate } public static nextUnusedAddress(walletId: string, version: AddressVersion): Address | undefined { diff --git a/packages/neuron-wallet/src/database/chain/cleaner.ts b/packages/neuron-wallet/src/database/chain/cleaner.ts index e168a1bc07..5d134bf9f2 100644 --- a/packages/neuron-wallet/src/database/chain/cleaner.ts +++ b/packages/neuron-wallet/src/database/chain/cleaner.ts @@ -11,4 +11,4 @@ export default class ChainCleaner { await getConnection().getRepository(entity).clear() } } -} \ No newline at end of file +} diff --git a/packages/neuron-wallet/src/listeners/address.ts b/packages/neuron-wallet/src/listeners/address.ts index 845105fd7f..a69592d6f9 100644 --- a/packages/neuron-wallet/src/listeners/address.ts +++ b/packages/neuron-wallet/src/listeners/address.ts @@ -1,6 +1,4 @@ import { remote } from 'electron' -import { ReplaySubject } from 'rxjs' -import { bufferTime } from 'rxjs/operators' import AddressesUsedSubject, { AddressesWithURL } from 'models/subjects/addresses-used-subject' import AddressService from 'services/addresses' import { Address } from 'database/address/address-dao' @@ -12,23 +10,13 @@ const addressesUsedSubject = isRenderer ? remote.require('./models/subjects/addresses-used-subject').default.getSubject() : AddressesUsedSubject.getSubject() -// pipe not working directly -const bridge = new ReplaySubject(1000) -addressesUsedSubject.subscribe((params: AddressesWithURL) => { - bridge.next(params) -}) - // update txCount when addresses used export const register = () => { - bridge.pipe(bufferTime(1000)).subscribe(async (addressesList: AddressesWithURL[]) => { - if (addressesList.length === 0) { - return - } - const addresses = addressesList.map(list => list.addresses).reduce((acc, val) => acc.concat(val), []) - const url: string = addressesList[addressesList.length - 1].url - const uniqueAddresses = [...new Set(addresses)] - const addrs = await AddressService.updateTxCountAndBalances(uniqueAddresses, url) - const walletIds: string[] = addrs.map(addr => (addr as Address).walletId).filter((value, idx, a) => a.indexOf(value) === idx) + addressesUsedSubject.subscribe(async (address: AddressesWithURL) => { + const addrs = await AddressService.updateTxCountAndBalances(address.addresses, address.url) + const walletIds: string[] = addrs + .map(addr => (addr as Address).walletId) + .filter((value, idx, a) => a.indexOf(value) === idx) for (const id of walletIds) { const wallet = WalletService.getInstance().get(id) const accountExtendedPublicKey: AccountExtendedPublicKey = wallet.accountExtendedPublicKey() diff --git a/packages/neuron-wallet/src/locales/en.ts b/packages/neuron-wallet/src/locales/en.ts index a437ec3d14..c38c5a8d5d 100644 --- a/packages/neuron-wallet/src/locales/en.ts +++ b/packages/neuron-wallet/src/locales/en.ts @@ -143,9 +143,6 @@ export default { }, updater: { 'update-not-available': 'There are currently no updates available.', - 'updates-found-do-you-want-to-update': 'An update ({{version}}) is available, do you want to update now?', - 'update-now': 'Update now', - 'updates-downloaded-about-to-quit-and-install': 'Update downloaded. Neuron will quit and install the update...', }, common: { yes: 'Yes', diff --git a/packages/neuron-wallet/src/locales/zh.ts b/packages/neuron-wallet/src/locales/zh.ts index c77849b7c2..7dc69d8408 100644 --- a/packages/neuron-wallet/src/locales/zh.ts +++ b/packages/neuron-wallet/src/locales/zh.ts @@ -142,9 +142,6 @@ export default { }, updater: { 'update-not-available': '没有可供升级的新版本。', - 'updates-found-do-you-want-to-update': '有可供升级的新版本({{version}})。现在进行升级吗?', - 'update-now': '马上升级', - 'updates-downloaded-about-to-quit-and-install': '下载完成。Neuron 将退出并安装新版本...', }, common: { yes: '是', diff --git a/packages/neuron-wallet/src/models/dao-utils.ts b/packages/neuron-wallet/src/models/dao-utils.ts index 04d76060c5..07bb9bf5d5 100644 --- a/packages/neuron-wallet/src/models/dao-utils.ts +++ b/packages/neuron-wallet/src/models/dao-utils.ts @@ -45,6 +45,10 @@ export default class DaoUtils { return DaoUtils.daoScriptInfo } + static cleanInfo(): void { + DaoUtils.daoScriptInfo = undefined + } + static setDaoScript(info: SystemScript) { DaoUtils.daoScriptInfo = info DaoUtils.previousURL = NodeService.getInstance().core.rpc.node.url diff --git a/packages/neuron-wallet/src/models/lock-utils.ts b/packages/neuron-wallet/src/models/lock-utils.ts index 2ef84f8e51..1e01f42d57 100644 --- a/packages/neuron-wallet/src/models/lock-utils.ts +++ b/packages/neuron-wallet/src/models/lock-utils.ts @@ -17,17 +17,6 @@ export interface SystemScript { hashType: ScriptHashType } -const subscribed = (target: any, propertyName: string) => { - let value: any - Object.defineProperty(target, propertyName, { - get: () => value, - set: (info: { codeHash: string }) => { - SystemScriptSubject.next({ codeHash: info.codeHash }) - value = info - }, - }) -} - export default class LockUtils { systemScript: SystemScript @@ -35,10 +24,9 @@ export default class LockUtils { this.systemScript = systemScript } - @subscribed - static systemScriptInfo: SystemScript | undefined + private static systemScriptInfo: SystemScript | undefined - static previousURL: string | undefined + private static previousURL: string | undefined static async loadSystemScript(nodeURL: string): Promise { const core = new Core(nodeURL) @@ -75,6 +63,10 @@ export default class LockUtils { return LockUtils.systemScriptInfo } + static cleanInfo(): void { + LockUtils.systemScriptInfo = undefined + } + static setSystemScript(info: SystemScript) { LockUtils.systemScriptInfo = info LockUtils.previousURL = NodeService.getInstance().core.rpc.node.url diff --git a/packages/neuron-wallet/src/models/subjects/address-created-subject.ts b/packages/neuron-wallet/src/models/subjects/address-created-subject.ts index 63ff7968cd..1601899534 100644 --- a/packages/neuron-wallet/src/models/subjects/address-created-subject.ts +++ b/packages/neuron-wallet/src/models/subjects/address-created-subject.ts @@ -7,4 +7,8 @@ export default class AddressCreatedSubject { static getSubject() { return this.subject } + + static setSubject(subject: ReplaySubject) { + this.subject = subject + } } diff --git a/packages/neuron-wallet/src/models/subjects/app-updater.ts b/packages/neuron-wallet/src/models/subjects/app-updater.ts new file mode 100644 index 0000000000..c3bd1c55af --- /dev/null +++ b/packages/neuron-wallet/src/models/subjects/app-updater.ts @@ -0,0 +1,10 @@ +import { Subject } from 'rxjs' + +const AppUpdaterSubject = new Subject<{ + checking: boolean + downloadProgress: number, // -1: not started, 1: finished, 0~1: downloading + version: string, // "": no update, "v.x.y.z": version v.x.y.z available + releaseNotes: string +}>() + +export default AppUpdaterSubject diff --git a/packages/neuron-wallet/src/models/subjects/network-switch-subject.ts b/packages/neuron-wallet/src/models/subjects/network-switch-subject.ts new file mode 100644 index 0000000000..d1a4e89ef6 --- /dev/null +++ b/packages/neuron-wallet/src/models/subjects/network-switch-subject.ts @@ -0,0 +1,10 @@ +import { BehaviorSubject } from 'rxjs' +import { NetworkWithID } from 'types/network' + +export default class NetworkSwitchSubject { + static subject = new BehaviorSubject(undefined) + + static getSubject() { + return this.subject + } +} diff --git a/packages/neuron-wallet/src/models/subjects/wallet-created-subject.ts b/packages/neuron-wallet/src/models/subjects/wallet-created-subject.ts index f7afa4f591..653a951c38 100644 --- a/packages/neuron-wallet/src/models/subjects/wallet-created-subject.ts +++ b/packages/neuron-wallet/src/models/subjects/wallet-created-subject.ts @@ -1,13 +1,13 @@ -import { ReplaySubject } from 'rxjs' +import { Subject } from 'rxjs' export class WalletCreatedSubject { - static subject = new ReplaySubject(1) + static subject = new Subject() static getSubject() { return this.subject } - static setSubject(subject: ReplaySubject) { + static setSubject(subject: Subject) { this.subject = subject } } diff --git a/packages/neuron-wallet/src/services/addresses.ts b/packages/neuron-wallet/src/services/addresses.ts index 9d876969d9..e5daa731c7 100644 --- a/packages/neuron-wallet/src/services/addresses.ts +++ b/packages/neuron-wallet/src/services/addresses.ts @@ -87,13 +87,11 @@ export default class AddressService { ) } - public static updateTxCountAndBalances = async (addresses: string[], url: string = NodeService.getInstance().core.rpc.node.url) => { - let result: Address[] = [] - for (const address of addresses) { - const updatedAddress = await AddressDao.updateTxCountAndBalance(address, url) - result = result.concat(updatedAddress) - } - return result + public static updateTxCountAndBalances = async ( + addresses: string[], + url: string = NodeService.getInstance().core.rpc.node.url + ): Promise => { + return AddressDao.updateTxCountAndBalances(addresses, url) } // Generate both receiving and change addresses. diff --git a/packages/neuron-wallet/src/services/networks.ts b/packages/neuron-wallet/src/services/networks.ts index 0f499da69a..2a89f3973f 100644 --- a/packages/neuron-wallet/src/services/networks.ts +++ b/packages/neuron-wallet/src/services/networks.ts @@ -1,7 +1,6 @@ import Core from '@nervosnetwork/ckb-sdk-core' import { v4 as uuid } from 'uuid' -import { BehaviorSubject } from 'rxjs' -import { LackOfDefaultNetwork, DefaultNetworkUnremovable } from 'exceptions/network' +import { DefaultNetworkUnremovable, LackOfDefaultNetwork } from 'exceptions/network' import Store from 'models/store' @@ -9,8 +8,9 @@ import { Validate, Required } from 'decorators' import { UsedName, NetworkNotFound, InvalidFormat } from 'exceptions' import { NetworkListSubject, CurrentNetworkIDSubject } from 'models/subjects/networks' import { MAINNET_GENESIS_HASH, EMPTY_GENESIS_HASH, NetworkID, NetworkName, NetworkRemote, NetworksKey, NetworkType, Network, NetworkWithID } from 'types/network' +import NetworkSwitchSubject from 'models/subjects/network-switch-subject' -export const networkSwitchSubject = new BehaviorSubject(undefined) +const isMainProcess = process && process.type === 'browser' const presetNetworks: { selected: string, networks: NetworkWithID[] } = { selected: 'mainnet', @@ -40,7 +40,9 @@ export default class NetworksService extends Store { super('networks', 'index.json', JSON.stringify(presetNetworks)) const currentNetworkList = this.getAll() - NetworkListSubject.next({ currentNetworkList }) + if (isMainProcess) { + NetworkListSubject.next({ currentNetworkList }) + } const currentNetwork = this.getCurrent() if (currentNetwork) { @@ -48,12 +50,16 @@ export default class NetworksService extends Store { this.update(currentNetwork.id, {}) // Update to trigger chain/genesis hash refresh } - CurrentNetworkIDSubject.next({ currentNetworkID: currentNetwork.id }) - networkSwitchSubject.next(currentNetwork) + if (isMainProcess) { + CurrentNetworkIDSubject.next({ currentNetworkID: currentNetwork.id }) + NetworkSwitchSubject.getSubject().next(currentNetwork) + } } this.on(NetworksKey.List, async (_, currentNetworkList: NetworkWithID[] = []) => { - NetworkListSubject.next({ currentNetworkList }) + if (isMainProcess) { + NetworkListSubject.next({ currentNetworkList }) + } const currentID = this.getCurrentID() if (currentNetworkList.find(network => network.id === currentID)) { @@ -72,8 +78,10 @@ export default class NetworksService extends Store { if (!currentNetwork) { throw new NetworkNotFound(currentNetworkID) } - CurrentNetworkIDSubject.next({ currentNetworkID }) - networkSwitchSubject.next(currentNetwork) + if (isMainProcess) { + CurrentNetworkIDSubject.next({ currentNetworkID }) + NetworkSwitchSubject.getSubject().next(currentNetwork) + } }) } @@ -157,9 +165,9 @@ export default class NetworksService extends Store { this.updateAll(list) - if (this.getCurrentID() === id) { + if (this.getCurrentID() === id && isMainProcess) { CurrentNetworkIDSubject.next({ currentNetworkID: id }) - networkSwitchSubject.next(network) + NetworkSwitchSubject.getSubject().next(network) } } diff --git a/packages/neuron-wallet/src/services/sync/block-listener.ts b/packages/neuron-wallet/src/services/sync/block-listener.ts index 5aed6e8d2b..b592ff7088 100644 --- a/packages/neuron-wallet/src/services/sync/block-listener.ts +++ b/packages/neuron-wallet/src/services/sync/block-listener.ts @@ -31,11 +31,12 @@ export default class BlockListener { } public setLockHashes = (lockHashes: string[]) => { - this.lockHashes = lockHashes + const hashes = [...new Set(lockHashes)] + this.lockHashes = hashes if (!this.queue) { return } - this.queue.setLockHashes(lockHashes) + this.queue.setLockHashes(hashes) } public appendLockHashes = (lockHashes: string[]) => { diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts index e8bf140b93..4127b62fb3 100644 --- a/packages/neuron-wallet/src/services/wallets.ts +++ b/packages/neuron-wallet/src/services/wallets.ts @@ -34,7 +34,6 @@ const fileService = FileService.getInstance() const MODULE_NAME = 'wallets' const DEBOUNCE_TIME = 200 -const SECP_CYCLES = BigInt('1440000') export interface Wallet { id: string @@ -329,22 +328,6 @@ export default class WalletService { this.listStore.clear() } - public sendCapacity = async ( - walletID: string = '', - items: { - address: string - capacity: string - }[] = [], - password: string = '', - fee: string = '0', - feeRate: string = '0', - description: string = '' - ): Promise => { - const tx = await this.generateTx(walletID, items, fee, feeRate) - - return this.sendTx(walletID, tx, password, description) - } - public sendTx = async (walletID: string = '', tx: TransactionWithoutHash, password: string = '', description: string = '') => { const wallet = this.get(walletID) if (!wallet) { @@ -425,7 +408,8 @@ export default class WalletService { // update addresses txCount and balance const blake160s = TransactionsService.blake160sOfTx(tx) - const usedAddresses = blake160s.map(blake160 => LockUtils.blake160ToAddress(blake160)) + const prefix = NetworksService.getInstance().isMainnet() ? AddressPrefix.Mainnet : AddressPrefix.Testnet + const usedAddresses = blake160s.map(blake160 => LockUtils.blake160ToAddress(blake160, prefix)) AddressesUsedSubject.getSubject().next({ addresses: usedAddresses, url: core.rpc.node.url, @@ -767,24 +751,6 @@ export default class WalletService { return (BigInt(0x20) << BigInt(56)) + (length << BigInt(40)) + (index << BigInt(24)) + number } - public computeCycles = async (walletID: string = '', capacities: string): Promise => { - const wallet = this.get(walletID) - if (!wallet) { - throw new WalletNotFound(walletID) - } - - const addressInfos = this.getAddressInfos(walletID) - - const addresses: string[] = addressInfos.map(info => info.address) - - const lockHashes: string[] = new LockUtils(await LockUtils.systemScript()).addressesToAllLockHashes(addresses) - - const { inputs } = await CellsService.gatherInputs(capacities, lockHashes, '0') - const cycles = SECP_CYCLES * BigInt(inputs.length) - - return cycles.toString() - } - // path is a BIP44 full path such as "m/44'/309'/0'/0/0" public getAddressInfos = (walletID: string): AddressInterface[] => { const wallet = this.get(walletID) @@ -799,26 +765,6 @@ export default class WalletService { return AddressService.nextUnusedChangeAddress(walletId)!.address } - public signWitness = ( - lock: string | undefined, - privateKey: string, - txHash: string, - inputType: string | undefined = undefined, - outputType: string | undefined = undefined - ): string => { - const witnessArg: CKBComponents.WitnessArgs = { - lock, - inputType, - outputType, - } - const result = core.signWitnesses(privateKey)({ - transactionHash: txHash, - witnesses: [witnessArg] - }) - - return result[0] as string - } - // Derive all child private keys for specified BIP44 paths. public getPrivateKeys = (wallet: Wallet, paths: string[], password: string): PathAndPrivateKey[] => { const masterPrivateKey = wallet.loadKeystore().extendedPrivateKey(password) 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 19b8d04fe9..a23072413d 100644 --- a/packages/neuron-wallet/src/startup/sync-block-task/create.ts +++ b/packages/neuron-wallet/src/startup/sync-block-task/create.ts @@ -1,7 +1,6 @@ import { BrowserWindow } from 'electron' import { ReplaySubject } from 'rxjs' import path from 'path' -import { networkSwitchSubject } from 'services/networks' import { NetworkWithID } from 'types/network' import env from 'env' import AddressService from 'services/addresses' @@ -11,7 +10,12 @@ import DataUpdateSubject from 'models/subjects/data-update' import logger from 'utils/logger' import NodeService from 'services/node' import NetworksService from 'services/networks' -import { distinctUntilChanged } from 'rxjs/operators' +import { distinctUntilChanged, pairwise, startWith } from 'rxjs/operators' +import LockUtils from 'models/lock-utils' +import DaoUtils from 'models/dao-utils' +import NetworkSwitchSubject from 'models/subjects/network-switch-subject' +import { SyncedBlockNumberSubject } from 'models/subjects/node' +import BlockNumber from 'services/sync/block-number' export { genesisBlockHash } @@ -29,8 +33,13 @@ export interface DatabaseInitParams { // network switch or network connect const networkChange = async (network: NetworkWithID) => { await InitDatabase.getInstance().stopAndWait() + // clean LockUtils info and DaoUtils info + LockUtils.cleanInfo() + DaoUtils.cleanInfo() const info = await InitDatabase.getInstance().init(network) + const blockNumber = await (new BlockNumber()).getCurrent() + SyncedBlockNumberSubject.next(blockNumber.toString()) DataUpdateSubject.next({ dataType: 'transaction', actionType: 'update', @@ -50,11 +59,17 @@ const networkChange = async (network: NetworkWithID) => { export const databaseInitSubject = new ReplaySubject(1) -networkSwitchSubject.subscribe(async (network: NetworkWithID | undefined) => { - if (network) { - await networkChange(network) - } -}) +NetworkSwitchSubject + .getSubject() + .pipe( + startWith(undefined), + pairwise() + ) + .subscribe(async ([previousNetwork, network]: (NetworkWithID | undefined)[]) => { + if ((!previousNetwork && network) || (previousNetwork && network && network.id !== previousNetwork.id)) { + await networkChange(network) + } + }) NodeService .getInstance() @@ -72,8 +87,6 @@ NodeService const loadURL = `file://${path.join(__dirname, 'index.html')}` -export { networkSwitchSubject } - let syncBlockBackgroundWindow: BrowserWindow | null // create a background task to sync transactions diff --git a/packages/neuron-wallet/src/startup/sync-block-task/indexer.ts b/packages/neuron-wallet/src/startup/sync-block-task/indexer.ts index cef634c18e..d8da7ba553 100644 --- a/packages/neuron-wallet/src/startup/sync-block-task/indexer.ts +++ b/packages/neuron-wallet/src/startup/sync-block-task/indexer.ts @@ -5,6 +5,7 @@ import IndexerQueue, { LockHashInfo } from 'services/indexer/queue' import { Address } from 'database/address/address-dao' import initConnection from 'database/chain/ormconfig' +import DaoUtils from 'models/dao-utils' const { nodeService, addressCreatedSubject, walletCreatedSubject } = remote.require('./startup/sync-block-task/params') @@ -24,6 +25,10 @@ export const switchNetwork = async (nodeURL: string, genesisBlockHash: string, _ await indexerQueue.stopAndWait() } + // clean LockUtils info and DaoUtils info + LockUtils.cleanInfo() + DaoUtils.cleanInfo() + // disconnect old connection and connect to new database await initConnection(genesisBlockHash) // load lockHashes diff --git a/packages/neuron-wallet/src/startup/sync-block-task/params.ts b/packages/neuron-wallet/src/startup/sync-block-task/params.ts index 7339c00e94..0c31a686ea 100644 --- a/packages/neuron-wallet/src/startup/sync-block-task/params.ts +++ b/packages/neuron-wallet/src/startup/sync-block-task/params.ts @@ -3,8 +3,7 @@ import { AddressesUsedSubject } from 'models/subjects/addresses-used-subject' import AddressDbChangedSubject from 'models/subjects/address-db-changed-subject' import WalletCreatedSubject from 'models/subjects/wallet-created-subject' import AddressCreatedSubject from 'models/subjects/address-created-subject' - -export { networkSwitchSubject } from 'services/networks' +import NetworkSwitchSubject from 'models/subjects/network-switch-subject' export { genesisBlockHash } from './create' export { databaseInitSubject } from './create' @@ -14,3 +13,4 @@ export const addressesUsedSubject = AddressesUsedSubject.getSubject() export const addressDbChangedSubject = AddressDbChangedSubject.getSubject() export const walletCreatedSubject = WalletCreatedSubject.getSubject() export const addressCreatedSubject = AddressCreatedSubject.getSubject() +export const networkSwitchSubject = NetworkSwitchSubject.getSubject() diff --git a/packages/neuron-wallet/src/startup/sync-block-task/sync.ts b/packages/neuron-wallet/src/startup/sync-block-task/sync.ts index e0a05985a1..799332d83a 100644 --- a/packages/neuron-wallet/src/startup/sync-block-task/sync.ts +++ b/packages/neuron-wallet/src/startup/sync-block-task/sync.ts @@ -4,6 +4,7 @@ import LockUtils from 'models/lock-utils' import BlockListener from 'services/sync/block-listener' import { Address } from 'database/address/address-dao' import initConnection from 'database/chain/ormconfig' +import DaoUtils from 'models/dao-utils' const { nodeService, addressCreatedSubject, walletCreatedSubject } = remote.require('./startup/sync-block-task/params') @@ -31,6 +32,10 @@ export const switchNetwork = async (url: string, genesisBlockHash: string, _chai await blockListener.stopAndWait() } + // clean LockUtils info and DaoUtils info + LockUtils.cleanInfo() + DaoUtils.cleanInfo() + // disconnect old connection and connect to new database await initConnection(genesisBlockHash) // load lockHashes @@ -79,4 +84,4 @@ export const switchNetwork = async (url: string, genesisBlockHash: string, _chai }) blockListener.start() -} \ No newline at end of file +} diff --git a/packages/neuron-wallet/src/startup/sync-block-task/task.ts b/packages/neuron-wallet/src/startup/sync-block-task/task.ts index 31c0226e0f..0638917d53 100644 --- a/packages/neuron-wallet/src/startup/sync-block-task/task.ts +++ b/packages/neuron-wallet/src/startup/sync-block-task/task.ts @@ -8,11 +8,14 @@ import Utils from 'services/sync/utils' import { switchNetwork as syncSwitchNetwork } from './sync' import { switchNetwork as indexerSwitchNetwork } from './indexer' import { DatabaseInitParams } from './create' +import AddressCreatedSubject from 'models/subjects/address-created-subject' // register to listen address updates registerAddressListener() -const { addressesUsedSubject, databaseInitSubject } = remote.require('./startup/sync-block-task/params') +const { addressesUsedSubject, databaseInitSubject, addressCreatedSubject } = remote.require('./startup/sync-block-task/params') + +AddressCreatedSubject.setSubject(addressCreatedSubject) // pass to task a main process subject AddressesUsedSubject.setSubject(addressesUsedSubject) diff --git a/packages/neuron-wallet/tests/services/tx/indexer-transaction.test.ts b/packages/neuron-wallet/tests/services/tx/indexer-transaction.test.ts new file mode 100644 index 0000000000..b7d3f58ddf --- /dev/null +++ b/packages/neuron-wallet/tests/services/tx/indexer-transaction.test.ts @@ -0,0 +1,108 @@ +import TransactionEntity from '../../../src/database/chain/entities/transaction' +import OutputEntity from '../../../src/database/chain/entities/output' +import InputEntity from '../../../src/database/chain/entities/input' +import { getConnection } from 'typeorm' +import { TransactionStatus, ScriptHashType } from '../../../src/types/cell-types' +import initConnection from '../../../src/database/chain/ormconfig' +import IndexerTransaction from '../../../src/services/tx/indexer-transaction' +import { OutputStatus } from '../../../src/services/tx/params' + +describe('IndexerTransaction', () => { + const tx1: TransactionEntity = new TransactionEntity() + tx1.hash = '0x0001' + tx1.version = '0x0' + tx1.cellDeps = [] + tx1.headerDeps = [] + tx1.witnesses = [] + tx1.timestamp = (+new Date()).toString() + tx1.blockNumber = '0x1' + tx1.blockHash = '0x000001' + tx1.status = TransactionStatus.Success + tx1.inputs = [] + tx1.outputs = [] + + const tx1Input = new InputEntity() + tx1Input.outPointTxHash = '0x0000' + tx1Input.outPointIndex = '0' + tx1Input.since = '0' + tx1Input.lockHash = '0x' + tx1Input.capacity = '100' + tx1.inputs.push(tx1Input) + + const tx1Output: OutputEntity = new OutputEntity() + tx1Output.capacity = '100000000000' + tx1Output.outPointTxHash = tx1.hash + tx1Output.outPointIndex = '0' + tx1Output.lock = {"args":"0x94c1720fdff98d1c0100cfb0e7ae42e470b53019","codeHash":"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8","hashType":"type" as ScriptHashType} + tx1Output.status = 'live' + tx1Output.lockHash = '0xb58a6fa6f2c57ed45fbdfc7fcebd5c79575590ecf190b72fa6e6a767d57cb105' + tx1Output.hasData = false + + tx1.outputs.push(tx1Output) + + + const tx2 = new TransactionEntity() + tx2.hash = '0x0002' + tx2.version = '0x0' + tx2.cellDeps = [] + tx2.headerDeps = [] + tx2.witnesses = [] + tx2.timestamp = (+new Date()).toString() + tx2.blockNumber = '0x2' + tx2.blockHash = '0x000002' + tx2.status = TransactionStatus.Success + tx2.inputs = [] + tx2.outputs = [] + + const tx2Input = new InputEntity() + tx2Input.outPointTxHash = tx1.hash + tx2Input.outPointIndex = '0' + tx2Input.since = '0' + tx2Input.lockHash = null // tx1Output.lockHash + tx2Input.capacity = null // tx1Output.capacity + tx2.inputs.push(tx2Input) + + const tx2Output = new OutputEntity() + tx2Output.capacity = '50000000000' + tx2Output.outPointTxHash = tx2.hash + tx2Output.outPointIndex = '0' + tx2Output.lock = {"args":"0x94c1720fdff98d1c0100cfb0e7ae42e470b53019","codeHash":"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8","hashType":"type" as ScriptHashType} + tx2Output.status = 'live' + tx2Output.lockHash = '0xb58a6fa6f2c57ed45fbdfc7fcebd5c79575590ecf190b72fa6e6a767d57cb105' + tx2Output.hasData = false + tx2.outputs.push(tx2Output) + + beforeAll(async () => { + await initConnection('0x1234') + }) + + afterAll(async () => { + await getConnection().close() + }) + + it('updateInputLockHash', async () => { + await getConnection().manager.save([tx1, tx1Input, tx1Output, tx2, tx2Input, tx2Output]) + + await IndexerTransaction.updateInputLockHash(tx2Input.outPointTxHash!, tx2Input.outPointIndex!) + + await tx1Output.reload() + expect(tx1Output.status).toEqual(OutputStatus.Dead) + await tx2Input.reload() + expect(tx2Input.lockHash).toEqual(tx1Output.lockHash) + expect(tx2Input.capacity).toEqual(tx1Output.capacity) + + const inputCount: number = await getConnection() + .getRepository(InputEntity) + .createQueryBuilder('input') + .getCount() + + expect(inputCount).toEqual(2) + + const outputCount: number = await getConnection() + .getRepository(OutputEntity) + .createQueryBuilder('output') + .getCount() + + expect(outputCount).toEqual(2) + }) +}) diff --git a/packages/neuron-wallet/tests/services/wallets.test.ts b/packages/neuron-wallet/tests/services/wallets.test.ts index 0478b44101..7ffc0bbc03 100644 --- a/packages/neuron-wallet/tests/services/wallets.test.ts +++ b/packages/neuron-wallet/tests/services/wallets.test.ts @@ -3,6 +3,7 @@ import Keystore from '../../src/models/keys/keystore' import Keychain from '../../src/models/keys/keychain' import { mnemonicToSeedSync } from '../../src/models/keys/mnemonic' import { ExtendedPrivateKey, AccountExtendedPublicKey } from '../../src/models/keys/key' +import Core from '@nervosnetwork/ckb-sdk-core' describe('wallet service', () => { let walletService: WalletService @@ -164,14 +165,21 @@ describe('wallet service', () => { }) describe('sign witness', () => { - const witness: string = '' + const witness = { + lock: undefined, + inputType: undefined, + outputType: undefined, + } const privateKey: string = '0xe79f3207ea4980b7fed79956d5934249ceac4751a4fae01a0f7c4a96884bc4e3' const txHash = '0x00f5f31941964004d665a8762df8eb4fab53b5ef8437b7d34a38e018b1409054' - const expectedData = '0x5500000010000000550000005500000041000000aa6de884b0dd0378383cedddc39790b5cad66e42d5dc7655de728ee7eb3a53be071605d76641ad26766c6ed4864e67dbc2cd1526e006c9be7ccfa9b8cbf9e7c701' + const expectedData = ['0x5500000010000000550000005500000041000000aa6de884b0dd0378383cedddc39790b5cad66e42d5dc7655de728ee7eb3a53be071605d76641ad26766c6ed4864e67dbc2cd1526e006c9be7ccfa9b8cbf9e7c701'] it('success', () => { - const wallet = new WalletService() - const newWitness = wallet.signWitness(witness, privateKey, txHash) + const core = new Core('') + const newWitness = core.signWitnesses(privateKey)({ + witnesses: [witness], + transactionHash: txHash + }) expect(newWitness).toEqual(expectedData) }) }) diff --git a/yarn.lock b/yarn.lock index f766729822..1090fbfeb1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2059,36 +2059,36 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" -"@nervosnetwork/ckb-sdk-core@0.25.0-alpha.0": - version "0.25.0-alpha.0" - resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-sdk-core/-/ckb-sdk-core-0.25.0-alpha.0.tgz#55d3286edffa01cc29b6a1e05de07b46fcd5f605" - integrity sha512-7ygYqDYkdRYxn1gEWmQC1yYFvWCrNA6czsxgZwPI9LQdvzemMk+CgotXhT7uf5/zKTgrDDwFMTixqdkPMlL7Mw== +"@nervosnetwork/ckb-sdk-core@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-sdk-core/-/ckb-sdk-core-0.25.0.tgz#0a9852f78272297f39f3a722d8ab59c24d9afe7e" + integrity sha512-tD/mKge4zd56jOqvgeWb4703HOSF8ELhFpg0ieHhkV2uMoqqdW9eHyhdIisxPpW/y//RiS2Vxra8AL4uVBwRjQ== dependencies: - "@nervosnetwork/ckb-sdk-rpc" "0.25.0-alpha.0" - "@nervosnetwork/ckb-sdk-utils" "0.25.0-alpha.0" - "@nervosnetwork/ckb-types" "0.25.0-alpha.0" + "@nervosnetwork/ckb-sdk-rpc" "0.25.0" + "@nervosnetwork/ckb-sdk-utils" "0.25.0" + "@nervosnetwork/ckb-types" "0.25.0" -"@nervosnetwork/ckb-sdk-rpc@0.25.0-alpha.0": - version "0.25.0-alpha.0" - resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-sdk-rpc/-/ckb-sdk-rpc-0.25.0-alpha.0.tgz#8f5009c8c67ef9e720ece0fb7c07b96f39f5af93" - integrity sha512-ZefPXjKHBDaESvk1upCarTnGvDf8amsa6D8Xhaam02OVGUnTxt5n6WteniBQAsrORmI69PE5bfHesYo8sQRQVQ== +"@nervosnetwork/ckb-sdk-rpc@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-sdk-rpc/-/ckb-sdk-rpc-0.25.0.tgz#221cebe82da5c378c2e82fe8d8ca385f9ad4ca27" + integrity sha512-YwUmFd5YOR7OfWE0qxG8VWAmjf/lHxtW5U5ezYDecYV0Ey4qV70c/+yu3+o3koxSZwmZb2bJaEu5u4pXalyeHA== dependencies: - "@nervosnetwork/ckb-sdk-utils" "0.25.0-alpha.0" + "@nervosnetwork/ckb-sdk-utils" "0.25.0" axios "0.19.0" -"@nervosnetwork/ckb-sdk-utils@0.25.0-alpha.0": - version "0.25.0-alpha.0" - resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-sdk-utils/-/ckb-sdk-utils-0.25.0-alpha.0.tgz#8bce9f30b38ebe7776e756defb9f9111c7cfa8ad" - integrity sha512-VqXcC+C9LrOni+ljyripbzl2fAf6ILHFz//rJqLkWCDeAeT3kGs4xVBCAHRGbAetI9wyTg3+n6BuSLOtzT5goQ== +"@nervosnetwork/ckb-sdk-utils@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-sdk-utils/-/ckb-sdk-utils-0.25.0.tgz#cc4708ecad678efc069d46aff5fcbecbd0f3a7fb" + integrity sha512-fFQBcnQTvvp7eLblY0ylZQaFWrIE7d/r+T/K7EAAG+XVdQhQXDJ6sb59kz7l0MWI/j4g9X5wZiFx/65VjotAJw== dependencies: - "@nervosnetwork/ckb-types" "0.25.0-alpha.0" + "@nervosnetwork/ckb-types" "0.25.0" blake2b-wasm "1.1.7" elliptic "6.5.1" -"@nervosnetwork/ckb-types@0.25.0-alpha.0": - version "0.25.0-alpha.0" - resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-types/-/ckb-types-0.25.0-alpha.0.tgz#7aafb57e99525d54f632601055fb66d4f3f0f9b1" - integrity sha512-xtSCjJkQjvuMcDqoMRCBtVc8W5jcsMlSwi7hAmDp5CIwNlMnU+xeUCTdnHX2Kt8vijhtUcIH90uEsdtzAPpWjQ== +"@nervosnetwork/ckb-types@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-types/-/ckb-types-0.25.0.tgz#8e8292e672954420b1068c0a99b7b652487a9ff9" + integrity sha512-KSrewk0Nz2LEUPJQOjGYl6maoHxDwsCpAMoN8gCznyei77BiD6k8Mq9xh+XoetZszk4SCjttYMUWEP+z/Lvmjw== "@nodelib/fs.scandir@2.1.3": version "2.1.3" @@ -2920,10 +2920,10 @@ "@types/prop-types" "*" "@types/react" "*" -"@types/react-router-dom@4.3.5": - version "4.3.5" - resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.5.tgz#72f229967690c890d00f96e6b85e9ee5780db31f" - integrity sha512-eFajSUASYbPHg2BDM1G8Btx+YqGgvROPIg6sBhl3O4kbDdYXdFdfrgQFf/pcBuQVObjfT9AL/dd15jilR5DIEA== +"@types/react-router-dom@5.1.3": + version "5.1.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.3.tgz#b5d28e7850bd274d944c0fbbe5d57e6b30d71196" + integrity sha512-pCq7AkOvjE65jkGS5fQwQhvUp4+4PVD9g39gXLZViP2UqFiFzsEpB3PKf0O6mdbKsewSK8N14/eegisa/0CwnA== dependencies: "@types/history" "*" "@types/react" "*" @@ -14070,23 +14070,23 @@ react-resize-detector@^4.0.5: raf-schd "^4.0.2" resize-observer-polyfill "^1.5.1" -react-router-dom@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.0.1.tgz#ee66f4a5d18b6089c361958e443489d6bab714be" - integrity sha512-zaVHSy7NN0G91/Bz9GD4owex5+eop+KvgbxXsP/O+iW1/Ln+BrJ8QiIR5a6xNPtrdTvLkxqlDClx13QO1uB8CA== +react-router-dom@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" + integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew== dependencies: "@babel/runtime" "^7.1.2" history "^4.9.0" loose-envify "^1.3.1" prop-types "^15.6.2" - react-router "5.0.1" + react-router "5.1.2" tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-router@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.0.1.tgz#04ee77df1d1ab6cb8939f9f01ad5702dbadb8b0f" - integrity sha512-EM7suCPNKb1NxcTZ2LEOWFtQBQRQXecLxVpdsP4DW4PbbqYWeRiLyV/Tt1SdCrvT2jcyXAXmVTmzvSzrPR63Bg== +react-router@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418" + integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A== dependencies: "@babel/runtime" "^7.1.2" history "^4.9.0"