diff --git a/CHANGELOG.md b/CHANGELOG.md index 402299c135..760656b3ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,35 @@ +## [0.24.5](https://github.com/nervosnetwork/neuron/compare/v0.24.4...v0.24.5) (2019-11-14) + + +### Bug Fixes + +* **neuron-ui:** add decimal validation on deposit value ([#1093](https://github.com/nervosnetwork/neuron/issues/1093)) ([61eab4f](https://github.com/nervosnetwork/neuron/commit/61eab4f)) +* current block number should be -1 if not start ([543cdd0](https://github.com/nervosnetwork/neuron/commit/543cdd0)) +* dao i18n ([4f41d9d](https://github.com/nervosnetwork/neuron/commit/4f41d9d)) + + +### Features + +* **neuron-ui:** add an alert when past epochs are less than 5 ([15d0cc8](https://github.com/nervosnetwork/neuron/commit/15d0cc8)) +* **neuron-ui:** add border color on dao records ([96b4ef6](https://github.com/nervosnetwork/neuron/commit/96b4ef6)) +* **neuron-ui:** rename APY to APC ([c337a21](https://github.com/nervosnetwork/neuron/commit/c337a21)) +* **neuron-ui:** rename interest to compensation ([e6d6060](https://github.com/nervosnetwork/neuron/commit/e6d6060)) +* Regenerate addresses if necessary on launch ([9988a13](https://github.com/nervosnetwork/neuron/commit/9988a13)) +* **neuron-ui:** add content in deposit notice ([dd0c7dc](https://github.com/nervosnetwork/neuron/commit/dd0c7dc)) +* **neuron-ui:** add global apy estimation ([#1092](https://github.com/nervosnetwork/neuron/issues/1092)) ([dc82cbb](https://github.com/nervosnetwork/neuron/commit/dc82cbb)) +* **neuron-ui:** add more translation of nervos dao ([3c62598](https://github.com/nervosnetwork/neuron/commit/3c62598)) +* **neuron-ui:** adjust the order of dao records ([e8398b6](https://github.com/nervosnetwork/neuron/commit/e8398b6)) +* **neuron-ui:** remove the user-confirmation from phase2 of nervos dao ([7e9e9e3](https://github.com/nervosnetwork/neuron/commit/7e9e9e3)) +* **neuron-ui:** rename deposit record to deposit receipt ([4c587a8](https://github.com/nervosnetwork/neuron/commit/4c587a8)) +* **neuron-ui:** use the same style of activity record on deposit record. ([99f77aa](https://github.com/nervosnetwork/neuron/commit/99f77aa)) +* Remove address sqlite db ([6f95340](https://github.com/nervosnetwork/neuron/commit/6f95340)) +* Send address db changed event when address store ([617a9a3](https://github.com/nervosnetwork/neuron/commit/617a9a3)) +* **neuron-ui:** remove redundant error messages ([c67aa36](https://github.com/nervosnetwork/neuron/commit/c67aa36)) +* **neuron-ui:** update the info and message of nervos dao ([cd44e43](https://github.com/nervosnetwork/neuron/commit/cd44e43)) +* **neuron-ui:** use the same style of overview on nervos dao overview ([20bbf33](https://github.com/nervosnetwork/neuron/commit/20bbf33)) + + + ## [0.24.4](https://github.com/nervosnetwork/neuron/compare/v0.24.3...v0.24.4) (2019-11-12) diff --git a/lerna.json b/lerna.json index 33a4f30e1f..a4bf763061 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "0.24.4", + "version": "0.24.5", "npmClient": "yarn", "useWorkspaces": true } diff --git a/ormconfig-address.json b/ormconfig-address.json deleted file mode 100644 index 89b3d7e5ca..0000000000 --- a/ormconfig-address.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "type": "sqlite", - "synchronize": false, - "migrationsRun": true, - "logging": true, - "database": "address-dev.sqlite", - "entities": [ - "packages/neuron-wallet/dist/database/address/entities/**/*.js" - ], - "migrations": [ - "packages/neuron-wallet/dist/database/address/migrations/**/*.js" - ], - "subscribers": [ - "packages/neuron-wallet/dist/database/address/subscriber/**/*.js" - ], - "cli": { - "entitiesDir": "packages/neuron-wallet/src/database/address/entities", - "migrationsDir": "packages/neuron-wallet/src/database/address/migrations", - "subscribersDir": "packages/neuron-wallet/src/database/address/subscriber" - } -} diff --git a/package.json b/package.json index e4d3b62edf..9768d96911 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "neuron", "productName": "Neuron", "description": "CKB Neuron Wallet", - "version": "0.24.4", + "version": "0.24.5", "private": true, "author": { "name": "Nervos Core Dev", @@ -34,8 +34,7 @@ "test:e2e": "yarn build && ./scripts/copy-ui-files.sh && lerna run --parallel test:e2e", "lint": "lerna run --stream lint", "postinstall": "lerna run rebuild:nativemodules", - "db:chain": "node ./node_modules/.bin/typeorm", - "db:address": "node ./node_modules/.bin/typeorm --config ormconfig-address.json" + "db:chain": "node ./node_modules/.bin/typeorm" }, "husky": { "hooks": { diff --git a/packages/neuron-ui/package.json b/packages/neuron-ui/package.json index 1c8eab3e4c..b10015055d 100644 --- a/packages/neuron-ui/package.json +++ b/packages/neuron-ui/package.json @@ -1,6 +1,6 @@ { "name": "neuron-ui", - "version": "0.24.4", + "version": "0.24.5", "private": true, "author": { "name": "Nervos Core Dev", diff --git a/packages/neuron-ui/src/components/CustomRows/DAORecordRow.tsx b/packages/neuron-ui/src/components/CustomRows/DAORecordRow.tsx index 80ee774f10..a847a68b62 100644 --- a/packages/neuron-ui/src/components/CustomRows/DAORecordRow.tsx +++ b/packages/neuron-ui/src/components/CustomRows/DAORecordRow.tsx @@ -1,8 +1,9 @@ -import React, { useEffect, useState } from 'react' +import React, { useEffect, useState, useMemo } from 'react' import { DefaultButton } from 'office-ui-fabric-react' import { useTranslation } from 'react-i18next' import { ckbCore, getBlockByNumber } from 'services/chain' -import calculateAPY from 'utils/calculateAPY' +import { showMessage } from 'services/remote' +import calculateAPC from 'utils/calculateAPC' import { shannonToCKBFormatter, uniformTimeFormatter, localNumberFormatter } from 'utils/formatters' import calculateClaimEpochNumber from 'utils/calculateClaimEpochNumber' import { epochParser } from 'utils/parsers' @@ -21,12 +22,14 @@ const DAORecord = ({ depositOutPoint, epoch, withdraw, + connectionStatus, }: State.NervosDAORecord & { actionLabel: string onClick: any tipBlockNumber: string epoch: string withdraw: string | null + connectionStatus: 'online' | 'offline' }) => { const [t] = useTranslation() const [withdrawingEpoch, setWithdrawingEpoch] = useState('') @@ -34,6 +37,13 @@ const DAORecord = ({ useEffect(() => { if (!depositOutPoint) { + getBlockByNumber(BigInt(blockNumber)) + .then(b => { + setDepositEpoch(b.header.epoch) + }) + .catch((err: Error) => { + console.error(err) + }) return } const depositBlockNumber = ckbCore.utils.bytesToHex(ckbCore.utils.hexToBytes(daoData).reverse()) @@ -54,13 +64,13 @@ const DAORecord = ({ }) }, [daoData, depositOutPoint, blockNumber]) - const interest = BigInt(withdraw || capacity) - BigInt(capacity) + const compensation = BigInt(withdraw || capacity) - BigInt(capacity) let ready = false let metaInfo = 'Ready' if (!depositOutPoint) { const duration = BigInt(tipBlockNumber) - BigInt(blockNumber) - metaInfo = t('nervos-dao.interest-accumulated', { + metaInfo = t('nervos-dao.compensation-accumulated', { blockNumber: localNumberFormatter(duration >= BigInt(0) ? duration : 0), }) } else { @@ -81,18 +91,42 @@ const DAORecord = ({ } } + const onActionClick = useMemo(() => { + const currentEpochInfo = epochParser(epoch) + const thresholdEpoch = withdrawingEpoch || depositEpoch + if (thresholdEpoch) { + const thresholdEpochInfo = epochParser(thresholdEpoch) + if (thresholdEpochInfo.number + BigInt(4) >= currentEpochInfo.number) { + return () => + showMessage( + { + title: t('nervos-dao.insufficient-period-alert-title'), + message: t('nervos-dao.insufficient-period-alert-title'), + detail: t('nervos-dao.insufficient-period-alert-message'), + }, + () => {} + ) + } + } + return onClick + }, [onClick, epoch, depositEpoch, withdrawingEpoch, t]) + return ( -
+
-
{interest >= BigInt(0) ? `${shannonToCKBFormatter(interest.toString()).toString()} CKB` : ''}
+
+ {compensation >= BigInt(0) + ? `${depositOutPoint ? '' : '~'}${shannonToCKBFormatter(compensation.toString()).toString()} CKB` + : ''} +
{`${shannonToCKBFormatter(capacity)} CKB`}
- {`APY: ~${calculateAPY(interest.toString(), capacity, `${Date.now() - +timestamp}`)}%`} + {`APC: ~${calculateAPC(compensation.toString(), capacity, `${Date.now() - +timestamp}`)}%`} {uniformTimeFormatter(+timestamp)} {metaInfo}
diff --git a/packages/neuron-ui/src/components/CustomRows/daoRecordRow.module.scss b/packages/neuron-ui/src/components/CustomRows/daoRecordRow.module.scss index 7a2bede64c..e0ad25f66c 100644 --- a/packages/neuron-ui/src/components/CustomRows/daoRecordRow.module.scss +++ b/packages/neuron-ui/src/components/CustomRows/daoRecordRow.module.scss @@ -1,10 +1,13 @@ .daoRecord { display: flex; flex-direction: column; - border: 1px solid #000; - border-radius: 5px; - margin: 10px 0; - padding: 5px 15px; + border-radius: 2px; + margin: 15px 0; + border-right: 1px solid #eee; + border-left: 5px solid green; + box-sizing: border-box; + padding: 5px 10px; + box-shadow: 0 1px 5px 1px rgba(0, 0, 0, 0.14); .primaryInfo, .secondaryInfo { @@ -32,4 +35,7 @@ color: #666; } + &.isClaim { + border-left-color: blue; + } } diff --git a/packages/neuron-ui/src/components/ImportKeystore/index.tsx b/packages/neuron-ui/src/components/ImportKeystore/index.tsx index e1c908c4a0..bf6b9a1443 100644 --- a/packages/neuron-ui/src/components/ImportKeystore/index.tsx +++ b/packages/neuron-ui/src/components/ImportKeystore/index.tsx @@ -105,12 +105,12 @@ const ImportKeystore = (props: React.PropsWithoutRef maxLength) { return t(`messages.codes.${ErrorCode.FieldTooLong}`, { - fieldName: key, - fieldValue: key === 'password' ? '' : text, + fieldName: `keystore-${key}`, + fieldValue: '', length: maxLength, }) } diff --git a/packages/neuron-ui/src/components/NervosDAO/DepositDialog.tsx b/packages/neuron-ui/src/components/NervosDAO/DepositDialog.tsx index 43e1d8a8b6..216d5ba756 100644 --- a/packages/neuron-ui/src/components/NervosDAO/DepositDialog.tsx +++ b/packages/neuron-ui/src/components/NervosDAO/DepositDialog.tsx @@ -61,7 +61,7 @@ const DepositDialog = ({ - {t('nervos-dao.notice')} + {`${t('nervos-dao.notice')}:`} {t('nervos-dao.deposit-terms') .split('\n') diff --git a/packages/neuron-ui/src/components/NervosDAO/WithdrawDialog.tsx b/packages/neuron-ui/src/components/NervosDAO/WithdrawDialog.tsx index 77c77b2760..01fce07c2d 100644 --- a/packages/neuron-ui/src/components/NervosDAO/WithdrawDialog.tsx +++ b/packages/neuron-ui/src/components/NervosDAO/WithdrawDialog.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react' -import { Dialog, DialogFooter, DefaultButton, PrimaryButton, DialogType } from 'office-ui-fabric-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' @@ -65,6 +65,16 @@ const WithdrawDialog = ({ blocks: localNumberFormatter(currentEpochInfo.length - currentEpochInfo.index), days: localNumberFormatter(epochs / BigInt(6)), }) + + const alert = + epochs <= BigInt(5) + ? t('nervos-dao.withdraw-alert', { + epochs, + nextLeftEpochs: epochs + BigInt(180), + days: (epochs + BigInt(180)) / BigInt(6), + }) + : '' + return ( ) - }, [epoch]) + }, [epoch, globalAPC]) + + const lockAndFreeProperties = [ + { + label: t('nervos-dao.free'), + value: `${shannonToCKBFormatter(`${free}`)} CKB`, + }, + { + label: t('nervos-dao.locked'), + value: `${shannonToCKBFormatter(`${locked}`)} CKB`, + }, + ] return ( <> @@ -259,19 +322,12 @@ const NervosDAO = ({ - - {`${t('nervos-dao.free')}: `} - {`${shannonToCKBFormatter(`${free}`)} CKB`} - - - {`${t('nervos-dao.locked')}: `} - {`${shannonToCKBFormatter(`${locked}`)} CKB`} - + setShowDepositDialog(true)} /> >) => { const { t } = useTranslation() @@ -234,7 +235,7 @@ const Send = ({ )} diff --git a/packages/neuron-ui/src/components/Transaction/index.tsx b/packages/neuron-ui/src/components/Transaction/index.tsx index 8aa66a5740..1c9136507f 100644 --- a/packages/neuron-ui/src/components/Transaction/index.tsx +++ b/packages/neuron-ui/src/components/Transaction/index.tsx @@ -254,7 +254,7 @@ const Transaction = () => { { label: t('transaction.transaction-hash'), value: transaction.hash || 'none' }, { label: t('transaction.block-number'), - value: localNumberFormatter(transaction.blockNumber) || 'none', + value: transaction.blockNumber ? localNumberFormatter(transaction.blockNumber) : 'none', }, { label: t('transaction.date'), diff --git a/packages/neuron-ui/src/containers/Main/hooks.ts b/packages/neuron-ui/src/containers/Main/hooks.ts index 7bea9fe347..568df8775a 100644 --- a/packages/neuron-ui/src/containers/Main/hooks.ts +++ b/packages/neuron-ui/src/containers/Main/hooks.ts @@ -40,6 +40,7 @@ export const useSyncChainData = ({ chainURL, dispatch }: { chainURL: string; dis payload: { tipBlockNumber: `${BigInt(header.number)}`, tipBlockHash: header.hash, + tipBlockTimestamp: +header.timestamp, chain: chainInfo.chain, difficulty: `${BigInt(chainInfo.difficulty)}`, epoch: chainInfo.epoch, diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index fc7c0ceb05..eb07aa5946 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -270,7 +270,8 @@ "mnemonic": "Mnemonic", "keystore-path": "Keystore File", "keystore-name": "Wallet name", - "keystore-password": "Password" + "keystore-password": "Password", + "deposit": "Deposit" }, "codes": { "-3": "", @@ -312,8 +313,8 @@ "free": "Free", "locked": "Locked", "deposit": "Deposit", - "deposit-records": "Deposit Records", - "apy": "APY", + "deposit-receipts": "Deposit Receipts", + "apc": "APC", "deposit-at": "Deposit at {{time}}", "claim": "Claim", "withdraw": "Withdraw", @@ -324,15 +325,17 @@ "cancel": "Cancel", "proceed": "Proceed", "deposit-value": "Deposit", - "interest": "Interest", - "yield": "Yield", + "compensation": "Compensation", "notice-wait-time": "Notice: You need to wait {{epochs}} epochs {{blocks}} blocks(~{{days}} days) to claim the saving.", - "deposit-terms": "Nervos DAO is a system layer decentralized infrastructure. Your saving here is secure.\nAccording to the Nervos DAO protocol, you need at least 180 epochs to withdraw your deposit", + "deposit-terms": "Nervos DAO needs 102 CKB for receipt storage, which is not compensation-bearing.\nNervos DAO is a system layer decentralized infrastructure. Your saving here is secure.\nAccording to the Nervos DAO protocol, you need at least 180 epochs to withdraw your deposit.", "deposited-action-label": "Withdraw", "withdrawing-action-label": "Claim", "minimal-fee-required": "The minimum deposit capacity is {{minimal}} CKB", - "interest-accumulated": "{{blockNumber}} blocks interest accumulated", - "blocks-left": "{{epochs}} epochs {{blocks}} blocks left(~{{days}} days)" + "compensation-accumulated": "{{blockNumber}} blocks compensation accumulated", + "blocks-left": "{{epochs}} epochs {{blocks}} blocks left(~{{days}} days)", + "withdraw-alert": "Alert: these are only {{epochs}} epochs left before the next start withdrawing epoch number conforming to Nervos DAO, and it is possible that you have to do the withdraw after the next period(~{{days}}) due to the jam on CKB.", + "insufficient-period-alert-title": "Insufficient Period", + "insufficient-period-alert-message": "Nervos DAO needs at least 4 epochs to handle your request." } } } diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 9decdf7c3e..d79846dac7 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -270,7 +270,8 @@ "mnemonic": "助记词", "keystore-path": "Keystore 文件", "keystore-name": "钱包名称", - "keystore-password": "密码" + "keystore-password": "密码", + "deposit": "存入金额" }, "codes": { "-3": "", @@ -312,11 +313,11 @@ "free": "当前可用", "locked": "已锁定", "deposit": "存入", - "deposit-records": "存款记录", - "apy": "预期年化利率", + "deposit-receipts": "存款凭证", + "apc": "年化锁定补贴率", "deposit-at": "存入于{{time}}", - "claim": "Claim", - "withdraw": "Withdraw", + "claim": "取款", + "withdraw": "结算", "fee": "手续费", "deposit-to-nervos-dao": "存入 Nervos DAO", "withdraw-from-nervos-dao": "从 Nervos DAO 取出", @@ -324,15 +325,17 @@ "cancel": "取消", "proceed": "继续", "deposit-value": "存款", - "interest": "利息", - "yield": "Yield", + "compensation": "锁定补贴", "notice-wait-time": "注意: 您需要等待 {{epochs}} epochs {{blocks}} 区块(~{{days}}天)完成最终取款。", - "deposit-terms": "Nervos DAO is a system layer decentralized infrastructure. Your saving here is secure.\nAccording to the Nervos DAO protocol, you need at least 180 epochs to withdraw your deposit", - "deposited-action-label": "Withdraw", - "withdrawing-action-label": "Claim", + "deposit-terms": "存入 NervosDAO 的资产中需要 102 CKB 作为存款凭证的存储,这部分 CKB 是无法产生锁定补贴的。\nNervos DAO 是一个系统层面的去中心化底层设施。您在其中存款是十分安全的。\n根据 Nervos DAO 的协议, 您需要等待至少 180 个 epochs 才能取回您的存款。", + "deposited-action-label": "结算", + "withdrawing-action-label": "取款", "minimal-fee-required": "存入金额应不少于 {{minimal}} CKB", - "interest-accumulated": "已累计 {{blockNumber}} 个块的利息", - "blocks-left": " 还需等待 {{epochs}} epochs {{blocks}} 个块(~{{days}} 天)" + "compensation-accumulated": "已累计 {{blockNumber}} 个块的锁定补贴", + "blocks-left": " 还需等待 {{epochs}} epochs {{blocks}} 个块(~{{days}} 天)", + "withdraw-alert": "风险提示:距离 NervosDAO 规定的最近一个允许提现 epoch 仅剩下 {{epochs}} 个 epoch,存在提现交易拥堵无法上链从而导致只能在下一个提现周期(约 {{days}} 天)的风险", + "insufficient-period-alert-title": "未达到要求周期", + "insufficient-period-alert-message": "Nervos DAO 要求您在至少 4 个 epochs 后执行此操作" } } } diff --git a/packages/neuron-ui/src/states/initStates/app.ts b/packages/neuron-ui/src/states/initStates/app.ts index 690b1e9b43..1eabdd4b18 100644 --- a/packages/neuron-ui/src/states/initStates/app.ts +++ b/packages/neuron-ui/src/states/initStates/app.ts @@ -3,6 +3,7 @@ import { CapacityUnit } from 'utils/const' const appState: State.App = { tipBlockNumber: '', tipBlockHash: '', + tipBlockTimestamp: 0, chain: '', difficulty: '', epoch: '', diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts index 1a44e0961e..a2a155b70f 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts @@ -271,9 +271,17 @@ export const backupWallet = (params: Controller.BackupWalletParams) => (dispatch export const updateNervosDaoData = (walletID: Controller.GetNervosDaoDataParams) => (dispatch: StateDispatch) => { getNervosDaoData(walletID).then(res => { if (res.status === 1) { + const withdrawList = res.result + .filter((r: State.NervosDAORecord) => !r.depositOutPoint) + .sort((r1: State.NervosDAORecord, r2: State.NervosDAORecord) => +r2.timestamp - +r1.timestamp) + + const claimList = res.result + .filter((r: State.NervosDAORecord) => r.depositOutPoint) + .sort((r1: State.NervosDAORecord, r2: State.NervosDAORecord) => +r2.timestamp - +r1.timestamp) + dispatch({ type: NeuronWalletActions.UpdateNervosDaoData, - payload: { records: res.result }, + payload: { records: [...claimList, ...withdrawList] }, }) } else { addNotification(failureResToNotification(res))(dispatch) diff --git a/packages/neuron-ui/src/tests/calculation/calculateGlobalAPC/fixtures.ts b/packages/neuron-ui/src/tests/calculation/calculateGlobalAPC/fixtures.ts new file mode 100644 index 0000000000..dea8b027bb --- /dev/null +++ b/packages/neuron-ui/src/tests/calculation/calculateGlobalAPC/fixtures.ts @@ -0,0 +1,12 @@ +export default { + 'return 0 if the genesis block is not loaded': { + currentTime: Date.now(), + genesisTime: undefined, + expectAPC: 0, + }, + 'one period and one handrand days': { + currentTime: new Date('2023-04-10').getTime(), + genesisTime: new Date('2019-01-01').getTime(), + expectAPC: 2.369552868619654, + }, +} diff --git a/packages/neuron-ui/src/tests/calculation/calculateGlobalAPC/index.test.ts b/packages/neuron-ui/src/tests/calculation/calculateGlobalAPC/index.test.ts new file mode 100644 index 0000000000..673cfaf3ad --- /dev/null +++ b/packages/neuron-ui/src/tests/calculation/calculateGlobalAPC/index.test.ts @@ -0,0 +1,16 @@ +import calculateGlobalAPC from 'utils/calculateGlobalAPC' +import fixtures from './fixtures' + +describe('calculate the global apc', () => { + const fixtureTable = Object.entries(fixtures).map(([title, { currentTime, genesisTime, expectAPC }]) => [ + title, + currentTime, + genesisTime, + expectAPC, + ]) + + test.each(fixtureTable)(`%s`, async (_title, currentTime, genesisTime, expectAPC) => { + const apc = await calculateGlobalAPC(currentTime, genesisTime) + expect(apc).toBe(expectAPC === 0 ? 0 : +expectAPC.toFixed(2)) + }) +}) diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts index e214ee89dd..b45c5513cc 100644 --- a/packages/neuron-ui/src/types/App/index.d.ts +++ b/packages/neuron-ui/src/types/App/index.d.ts @@ -75,6 +75,7 @@ declare namespace State { interface App { tipBlockNumber: string tipBlockHash: string + tipBlockTimestamp: number chain: string difficulty: string epoch: string diff --git a/packages/neuron-ui/src/utils/calculateAPC.ts b/packages/neuron-ui/src/utils/calculateAPC.ts new file mode 100644 index 0000000000..cae67e4206 --- /dev/null +++ b/packages/neuron-ui/src/utils/calculateAPC.ts @@ -0,0 +1,7 @@ +const YEAR = 365 * 24 * 60 * 60 * 1000 +const BASE = 10000000 + +export default (compensation: string, amount: string, duration: string) => { + const v = (BigInt(compensation) * BigInt(YEAR) * BigInt(BASE)) / (BigInt(amount) * BigInt(duration)) + return `${(Number(v) / (BASE / 100)).toFixed(2)}` +} diff --git a/packages/neuron-ui/src/utils/calculateAPY.ts b/packages/neuron-ui/src/utils/calculateAPY.ts deleted file mode 100644 index 330e5e1d38..0000000000 --- a/packages/neuron-ui/src/utils/calculateAPY.ts +++ /dev/null @@ -1,7 +0,0 @@ -const YEAR = 365 * 24 * 60 * 60 * 1000 - -export default (interest: string, amount: string, duration: string) => { - const BASE = 10000 - const v = Math.floor(+((BigInt(interest) * BigInt(BASE)) / BigInt(amount)).toString()) * (YEAR / +duration) - return `${(v / BASE).toFixed(2)}` -} diff --git a/packages/neuron-ui/src/utils/calculateGlobalAPC.ts b/packages/neuron-ui/src/utils/calculateGlobalAPC.ts new file mode 100644 index 0000000000..b80971bec1 --- /dev/null +++ b/packages/neuron-ui/src/utils/calculateGlobalAPC.ts @@ -0,0 +1,48 @@ +import { getBlockByNumber } from '../services/chain' + +const INITIAL_OFFER = BigInt(33600000000) +const SECONDARY_OFFER = BigInt(1344000000) +const DAYS_PER_PERIOD = 365 * 4 * 1 +const MILLI_SECONDS_PER_DAY = 24 * 3600 * 1000 +const PERIOD_LENGTH = DAYS_PER_PERIOD * MILLI_SECONDS_PER_DAY + +let cachedGenesisTimestamp: number | undefined + +export default async (now: number, initialTimestamp: number | undefined = cachedGenesisTimestamp) => { + let genesisTimestamp = initialTimestamp + if (genesisTimestamp === undefined) { + genesisTimestamp = await getBlockByNumber('0x0') + .then(b => { + cachedGenesisTimestamp = +b.header.timestamp + return cachedGenesisTimestamp + }) + .catch(() => undefined) + } + if (genesisTimestamp === undefined || now <= genesisTimestamp) { + return 0 + } + + const pastPeriods = BigInt(now - genesisTimestamp) / BigInt(PERIOD_LENGTH) + const pastDays = Math.ceil(((now - genesisTimestamp) % PERIOD_LENGTH) / MILLI_SECONDS_PER_DAY) + + const realSecondaryOffer = + BigInt(4) * SECONDARY_OFFER * pastPeriods + + (BigInt(4) * SECONDARY_OFFER * BigInt(pastDays)) / BigInt(DAYS_PER_PERIOD) + + let realPrimaryOffer = BigInt(0) + + let PRIMARY_OFFER = INITIAL_OFFER + for (let i = 0; i < Number(pastPeriods); i++) { + PRIMARY_OFFER /= BigInt(2) + const offer = PRIMARY_OFFER + realPrimaryOffer += offer + } + + PRIMARY_OFFER /= BigInt(2) + + const primaryOfferFraction = (BigInt(pastDays) * PRIMARY_OFFER) / BigInt(DAYS_PER_PERIOD) + realPrimaryOffer += primaryOfferFraction + + const totalOffer = INITIAL_OFFER + realPrimaryOffer + realSecondaryOffer + return +(Number(SECONDARY_OFFER) / (Number(totalOffer) / 100)).toFixed(2) +} diff --git a/packages/neuron-ui/src/utils/formatters.ts b/packages/neuron-ui/src/utils/formatters.ts index 0240e1a73a..7edb19e965 100644 --- a/packages/neuron-ui/src/utils/formatters.ts +++ b/packages/neuron-ui/src/utils/formatters.ts @@ -72,7 +72,7 @@ export const currencyFormatter = ( return `${integer.replace(/\B(?=(\d{3})+(?!\d))/g, delimiter)}${dot}${decimal} ${unit}` } -export const CKBToShannonFormatter = (amount: string = '0', unit: CapacityUnit) => { +export const CKBToShannonFormatter = (amount: string = '0', unit: CapacityUnit = CapacityUnit.CKB) => { if (Number.isNaN(+amount)) { console.warn(`Amount is not a valid number`) return `${amount} ${unit}` diff --git a/packages/neuron-ui/src/utils/validators.ts b/packages/neuron-ui/src/utils/validators.ts index 3d6a5312fc..23727dfd2c 100644 --- a/packages/neuron-ui/src/utils/validators.ts +++ b/packages/neuron-ui/src/utils/validators.ts @@ -1,6 +1,14 @@ -import { MAX_NETWORK_NAME_LENGTH } from 'utils/const' +import { + MAX_NETWORK_NAME_LENGTH, + MIN_PASSWORD_LENGTH, + MAX_PASSWORD_LENGTH, + MIN_AMOUNT, + MAX_DECIMAL_DIGITS, + SHANNON_CKB_RATIO, + ErrorCode, +} from 'utils/const' +import { CKBToShannonFormatter } from 'utils/formatters' import { ckbCore } from 'services/chain' -import { MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH, MIN_AMOUNT, MAX_DECIMAL_DIGITS, ErrorCode } from './const' export const verifyAddress = (address: string): boolean => { if (typeof address !== 'string' || address.length !== 46) { @@ -14,27 +22,27 @@ export const verifyAddress = (address: string): boolean => { } export const verifyAmountRange = (amount: string = '') => { - return +amount >= MIN_AMOUNT + return BigInt(CKBToShannonFormatter(amount)) >= BigInt(MIN_AMOUNT * SHANNON_CKB_RATIO) } export const verifyAmount = (amount: string = '0') => { if (Number.isNaN(+amount)) { return { code: ErrorCode.FieldInvalid } } - if (+amount < 0) { - return { code: ErrorCode.NotNegative } - } const [, decimal = ''] = amount.split('.') if (decimal.length > MAX_DECIMAL_DIGITS) { return { code: ErrorCode.DecimalExceed, } } + if (BigInt(CKBToShannonFormatter(amount)) < BigInt(0)) { + return { code: ErrorCode.NotNegative } + } return true } export const verifyTotalAmount = (totalAmount: string, fee: string, balance: string) => { - if (+balance < 0) { + if (BigInt(balance) < BigInt(0)) { return false } return BigInt(totalAmount) + BigInt(fee) <= BigInt(balance) diff --git a/packages/neuron-ui/src/widgets/PropertyList/index.tsx b/packages/neuron-ui/src/widgets/PropertyList/index.tsx index 9c78471efc..1232ca35e5 100644 --- a/packages/neuron-ui/src/widgets/PropertyList/index.tsx +++ b/packages/neuron-ui/src/widgets/PropertyList/index.tsx @@ -28,7 +28,7 @@ const onRenderCell = (item?: Property & CellStyles) => diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json index 1530c36f10..bb864d38bb 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.24.4", + "version": "0.24.5", "private": true, "author": { "name": "Nervos Core Dev", @@ -21,7 +21,7 @@ "start:dev": "yarn run build && electron .", "build": "ttsc && ncp ./src/startup/sync-block-task/index.html ./dist/startup/sync-block-task/index.html", "clean": "rimraf dist/*", - "test": "jest --color", + "test": "jest --color --runInBand", "test:e2e": "jest --config jest.e2e.config.js --color", "lint": "eslint --fix --ext .ts,.js src", "precommit": "lint-staged", @@ -64,7 +64,7 @@ "electron-devtools-installer": "2.2.4", "electron-notarize": "0.1.1", "lint-staged": "9.2.5", - "neuron-ui": "0.24.4", + "neuron-ui": "0.24.5", "rimraf": "3.0.0", "spectron": "8.0.0", "ts-transformer-imports": "0.4.3", diff --git a/packages/neuron-wallet/src/controllers/app/index.ts b/packages/neuron-wallet/src/controllers/app/index.ts index e5e404d159..6a0ed85bd5 100644 --- a/packages/neuron-wallet/src/controllers/app/index.ts +++ b/packages/neuron-wallet/src/controllers/app/index.ts @@ -6,6 +6,7 @@ import env from 'env' import { updateApplicationMenu } from './menu' import logger from 'utils/logger' import { subscribe } from './subscribe' +import WalletService from 'services/wallets' const app = electronApp || (remote && remote.app) @@ -65,6 +66,8 @@ export default class AppController { this.mainWindow.show() this.mainWindow.focus() logger.info('The main window is ready to show') + + WalletService.getInstance().generateAddressesIfNecessary() } else { logger.error('The main window is not initialized on ready to show') } diff --git a/packages/neuron-wallet/src/controllers/wallets.ts b/packages/neuron-wallet/src/controllers/wallets.ts index a8aa816d5b..0491f7952d 100644 --- a/packages/neuron-wallet/src/controllers/wallets.ts +++ b/packages/neuron-wallet/src/controllers/wallets.ts @@ -134,7 +134,7 @@ export default class WalletsController { keystore, }) - await walletsService.generateAddressesById(wallet.id, isImporting) + walletsService.generateAddressesById(wallet.id, isImporting) return { status: ResponseCode.Success, @@ -294,26 +294,24 @@ export default class WalletsController { } public static async getAllAddresses(id: string) { - const addresses = await AddressService.allAddressesByWalletId(id).then(addrs => - addrs.map( - ({ - address, - blake160: identifier, - addressType: type, - txCount, - balance, - description = '', - addressIndex: index = '', - }) => ({ - address, - identifier, - type, - txCount, - description, - balance, - index, - }) - ) + const addresses = AddressService.allAddressesByWalletId(id).map( + ({ + address, + blake160: identifier, + addressType: type, + txCount, + balance, + description = '', + addressIndex: index = '', + }) => ({ + address, + identifier, + type, + txCount, + description, + balance, + index, + }) ) return { status: ResponseCode.Success, @@ -501,7 +499,7 @@ export default class WalletsController { const walletService = WalletsService.getInstance() const wallet = walletService.get(walletID) - await AddressService.updateDescription(wallet.id, address, description) + AddressService.updateDescription(wallet.id, address, description) return { status: ResponseCode.Success, diff --git a/packages/neuron-wallet/src/database/address/address-dao.ts b/packages/neuron-wallet/src/database/address/address-dao.ts new file mode 100644 index 0000000000..c2b48409e6 --- /dev/null +++ b/packages/neuron-wallet/src/database/address/address-dao.ts @@ -0,0 +1,244 @@ +import { remote } from 'electron' +import { AddressType } from 'models/keys/address' +import { TransactionsService } from 'services/tx' +import CellsService from 'services/cells' +import LockUtils from 'models/lock-utils' +import { TransactionStatus } from 'types/cell-types' +import { OutputStatus } from 'services/tx/params' +import NodeService from 'services/node' +import Store from 'models/store' +import AddressDbChangedSubject from 'models/subjects/address-db-changed-subject' + +export enum AddressVersion { + Testnet = 'testnet', + Mainnet = 'mainnet', +} + +export interface Address { + walletId: string + address: string + path: string + addressType: AddressType + addressIndex: number + txCount: number + liveBalance: string + sentBalance: string + pendingBalance: string + balance: string + blake160: string + version: AddressVersion + description?: string + isImporting?: boolean | undefined +} + +export default class AddressDao { + public static create = (addresses: Address[]): Address[] => { + const result = addresses.map(address => { + address.txCount = address.txCount || 0 + address.liveBalance = address.liveBalance || '0' + address.sentBalance = address.sentBalance || '0' + address.pendingBalance = address.pendingBalance || '0' + address.balance = (BigInt(address.liveBalance) + BigInt(address.sentBalance)).toString() + return address + }) + return AddressStore.add(result) + } + + public static getAll(): Address[] { + return AddressStore.getAll() + } + + // txCount include all txs in db + // liveBalance means balance of OutputStatus.Live cells (already in chain and not spent) + // sentBalance means balance of OutputStatus.Sent cells (sent to me but not committed) + // 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, + url: string = NodeService.getInstance().core.rpc.node.url + ): Promise => { + const all = AddressStore.getAll() + const toUpdate = all.filter(value => { + return value.address === address + }) + const others = all.filter(value => { + return value.address !== 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 + }) + ) + + AddressStore.updateAll(toUpdate.concat(others)) + return result + } + + public static nextUnusedAddress(walletId: string, version: AddressVersion): Address | undefined { + const addresses = AddressStore.getAll().filter(value => { + return value.walletId === walletId + && value.version === version + && value.addressType == AddressType.Receiving + && value.txCount === 0 + }) + return addresses.sort((lhs, rhs) => { + return lhs.addressIndex < rhs.addressIndex ? 1 : -1 + })[0] + } + + public static nextUnusedChangeAddress(walletId: string, version: AddressVersion): Address | undefined { + const addresses = AddressStore.getAll().filter(value => { + return value.walletId === walletId + && value.version === version + && value.addressType == AddressType.Change + && value.txCount === 0 + }) + return addresses.sort((lhs, rhs) => { + return lhs.addressIndex < rhs.addressIndex ? 1 : -1 + })[0] + } + + public static allAddresses(version: AddressVersion): Address[] { + const all = AddressStore.getAll() + return all.filter(value => { + return value.version === version + }) + } + + public static allAddressesByWalletId(walletId: string, version: AddressVersion): Address[] { + return AddressStore.getAll() + .filter(value => value.walletId === walletId && value.version === version) + .sort((lhs, rhs) => { + return lhs.addressType - rhs.addressType || lhs.addressIndex - rhs.addressIndex + }) + } + + public static usedAddressesByWalletId(walletId: string, version: AddressVersion): Address[] { + const all = AddressStore.getAll() + return all.filter(value => { + return value.walletId === walletId + && value.version === version + && value.txCount !== 0 + }) + } + + public static findByAddress(address: string, walletId: string): Address | undefined { + return AddressStore.getAll().find(value => { + return value.address === address && value.walletId == walletId + }) + } + + public static findByAddresses(addresses: string[]): Address[] { + return AddressStore.getAll().filter(value => { + return addresses.includes(value.address) + }) + } + + public static maxAddressIndex(walletId: string, addressType: AddressType, version: AddressVersion): Address | undefined { + const addresses = AddressStore.getAll().filter(value => { + return value.walletId === walletId + && value.addressType === addressType + && value.version === version + }) + return addresses.sort((lhs, rhs) => { + return lhs.addressIndex > rhs.addressIndex ? -1 : 1 + })[0] + } + + public static updateDescription(walletId: string, address: string, description: string): Address | undefined { + const item = AddressDao.findByAddress(address, walletId) + if (!item) { + return undefined + } + item.description = description + return AddressStore.update(item) + } + + public static deleteByWalletId(walletId: string): Address[] { + const all = AddressStore.getAll() + const toKeep = all.filter(value => { + return value.walletId !== walletId + }) + const deleted = all.filter(value => { + return value.walletId === walletId + }) + AddressStore.updateAll(toKeep) + + return deleted + } + + public static updateAll(addresses: Address[]) { + AddressStore.updateAll(addresses) + } + + public static deleteAll() { + AddressStore.updateAll([]) + } +} + +const isRenderer = process && process.type === 'renderer' +const addressDbChangedSubject = isRenderer + ? remote.require('./models/subjects/address-db-changed-subject').default.getSubject() + : AddressDbChangedSubject.getSubject() + +/// Persist all addresses as array in `addresses/index.json`. +class AddressStore { + static MODULE_NAME = 'addresses' + static ROOT_KEY = 'addresses' + static store = new Store(AddressStore.MODULE_NAME, 'index.json', '{}') + + static getAll(): Address[] { + const root = AddressStore.store.readSync(AddressStore.ROOT_KEY) + return root || [] + } + + static updateAll(addresses: Address[]) { + AddressStore.store.writeSync(AddressStore.ROOT_KEY, addresses) + AddressStore.changed() + } + + static add(addresses: Address[]): Address[] { + const all = AddressStore.getAll() + for (let address of addresses) { + all.push(address) + } + + AddressStore.updateAll(all) + + return addresses + } + + static update(address: Address): Address { + const all = AddressStore.getAll() + const exist = all.findIndex(value => { + return value.walletId === address.walletId && value.address === address.address + }) + if (exist !== -1) { + all[exist] = address + } else { + all.push(address) + } + + AddressStore.updateAll(all) + + return address + } + + static changed() { + addressDbChangedSubject.next("Updated") + } +} diff --git a/packages/neuron-wallet/src/database/address/dao.ts b/packages/neuron-wallet/src/database/address/dao.ts deleted file mode 100644 index b6eed992c4..0000000000 --- a/packages/neuron-wallet/src/database/address/dao.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { Not, In } from 'typeorm' -import { AddressType } from 'models/keys/address' -import { TransactionsService } from 'services/tx' -import CellsService from 'services/cells' -import LockUtils from 'models/lock-utils' -import { TransactionStatus } from 'types/cell-types' -import { OutputStatus } from 'services/tx/params' -import AddressEntity, { AddressVersion } from './entities/address' -import { getConnection } from './ormconfig' -import NodeService from 'services/node' - -export interface Address { - walletId: string - address: string - path: string - addressType: AddressType - addressIndex: number - txCount: number - liveBalance: string - sentBalance: string - pendingBalance: string - balance: string - blake160: string - version: AddressVersion - description?: string - isImporting?: boolean | undefined -} - -export default class AddressDao { - public static create = async (addresses: Address[]): Promise => { - const addressEntities: AddressEntity[] = addresses.map(address => { - const addressEntity = new AddressEntity() - addressEntity.walletId = address.walletId - addressEntity.address = address.address - addressEntity.path = address.path - addressEntity.addressType = address.addressType - addressEntity.addressIndex = address.addressIndex - addressEntity.txCount = address.txCount || 0 - addressEntity.blake160 = address.blake160 - addressEntity.version = address.version - addressEntity.liveBalance = address.liveBalance || '0' - addressEntity.sentBalance = address.sentBalance || '0' - addressEntity.pendingBalance = address.pendingBalance || '0' - return addressEntity - }) - - return getConnection().manager.save(addressEntities) - } - - // txCount include all txs in db - // liveBalance means balance of OutputStatus.Live cells (already in chain and not spent) - // sentBalance means balance of OutputStatus.Sent cells (sent to me but not committed) - // 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, - url: string = NodeService.getInstance().core.rpc.node.url - ): Promise => { - const addressEntities = await getConnection() - .getRepository(AddressEntity) - .find({ - address, - }) - - const txCount: number = await TransactionsService.getCountByAddressAndStatus(address, [ - TransactionStatus.Pending, - TransactionStatus.Success, - ], url) - const lockUtils = new LockUtils(await LockUtils.systemScript(url)) - const entities = await Promise.all( - addressEntities.map(async entity => { - const addressEntity = entity - addressEntity.txCount = txCount - const lockHashes: string[] = lockUtils.addressToAllLockHashes(addressEntity.address) - addressEntity.liveBalance = await CellsService.getBalance(lockHashes, OutputStatus.Live) - addressEntity.sentBalance = await CellsService.getBalance(lockHashes, OutputStatus.Sent) - addressEntity.pendingBalance = await CellsService.getBalance(lockHashes, OutputStatus.Pending) - return addressEntity - }) - ) - - return getConnection().manager.save(entities) - } - - public static nextUnusedAddress = async ( - walletId: string, - version: AddressVersion - ): Promise => { - const addressEntity = await getConnection() - .getRepository(AddressEntity) - .createQueryBuilder('address') - .where({ - walletId, - version, - addressType: AddressType.Receiving, - txCount: 0, - }) - .orderBy('address.addressIndex', 'ASC') - .getOne() - - return addressEntity - } - - public static nextUnusedChangeAddress = async ( - walletId: string, - version: AddressVersion - ): Promise => { - const addressEntity = await getConnection() - .getRepository(AddressEntity) - .createQueryBuilder('address') - .where({ - walletId, - version, - addressType: AddressType.Change, - txCount: 0, - }) - .orderBy('address.addressIndex', 'ASC') - .getOne() - - return addressEntity - } - - public static allAddresses = async (version: AddressVersion): Promise => { - const addressEntities = await getConnection() - .getRepository(AddressEntity) - .createQueryBuilder('address') - .where({ - version, - }) - .getMany() - - return addressEntities - } - - public static allAddressesByWalletId = async ( - walletId: string, - version: AddressVersion - ): Promise => { - const addressEntities = await getConnection() - .getRepository(AddressEntity) - .createQueryBuilder('address') - .where({ - walletId, - version, - }) - .getMany() - - return addressEntities - } - - public static usedAddressesByWalletId = async ( - walletId: string, - version: AddressVersion - ): Promise => { - const addressEntities = await getConnection() - .getRepository(AddressEntity) - .createQueryBuilder('address') - .where({ - walletId, - version, - txCount: Not(0), - }) - .getMany() - - return addressEntities - } - - public static findByAddress = async (address: string, walletId: string): Promise => { - const addressEntity = await getConnection() - .getRepository(AddressEntity) - .createQueryBuilder('address') - .where({ - address, - walletId, - }) - .getOne() - - return addressEntity - } - - public static findByAddresses = async (addresses: string[]) => { - const addressEntities = await getConnection() - .getRepository(AddressEntity) - .createQueryBuilder('address') - .where({ - address: In(addresses), - }) - .getMany() - return addressEntities - } - - public static maxAddressIndex = async ( - walletId: string, - addressType: AddressType, - version: AddressVersion - ): Promise => { - const addressEntity = await getConnection() - .getRepository(AddressEntity) - .createQueryBuilder('address') - .where({ - walletId, - addressType, - version, - }) - .orderBy('address.addressIndex', 'DESC') - .getOne() - - if (!addressEntity) { - return undefined - } - - return addressEntity - } - - public static updateDescription = async (walletId: string, address: string, description: string) => { - const addressEntity = await getConnection() - .getRepository(AddressEntity) - .createQueryBuilder('address') - .where({ - walletId, - address, - }) - .getOne() - - if (!addressEntity) { - return undefined - } - addressEntity.description = description - return getConnection().manager.save(addressEntity) - } - - public static deleteByWalletId = async (walletId: string) => { - const addresses = await getConnection() - .getRepository(AddressEntity) - .createQueryBuilder('address') - .where({ - walletId, - }) - .getMany() - const result = addresses.map(addr => addr.toInterface()) - await getConnection().manager.remove(addresses) - return result - } -} diff --git a/packages/neuron-wallet/src/database/address/entities/address.ts b/packages/neuron-wallet/src/database/address/entities/address.ts deleted file mode 100644 index 1cdd995d60..0000000000 --- a/packages/neuron-wallet/src/database/address/entities/address.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { remote } from 'electron' -import { Entity, BaseEntity, PrimaryColumn, Column, AfterInsert, AfterUpdate, AfterRemove } from 'typeorm' -import { AddressType } from 'models/keys/address' -import AddressDbChangedSubject from 'models/subjects/address-db-changed-subject' -import { Address as AddressInterface } from '../dao' - -export enum AddressVersion { - Testnet = 'testnet', - Mainnet = 'mainnet', -} - -const isRenderer = process && process.type === 'renderer' -const addressDbChangedSubject = isRenderer - ? remote.require('./models/subjects/address-db-changed-subject').default.getSubject() - : AddressDbChangedSubject.getSubject() - -@Entity() -export default class Address extends BaseEntity { - @PrimaryColumn({ - type: 'varchar', - }) - address!: string - - @PrimaryColumn({ - type: 'varchar', - }) - walletId!: string - - @Column({ - type: 'varchar', - }) - path!: string - - @Column({ - type: 'int', - }) - addressType!: AddressType - - @Column({ - type: 'int', - }) - addressIndex!: number - - @Column({ - type: 'int', - }) - txCount!: number - - @Column({ - type: 'varchar', - }) - blake160!: string - - @Column({ - type: 'varchar', - }) - version!: AddressVersion - - @Column({ - type: 'varchar', - nullable: true, - }) - description?: string - - @Column({ - type: 'varchar', - }) - liveBalance: string = '0' - - @Column() - sentBalance: string = '0' - - @Column() - pendingBalance: string = '0' - - public balance = (): string => { - return (BigInt(this.liveBalance) + BigInt(this.sentBalance)).toString() - } - - public toInterface = (): AddressInterface => { - return { - address: this.address, - walletId: this.walletId, - path: this.path, - addressType: this.addressType, - addressIndex: this.addressIndex, - txCount: this.txCount, - blake160: this.blake160, - version: this.version, - liveBalance: this.liveBalance, - sentBalance: this.sentBalance, - pendingBalance: this.pendingBalance, - balance: this.balance(), - description: this.description, - } - } - - @AfterInsert() - emitInsert() { - this.changed('AfterInsert') - } - - @AfterUpdate() - emitUpdate() { - this.changed('AfterUpdate') - } - - @AfterRemove() - emitRemove() { - this.changed('AfterRemove') - } - - private changed = (event: string) => { - addressDbChangedSubject.next(event) - } -} diff --git a/packages/neuron-wallet/src/database/address/migrations/1561461669542-AddAddress.ts b/packages/neuron-wallet/src/database/address/migrations/1561461669542-AddAddress.ts deleted file mode 100644 index 34bccf8ae8..0000000000 --- a/packages/neuron-wallet/src/database/address/migrations/1561461669542-AddAddress.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {MigrationInterface, QueryRunner} from "typeorm"; - -export class AddAddress1561461669542 implements MigrationInterface { - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`CREATE TABLE "address" ("address" varchar NOT NULL, "walletId" varchar NOT NULL, "path" varchar NOT NULL, "addressType" integer NOT NULL, "addressIndex" integer NOT NULL, "txCount" integer NOT NULL, "blake160" varchar NOT NULL, "version" varchar NOT NULL, "description" varchar, "balance" varchar NOT NULL, PRIMARY KEY ("address", "walletId"))`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`DROP TABLE "address"`); - } - -} diff --git a/packages/neuron-wallet/src/database/address/migrations/1562126909151-extendBalance.ts b/packages/neuron-wallet/src/database/address/migrations/1562126909151-extendBalance.ts deleted file mode 100644 index 516d5befbc..0000000000 --- a/packages/neuron-wallet/src/database/address/migrations/1562126909151-extendBalance.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {MigrationInterface, QueryRunner, TableColumn} from "typeorm"; - -export class extendBalance1562126909151 implements MigrationInterface { - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE 'address' ADD COLUMN 'sentBalance' varchar NOT NULL DEFAULT '0';`) - await queryRunner.query(`ALTER TABLE 'address' ADD COLUMN 'pendingBalance' varchar NOT NULL DEFAULT '0';`) - - await queryRunner.changeColumn('address', 'balance', new TableColumn({ - name: 'liveBalance', - type: 'varchar', - })) - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.dropColumn('address', 'sentBalance') - await queryRunner.dropColumn('address', 'pendingBalance') - await queryRunner.changeColumn('address', 'liveBalance', new TableColumn({ - name: 'balance', - type: 'varchar', - })) - } - -} diff --git a/packages/neuron-wallet/src/database/address/migrations/1567485550388-AddTotalBalance.ts b/packages/neuron-wallet/src/database/address/migrations/1567485550388-AddTotalBalance.ts deleted file mode 100644 index 03b3e5df9b..0000000000 --- a/packages/neuron-wallet/src/database/address/migrations/1567485550388-AddTotalBalance.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {MigrationInterface, QueryRunner, TableColumn} from "typeorm"; - -export class AddTotalBalance1567485550388 implements MigrationInterface { - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.addColumn('address', new TableColumn({ - name: 'totalBalance', - type: 'varchar', - default: '0', - })) - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.dropColumn('address', 'totalBalance') - } - -} diff --git a/packages/neuron-wallet/src/database/address/migrations/1573458655136-RemoveTotalBalance.ts b/packages/neuron-wallet/src/database/address/migrations/1573458655136-RemoveTotalBalance.ts deleted file mode 100644 index 4bc22a5710..0000000000 --- a/packages/neuron-wallet/src/database/address/migrations/1573458655136-RemoveTotalBalance.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {MigrationInterface, QueryRunner, TableColumn} from "typeorm"; - -export class RemoveTotalBalance1573458655136 implements MigrationInterface { - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.dropColumn('address', 'totalBalance') - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.addColumn('address', new TableColumn({ - name: 'totalBalance', - type: 'varchar', - default: '0', - })) - } - -} diff --git a/packages/neuron-wallet/src/database/address/ormconfig.ts b/packages/neuron-wallet/src/database/address/ormconfig.ts deleted file mode 100644 index 3aa6ee695a..0000000000 --- a/packages/neuron-wallet/src/database/address/ormconfig.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { createConnection, getConnection as ormGetConnection } from 'typeorm' -import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions' -import path from 'path' - -import env from 'env' - -import Address from './entities/address' - -import { AddAddress1561461669542 } from './migrations/1561461669542-AddAddress' -import { extendBalance1562126909151 } from './migrations/1562126909151-extendBalance' -import { AddTotalBalance1567485550388 } from './migrations/1567485550388-AddTotalBalance' -import { RemoveTotalBalance1573458655136 } from './migrations/1573458655136-RemoveTotalBalance' - -const dbPath = path.join(env.fileBasePath, 'address.sqlite') - -const connectionName = 'address' - -const connectOptions = (): SqliteConnectionOptions => { - const database = env.isTestMode ? ':memory:' : dbPath - return { - name: connectionName, - type: 'sqlite', - database, - entities: [Address], - migrations: [ - AddAddress1561461669542, - extendBalance1562126909151, - AddTotalBalance1567485550388, - RemoveTotalBalance1573458655136, - ], - synchronize: false, - migrationsRun: true, - logging: ['error'], - } -} - -export const getConnection = () => { - return ormGetConnection(connectionName) -} - -const setBusyTimeout = async () => { - await getConnection().manager.query(`PRAGMA busy_timeout = 3000;`) -} - -export const initConnection = async () => { - const connectionOptions = connectOptions() - await createConnection(connectionOptions) - await setBusyTimeout() -} - -export default initConnection diff --git a/packages/neuron-wallet/src/database/chain/ormconfig.ts b/packages/neuron-wallet/src/database/chain/ormconfig.ts index 030e4eed5e..c5730b278f 100644 --- a/packages/neuron-wallet/src/database/chain/ormconfig.ts +++ b/packages/neuron-wallet/src/database/chain/ormconfig.ts @@ -32,7 +32,7 @@ const connectOptions = async (genesisBlockHash: string): Promise { 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.walletId).filter((value, idx, a) => a.indexOf(value) === idx) - await Promise.all( - walletIds.map(async id => { - const wallet = WalletService.getInstance().get(id) - const accountExtendedPublicKey: AccountExtendedPublicKey = wallet.accountExtendedPublicKey() - // set isImporting to undefined means unknown - await AddressService.checkAndGenerateSave(id, accountExtendedPublicKey, undefined, 20, 10) - }) - ) + 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() + // set isImporting to undefined means unknown + AddressService.checkAndGenerateSave(id, accountExtendedPublicKey, undefined, 20, 10) + } }) } diff --git a/packages/neuron-wallet/src/main.ts b/packages/neuron-wallet/src/main.ts index 550f1588eb..bb62cfe71e 100644 --- a/packages/neuron-wallet/src/main.ts +++ b/packages/neuron-wallet/src/main.ts @@ -1,7 +1,6 @@ import { app } from 'electron' import AppController from 'controllers/app' -import initConnection from 'database/address/ormconfig' import createSyncBlockTask from 'startup/sync-block-task/create' import { changeLanguage } from 'utils/i18n' @@ -10,7 +9,6 @@ const appController = new AppController() app.on('ready', async () => { changeLanguage(app.getLocale()) - await initConnection() createSyncBlockTask() appController.openWindow() 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 40f5ab3fc9..63ff7968cd 100644 --- a/packages/neuron-wallet/src/models/subjects/address-created-subject.ts +++ b/packages/neuron-wallet/src/models/subjects/address-created-subject.ts @@ -1,5 +1,5 @@ import { ReplaySubject } from 'rxjs' -import { Address } from 'database/address/dao' +import { Address } from 'database/address/address-dao' export default class AddressCreatedSubject { static subject = new ReplaySubject(100) diff --git a/packages/neuron-wallet/src/services/addresses.ts b/packages/neuron-wallet/src/services/addresses.ts index 101d19cafc..5c479c4df3 100644 --- a/packages/neuron-wallet/src/services/addresses.ts +++ b/packages/neuron-wallet/src/services/addresses.ts @@ -2,8 +2,7 @@ import { AddressPrefix } from '@nervosnetwork/ckb-sdk-utils' import { AccountExtendedPublicKey } from 'models/keys/key' import Address, { AddressType } from 'models/keys/address' import LockUtils from 'models/lock-utils' -import AddressDao, { Address as AddressInterface } from 'database/address/dao' -import AddressEntity, { AddressVersion } from 'database/address/entities/address' +import AddressDao, { Address as AddressInterface, AddressVersion } from 'database/address/address-dao' import AddressCreatedSubject from 'models/subjects/address-created-subject' import NodeService from './node' import ChainInfo from 'models/chain-info' @@ -18,12 +17,12 @@ export interface AddressMetaInfo { } export default class AddressService { - public static isAddressUsed = async (address: string, walletId: string): Promise => { - const addressEntity = await AddressDao.findByAddress(address, walletId) + public static isAddressUsed = (address: string, walletId: string): boolean => { + const addressEntity = AddressDao.findByAddress(address, walletId) return !!addressEntity } - public static generateAndSave = async ( + public static generateAndSave = ( walletId: string, extendedKey: AccountExtendedPublicKey, isImporting: boolean | undefined, @@ -46,7 +45,7 @@ export default class AddressService { ...addresses.testnetChange, ...addresses.mainnetChange, ] - await AddressDao.create(allAddresses) + AddressDao.create(allAddresses) // TODO: notify address created and pass addressWay AddressService.notifyAddressCreated(allAddresses, isImporting) @@ -64,7 +63,7 @@ export default class AddressService { AddressCreatedSubject.getSubject().next(addrs) } - public static checkAndGenerateSave = async ( + public static checkAndGenerateSave = ( walletId: string, extendedKey: AccountExtendedPublicKey, isImporting: boolean | undefined, @@ -72,8 +71,8 @@ export default class AddressService { changeAddressCount: number = 10 ) => { const addressVersion = AddressService.getAddressVersion() - const maxIndexReceivingAddress = await AddressDao.maxAddressIndex(walletId, AddressType.Receiving, addressVersion) - const maxIndexChangeAddress = await AddressDao.maxAddressIndex(walletId, AddressType.Change, addressVersion) + const maxIndexReceivingAddress = AddressDao.maxAddressIndex(walletId, AddressType.Receiving, addressVersion) + const maxIndexChangeAddress = AddressDao.maxAddressIndex(walletId, AddressType.Change, addressVersion) if ( maxIndexReceivingAddress !== undefined && maxIndexReceivingAddress.txCount === 0 && @@ -95,16 +94,13 @@ export default class AddressService { ) } - public static updateTxCountAndBalances = async ( - addresses: string[], - url: string = NodeService.getInstance().core.rpc.node.url - ) => { - let addrs: AddressEntity[] = [] + public static updateTxCountAndBalances = async (addresses: string[], url: string = NodeService.getInstance().core.rpc.node.url) => { + let result: Address[] = [] for (const address of addresses) { - const ads = await AddressDao.updateTxCountAndBalance(address, url) - addrs = addrs.concat(ads) + const updatedAddress = await AddressDao.updateTxCountAndBalance(address, url) + result = result.concat(updatedAddress) } - return addrs + return result } // Generate both receiving and change addresses. @@ -194,59 +190,52 @@ export default class AddressService { return [testnetAddressInfo, mainnetAddressInfo] } - public static nextUnusedAddress = async (walletId: string): Promise => { + public static nextUnusedAddress = (walletId: string): AddressInterface | undefined => { const version = AddressService.getAddressVersion() - const addressEntity = await AddressDao.nextUnusedAddress(walletId, version) + const addressEntity = AddressDao.nextUnusedAddress(walletId, version) if (!addressEntity) { return undefined } - return addressEntity.toInterface() + return addressEntity } - public static nextUnusedChangeAddress = async (walletId: string): Promise => { + public static nextUnusedChangeAddress = (walletId: string): AddressInterface | undefined => { const version = AddressService.getAddressVersion() - const addressEntity = await AddressDao.nextUnusedChangeAddress(walletId, version) + const addressEntity = AddressDao.nextUnusedChangeAddress(walletId, version) if (!addressEntity) { return undefined } - return addressEntity.toInterface() + return addressEntity } - public static allAddresses = async (): Promise => { + public static allAddresses = (): AddressInterface[] => { const version = AddressService.getAddressVersion() - const addressEntities = await AddressDao.allAddresses(version) - - return addressEntities.map(addr => addr.toInterface()) + return AddressDao.allAddresses(version) } - public static allAddressesByWalletId = async (walletId: string): Promise => { + public static allAddressesByWalletId = (walletId: string): AddressInterface[] => { const version = AddressService.getAddressVersion() - const addressEntities = await AddressDao.allAddressesByWalletId(walletId, version) - - return addressEntities.map(addr => addr.toInterface()) + return AddressDao.allAddressesByWalletId(walletId, version) } - public static usedAddresses = async (walletId: string): Promise => { + public static usedAddresses = (walletId: string): AddressInterface[] => { const version = AddressService.getAddressVersion() - const addressEntities = await AddressDao.usedAddressesByWalletId(walletId, version) - - return addressEntities.map(addr => addr.toInterface()) + return AddressDao.usedAddressesByWalletId(walletId, version) } - public static updateDescription = async (walletId: string, address: string, description: string) => { + public static updateDescription = (walletId: string, address: string, description: string): AddressInterface | undefined => { return AddressDao.updateDescription(walletId, address, description) } - public static deleteByWalletId = async (walletId: string) => { + public static deleteByWalletId = (walletId: string): AddressInterface[] => { return AddressDao.deleteByWalletId(walletId) } - public static findByAddresses = async (addresses: string[]) => { - const entities = await AddressDao.findByAddresses(addresses) - return entities.map(entity => entity.toInterface()) + public static findByAddresses = (addresses: string[]): AddressInterface[] => { + return AddressDao.findByAddresses(addresses) } private static getAddressVersion = (): AddressVersion => { diff --git a/packages/neuron-wallet/src/services/cells.ts b/packages/neuron-wallet/src/services/cells.ts index 96c1041196..c57912479f 100644 --- a/packages/neuron-wallet/src/services/cells.ts +++ b/packages/neuron-wallet/src/services/cells.ts @@ -4,6 +4,7 @@ import { Cell, OutPoint, Input } from 'types/cell-types' import { CapacityNotEnough, CapacityNotEnoughForChange } from 'exceptions' import { OutputStatus } from './tx/params' import FeeMode from 'models/fee-mode' +import { TransactionStatus } from 'types/cell-types' export const MIN_CELL_CAPACITY = '6100000000' @@ -43,9 +44,10 @@ export default class CellsService { .getRepository(OutputEntity) .createQueryBuilder('output') .leftJoinAndSelect('output.transaction', 'tx') - .where(`output.status <> :deadStatus AND output.daoData IS NOT NULL AND output.lockHash in (:...lockHashes) AND tx.blockNumber IS NOT NULL`, { + .where(`(output.status = :liveStatus OR tx.status = :failedStatus) AND output.daoData IS NOT NULL AND output.lockHash in (:...lockHashes) AND tx.blockNumber IS NOT NULL`, { lockHashes, - deadStatus: OutputStatus.Dead, + liveStatus: OutputStatus.Live, + failedStatus: TransactionStatus.Failed, }) .orderBy(`CASE output.daoData WHEN '0x0000000000000000' THEN 1 ELSE 0 END`, 'ASC') .addOrderBy('tx.timestamp', 'ASC') diff --git a/packages/neuron-wallet/src/services/sync/block-number.ts b/packages/neuron-wallet/src/services/sync/block-number.ts index cd7dad0740..bbf59fb5ac 100644 --- a/packages/neuron-wallet/src/services/sync/block-number.ts +++ b/packages/neuron-wallet/src/services/sync/block-number.ts @@ -19,7 +19,7 @@ export default class BlockNumber { const blockNumberEntity: SyncInfoEntity | undefined = await this.blockNumber() if (!blockNumberEntity) { - return BigInt(0) + return BigInt(-1) } return BigInt(blockNumberEntity.value) diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts index 490cc3550e..9bfea2d62a 100644 --- a/packages/neuron-wallet/src/services/wallets.ts +++ b/packages/neuron-wallet/src/services/wallets.ts @@ -7,7 +7,7 @@ import LockUtils from 'models/lock-utils' import { TransactionWithoutHash, Input, OutPoint, WitnessArgs } from 'types/cell-types' import ConvertTo from 'types/convert-to' import { WalletNotFound, IsRequired, UsedName } from 'exceptions' -import { Address as AddressInterface } from 'database/address/dao' +import { Address as AddressInterface } from 'database/address/address-dao' import Keychain from 'models/keys/keychain' import AddressDbChangedSubject from 'models/subjects/address-db-changed-subject' import AddressesUsedSubject from 'models/subjects/addresses-used-subject' @@ -173,15 +173,22 @@ export default class WalletService { return FileKeystoreWallet.fromJSON(wallet) } - public generateAddressesById = async ( + public generateAddressesIfNecessary = () => { + for (const wallet of this.getAll()) { + if (AddressService.allAddressesByWalletId(wallet.id).length === 0) { + this.generateAddressesById(wallet.id, false) + } + } + } + + public generateAddressesById = ( id: string, isImporting: boolean, receivingAddressCount: number = 20, changeAddressCount: number = 10 ) => { - const wallet: Wallet = this.get(id) - const accountExtendedPublicKey: AccountExtendedPublicKey = wallet.accountExtendedPublicKey() - await AddressService.checkAndGenerateSave( + const accountExtendedPublicKey: AccountExtendedPublicKey = this.get(id).accountExtendedPublicKey() + AddressService.checkAndGenerateSave( id, accountExtendedPublicKey, isImporting, @@ -190,18 +197,6 @@ export default class WalletService { ) } - public generateCurrentWalletAddresses = async ( - isImporting: boolean, - receivingAddressCount: number = 20, - changeAddressCount: number = 10 - ) => { - const wallet: Wallet | undefined = this.getCurrent() - if (!wallet) { - return undefined - } - return this.generateAddressesById(wallet.id, isImporting, receivingAddressCount, changeAddressCount) - } - public create = (props: WalletProperties) => { if (!props) { throw new IsRequired('wallet property') @@ -269,7 +264,7 @@ export default class WalletService { this.listStore.writeSync(this.walletsKey, newWallets) wallet.deleteKeystore() - const addressInterfaces = await AddressService.deleteByWalletId(id) + const addressInterfaces = AddressService.deleteByWalletId(id) this.deindexAddresses(addressInterfaces.map(addr => addr.address)) } @@ -280,7 +275,7 @@ export default class WalletService { if (addressesWithEnvPrefix.length === 0) { return } - const addrs: string[] = (await AddressService.findByAddresses(addressesWithEnvPrefix)).map(addr => addr.address) + const addrs: string[] = AddressService.findByAddresses(addressesWithEnvPrefix).map(addr => addr.address) const deindexAddresses: string[] = addresses.filter(item => addrs.indexOf(item) < 0); // only deindex if no same wallet if (deindexAddresses.length !== 0) { @@ -362,7 +357,7 @@ export default class WalletService { const txHash = core.utils.rawTransactionToHash(ConvertTo.toSdkTxWithoutHash(tx)) - const addressInfos = await this.getAddressInfos(walletID) + const addressInfos = this.getAddressInfos(walletID) const paths = addressInfos.map(info => info.path) const pathAndPrivateKeys = this.getPrivateKeys(wallet, paths, password) const findPrivateKey = (blake160: string) => { @@ -459,12 +454,12 @@ export default class WalletService { fee: string = '0', feeRate: string = '0', ): Promise => { - const wallet = await this.get(walletID) + const wallet = this.get(walletID) if (!wallet) { throw new WalletNotFound(walletID) } - const addressInfos = await this.getAddressInfos(walletID) + const addressInfos = this.getAddressInfos(walletID) const addresses: string[] = addressInfos.map(info => info.address) @@ -475,7 +470,7 @@ export default class WalletService { capacity: BigInt(item.capacity).toString(), })) - const changeAddress: string = await this.getChangeAddress() + const changeAddress: string = this.getChangeAddress() const tx: TransactionWithoutHash = await TransactionGenerator.generateTx( lockHashes, @@ -494,20 +489,20 @@ export default class WalletService { fee: string = '0', feeRate: string = '0', ): Promise => { - const wallet = await this.get(walletID) + const wallet = this.get(walletID) if (!wallet) { throw new WalletNotFound(walletID) } - const addressInfos = await this.getAddressInfos(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 address = await AddressesService.nextUnusedAddress(walletID) + const address = AddressesService.nextUnusedAddress(walletID) - const changeAddress: string = await this.getChangeAddress() + const changeAddress: string = this.getChangeAddress() const tx = await TransactionGenerator.generateDepositTx( lockHashes, @@ -527,7 +522,7 @@ export default class WalletService { fee: string = '0', feeRate: string = '0' ): Promise => { - const wallet = await this.get(walletID) + const wallet = this.get(walletID) if (!wallet) { throw new WalletNotFound(walletID) } @@ -543,7 +538,7 @@ export default class WalletService { throw new TransactionIsNotCommittedYet() } - const addressInfos = await this.getAddressInfos(walletID) + const addressInfos = this.getAddressInfos(walletID) const addresses: string[] = addressInfos.map(info => info.address) @@ -772,12 +767,12 @@ export default class WalletService { } public computeCycles = async (walletID: string = '', capacities: string): Promise => { - const wallet = await this.get(walletID) + const wallet = this.get(walletID) if (!wallet) { throw new WalletNotFound(walletID) } - const addressInfos = await this.getAddressInfos(walletID) + const addressInfos = this.getAddressInfos(walletID) const addresses: string[] = addressInfos.map(info => info.address) @@ -790,7 +785,7 @@ export default class WalletService { } // path is a BIP44 full path such as "m/44'/309'/0'/0/0" - public getAddressInfos = async (walletID: string): Promise => { + public getAddressInfos = (walletID: string): AddressInterface[] => { const wallet = this.get(walletID) if (!wallet) { throw new WalletNotFound(walletID) @@ -798,10 +793,9 @@ export default class WalletService { return AddressService.allAddressesByWalletId(walletID) } - public getChangeAddress = async (): Promise => { + public getChangeAddress = (): string => { const walletId = this.getCurrent()!.id - const addr = await AddressService.nextUnusedChangeAddress(walletId) - return addr!.address + return AddressService.nextUnusedChangeAddress(walletId)!.address } public signWitness = ( 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 a3220e1a12..1c856cbe84 100644 --- a/packages/neuron-wallet/src/startup/sync-block-task/create.ts +++ b/packages/neuron-wallet/src/startup/sync-block-task/create.ts @@ -12,7 +12,7 @@ import DataUpdateSubject from 'models/subjects/data-update' export { genesisBlockHash } const updateAllAddressesTxCount = async (url: string) => { - const addresses = (await AddressService.allAddresses()).map(addr => addr.address) + const addresses = AddressService.allAddresses().map(addr => addr.address) await AddressService.updateTxCountAndBalances(addresses, url) } 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 cb1331ba7c..87f4b91b62 100644 --- a/packages/neuron-wallet/src/startup/sync-block-task/indexer.ts +++ b/packages/neuron-wallet/src/startup/sync-block-task/indexer.ts @@ -2,7 +2,7 @@ import { remote } from 'electron' import AddressService from 'services/addresses' import LockUtils from 'models/lock-utils' import IndexerQueue, { LockHashInfo } from 'services/indexer/queue' -import { Address } from 'database/address/dao' +import { Address } from 'database/address/address-dao' import initConnection from 'database/chain/ormconfig' import ChainInfo from 'models/chain-info' @@ -12,7 +12,7 @@ const { nodeService, addressCreatedSubject, walletCreatedSubject } = remote.requ // maybe should call this every time when new address generated // load all addresses and convert to lockHashes export const loadAddressesAndConvert = async (nodeURL: string): Promise => { - const addresses: string[] = (await AddressService.allAddresses()).map(addr => addr.address) + const addresses: string[] = AddressService.allAddresses().map(addr => addr.address) const lockUtils = new LockUtils(await LockUtils.loadSystemScript(nodeURL)) return lockUtils.addressesToAllLockHashes(addresses) } 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 decb81948f..fdac5e0682 100644 --- a/packages/neuron-wallet/src/startup/sync-block-task/sync.ts +++ b/packages/neuron-wallet/src/startup/sync-block-task/sync.ts @@ -2,7 +2,7 @@ import { remote } from 'electron' import AddressService from 'services/addresses' import LockUtils from 'models/lock-utils' import BlockListener from 'services/sync/block-listener' -import { Address } from 'database/address/dao' +import { Address } from 'database/address/address-dao' import initConnection from 'database/chain/ormconfig' import ChainInfo from 'models/chain-info' @@ -21,7 +21,7 @@ export interface LockHashInfo { // load all addresses and convert to lockHashes export const loadAddressesAndConvert = async (nodeURL: string): Promise => { const lockUtils = new LockUtils(await LockUtils.systemScript(nodeURL)) - const addresses = (await AddressService.allAddresses()).map(addr => addr.address) + const addresses = AddressService.allAddresses().map(addr => addr.address) return lockUtils.addressesToAllLockHashes(addresses) } 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 9b7d4abc98..31c0226e0f 100644 --- a/packages/neuron-wallet/src/startup/sync-block-task/task.ts +++ b/packages/neuron-wallet/src/startup/sync-block-task/task.ts @@ -1,5 +1,4 @@ import { remote } from 'electron' -import { initConnection as initAddressConnection } from 'database/address/ormconfig' import AddressesUsedSubject from 'models/subjects/addresses-used-subject' import { register as registerTxStatusListener } from 'listeners/tx-status' import { register as registerAddressListener } from 'listeners/address' @@ -31,7 +30,6 @@ export const testIndexer = async (url: string): Promise => { } export const run = async () => { - await initAddressConnection() databaseInitSubject.subscribe(async (params: DatabaseInitParams) => { const { network, genesisBlockHash, chain } = params if (network && genesisBlockHash.startsWith('0x')) { diff --git a/packages/neuron-wallet/tests/database/address/balance.test.ts b/packages/neuron-wallet/tests/database/address/balance.test.ts index 3abb13f16f..15811932de 100644 --- a/packages/neuron-wallet/tests/database/address/balance.test.ts +++ b/packages/neuron-wallet/tests/database/address/balance.test.ts @@ -1,21 +1,9 @@ -import AddressEntity, { AddressVersion } from '../../../src/database/address/entities/address' import { AddressType } from '../../../src/models/keys/address' -import initConnection, { getConnection } from '../../../src/database/address/ormconfig' -import AddressDao, { Address } from '../../../src/database/address/dao' +import AddressDao, { Address, AddressVersion } from '../../../src/database/address/address-dao' describe('balance', () => { - beforeAll(async () => { - await initConnection() - }) - - afterAll(async () => { - await getConnection().close() - }) - - beforeEach(async () => { - const connection = getConnection() - await connection.dropDatabase() - await connection.synchronize() + beforeEach(() => { + AddressDao.deleteAll() }) const generateAddress = ( @@ -43,24 +31,24 @@ describe('balance', () => { it('balance = live + sent - pending', async () => { const address = generateAddress('1000', '100', '300') - const addrs: AddressEntity[] = await AddressDao.create([address]) + const addrs: Address[] = await AddressDao.create([address]) const addr = addrs[0] - expect(addr.balance()).toEqual((1000 + 100).toString()) + expect(addr.balance).toEqual((1000 + 100).toString()) }) it('the balance returned by the toInterface() is correct', async () => { const address = generateAddress('1000', '100', '300') - const addrs: AddressEntity[] = await AddressDao.create([address]) + const addrs: Address[] = AddressDao.create([address]) const addr = addrs[0] - expect(addr.toInterface().balance).toEqual((1000 + 100).toString()) + expect(addr.balance).toEqual((1000 + 100).toString()) }) it('sent to others', async () => { // have 1000, sent to others 200, and refund 800 const addresses = [generateAddress('0', '0', '1000'), generateAddress('0', '800', '0')] - const addrs: AddressEntity[] = await AddressDao.create(addresses) - const balance: bigint = addrs.map(addr => BigInt(addr.balance())).reduce((result, c) => result + c, BigInt(0)) + const addrs: Address[] = await AddressDao.create(addresses) + const balance: bigint = addrs.map(addr => BigInt(addr.balance)).reduce((result, c) => result + c, BigInt(0)) expect(balance).toEqual(BigInt(800)) }) @@ -72,8 +60,8 @@ describe('balance', () => { generateAddress('0', '200', '0'), generateAddress('0', '800', '0'), ] - const addrs: AddressEntity[] = await AddressDao.create(addresses) - const balance: bigint = addrs.map(addr => BigInt(addr.balance())).reduce((result, c) => result + c, BigInt(0)) + const addrs: Address[] = await AddressDao.create(addresses) + const balance: bigint = addrs.map(addr => BigInt(addr.balance)).reduce((result, c) => result + c, BigInt(0)) expect(balance).toEqual(BigInt(1000)) }) @@ -82,8 +70,8 @@ describe('balance', () => { // have 1000, sent to others 200, and refund 790, with 10 shannon fee const addresses = [generateAddress('0', '0', '1000'), generateAddress('0', '790', '0')] - const addrs: AddressEntity[] = await AddressDao.create(addresses) - const balance: bigint = addrs.map(addr => BigInt(addr.balance())).reduce((result, c) => result + c, BigInt(0)) + const addrs: Address[] = await AddressDao.create(addresses) + const balance: bigint = addrs.map(addr => BigInt(addr.balance)).reduce((result, c) => result + c, BigInt(0)) expect(balance).toEqual(BigInt(790)) }) @@ -96,8 +84,8 @@ describe('balance', () => { generateAddress('0', '790', '0'), ] - const addrs: AddressEntity[] = await AddressDao.create(addresses) - const balance: bigint = addrs.map(addr => BigInt(addr.balance())).reduce((result, c) => result + c, BigInt(0)) + const addrs: Address[] = await AddressDao.create(addresses) + const balance: bigint = addrs.map(addr => BigInt(addr.balance)).reduce((result, c) => result + c, BigInt(0)) expect(balance).toEqual(BigInt(990)) }) diff --git a/packages/neuron-wallet/tests/database/address/dao.test.ts b/packages/neuron-wallet/tests/database/address/dao.test.ts index 2f05b462d5..5044459f74 100644 --- a/packages/neuron-wallet/tests/database/address/dao.test.ts +++ b/packages/neuron-wallet/tests/database/address/dao.test.ts @@ -1,7 +1,5 @@ -import AddressEntity, { AddressVersion } from '../../../src/database/address/entities/address' import { AddressType } from '../../../src/models/keys/address' -import initConnection, { getConnection } from '../../../src/database/address/ormconfig' -import AddressDao, { Address } from '../../../src/database/address/dao' +import AddressDao, { Address, AddressVersion } from '../../../src/database/address/address-dao' describe('Address Dao tests', () => { const address: Address = { @@ -49,27 +47,14 @@ describe('Address Dao tests', () => { version: AddressVersion.Testnet, } - beforeAll(async () => { - await initConnection() + beforeEach(() => { + AddressDao.deleteAll() }) - afterAll(async () => { - await getConnection().close() - }) - - beforeEach(async () => { - const connection = getConnection() - await connection.dropDatabase() - await connection.synchronize() - }) - - it('create', async () => { - await AddressDao.create([address]) + it('create', () => { + AddressDao.create([address]) - const all = await getConnection() - .getRepository(AddressEntity) - .createQueryBuilder('address') - .getMany() + const all = AddressDao.getAll() expect(all.length).toEqual(1) expect(all[0].address).toEqual(address.address) @@ -89,64 +74,64 @@ describe('Address Dao tests', () => { // expect(dao.txCount).toEqual(getCountByAddress) // }) - it('nextUnusedAddress', async () => { - await AddressDao.create([address, usedAddress]) + it('nextUnusedAddress', () => { + AddressDao.create([address, usedAddress]) - const addr = await AddressDao.nextUnusedAddress('1', AddressVersion.Testnet) + const addr = AddressDao.nextUnusedAddress('1', AddressVersion.Testnet) expect(addr!.address).toEqual(address.address) - const usedAddr = await AddressDao.nextUnusedAddress('2', AddressVersion.Testnet) + const usedAddr = AddressDao.nextUnusedAddress('2', AddressVersion.Testnet) expect(usedAddr).toBe(undefined) - const mainnetAddr = await AddressDao.nextUnusedAddress('1', AddressVersion.Mainnet) + const mainnetAddr = AddressDao.nextUnusedAddress('1', AddressVersion.Mainnet) expect(mainnetAddr).toBe(undefined) }) - it('nextUnusedChangeAddress', async () => { - await AddressDao.create([address, usedAddress, changeAddress]) + it('nextUnusedChangeAddress', () => { + AddressDao.create([address, usedAddress, changeAddress]) - const addr = await AddressDao.nextUnusedChangeAddress('1', AddressVersion.Testnet) + const addr = AddressDao.nextUnusedChangeAddress('1', AddressVersion.Testnet) expect(addr!.address).toEqual(changeAddress.address) - const usedAddr = await AddressDao.nextUnusedAddress('2', AddressVersion.Testnet) + const usedAddr = AddressDao.nextUnusedAddress('2', AddressVersion.Testnet) expect(usedAddr).toBe(undefined) - const mainnetAddr = await AddressDao.nextUnusedAddress('1', AddressVersion.Mainnet) + const mainnetAddr = AddressDao.nextUnusedAddress('1', AddressVersion.Mainnet) expect(mainnetAddr).toBe(undefined) }) - it('allAddresses', async () => { - await AddressDao.create([address, usedAddress]) + it('allAddresses', () => { + AddressDao.create([address, usedAddress]) - const all = await AddressDao.allAddresses(AddressVersion.Testnet) + const all = AddressDao.allAddresses(AddressVersion.Testnet) - const allMainnet = await AddressDao.allAddresses(AddressVersion.Mainnet) + const allMainnet = AddressDao.allAddresses(AddressVersion.Mainnet) expect(all.length).toEqual(2) expect(allMainnet.length).toEqual(0) }) - it('allAddressesByWalletId', async () => { - await AddressDao.create([address, usedAddress]) + it('allAddressesByWalletId', () => { + AddressDao.create([address, usedAddress]) - const all = await AddressDao.allAddressesByWalletId('1', AddressVersion.Testnet) + const all = AddressDao.allAddressesByWalletId('1', AddressVersion.Testnet) expect(all.length).toEqual(1) }) - it('usedAddressByWalletId', async () => { - await AddressDao.create([address, usedAddress]) + it('usedAddressByWalletId', () => { + AddressDao.create([address, usedAddress]) - const walletOne = await AddressDao.usedAddressesByWalletId('1', AddressVersion.Testnet) + const walletOne = AddressDao.usedAddressesByWalletId('1', AddressVersion.Testnet) expect(walletOne.length).toEqual(0) - const walletTwo = await AddressDao.usedAddressesByWalletId('2', AddressVersion.Testnet) + const walletTwo = AddressDao.usedAddressesByWalletId('2', AddressVersion.Testnet) expect(walletTwo.length).toEqual(1) }) - it('findByAddress', async () => { - await AddressDao.create([address, usedAddress]) + it('findByAddress', () => { + AddressDao.create([address, usedAddress]) - const one = await AddressDao.findByAddress(address.address, address.walletId) + const one = AddressDao.findByAddress(address.address, address.walletId) expect(one!.address).toEqual(address.address) }) diff --git a/packages/neuron-wallet/tests/services/address.test.ts b/packages/neuron-wallet/tests/services/address.test.ts index 2fae0cdda5..a06ff9be13 100644 --- a/packages/neuron-wallet/tests/services/address.test.ts +++ b/packages/neuron-wallet/tests/services/address.test.ts @@ -1,7 +1,5 @@ import AddressService from '../../src/services/addresses' -import initConnection, { getConnection } from '../../src/database/address/ormconfig' -import AddressEntity, { AddressVersion } from '../../src/database/address/entities/address' -import AddressDao, { Address } from '../../src/database/address/dao' +import AddressDao, { Address, AddressVersion } from '../../src/database/address/address-dao' import { AddressType } from '../../src/models/keys/address' import { AccountExtendedPublicKey } from '../../src/models/keys/key' @@ -88,117 +86,98 @@ describe('Key tests with db', () => { version: AddressVersion.Testnet, } - beforeAll(async () => { - await initConnection() + beforeEach(() => { + AddressDao.deleteAll() }) - afterAll(async () => { - await getConnection().close() - }) - - beforeEach(async () => { - const connection = getConnection() - await connection.dropDatabase() - await connection.synchronize() - }) - - const generate = async (id: string = walletId) => { - await AddressService.generateAndSave(id, extendedKey, undefined, 0, 0, 2, 1) + const generate = (id: string = walletId) => { + AddressService.generateAndSave(id, extendedKey, undefined, 0, 0, 2, 1) } - const checkAndGenerate = async (id: string = walletId) => { - await AddressService.checkAndGenerateSave(id, extendedKey, undefined, 2, 1) + const checkAndGenerate = (id: string = walletId) => { + AddressService.checkAndGenerateSave(id, extendedKey, undefined, 2, 1) } - it('generateAndSave', async () => { - await generate() + it('generateAndSave', () => { + generate() - const all = await getConnection() - .getRepository(AddressEntity) - .createQueryBuilder('address') - .getMany() + const all = AddressDao.getAll() expect(all.length).toEqual((2 + 1) * 2) }) - it('checkAndGenerateSave', async () => { - await generate() + it('checkAndGenerateSave', () => { + generate() - const all = await getConnection() - .getRepository(AddressEntity) - .createQueryBuilder('address') - .getMany() + const all = AddressDao.getAll() const usedAll = all - .filter(one => one.addressType === AddressType.Receiving) .map(one => { - const entity = one - entity.txCount = 1 - return entity + if (one.addressType === AddressType.Receiving) { + const entity = one + entity.txCount = 1 + return entity + } else { + return one + } }) - await getConnection().manager.save(usedAll) + AddressDao.updateAll(usedAll) - await checkAndGenerate() + checkAndGenerate() - const final = await getConnection() - .getRepository(AddressEntity) - .createQueryBuilder('address') - .getMany() + const final = AddressDao.getAll() expect(final.length).toEqual((2 + 1) * 2 * 2) }) - it('generateAndSave with two wallet', async () => { - await generate() - await generate('2') - const all = await getConnection() - .getRepository(AddressEntity) - .createQueryBuilder('address') - .getMany() + it('generateAndSave with two wallet', () => { + generate() + generate('2') + const all = AddressDao.getAll() expect(all.length).toEqual((2 + 1) * 2 * 2) }) - it('isAddressUsed', async () => { - await AddressDao.create([address, usedAddress]) - const used = await AddressService.isAddressUsed(address.address, walletId) + it('isAddressUsed', () => { + AddressDao.create([address, usedAddress]) + const used = AddressService.isAddressUsed(address.address, walletId) expect(used).toBe(true) }) - it('nextUnusedAddress', async () => { - await AddressDao.create([address, usedAddress, changeAddress]) - const addr = await AddressService.nextUnusedAddress(walletId) - const addrDao = await AddressDao.nextUnusedAddress(walletId, AddressVersion.Testnet) - expect(addr).toEqual(addrDao && addrDao.toInterface()) + it('nextUnusedAddress', () => { + AddressDao.create([address, usedAddress, changeAddress]) + const addr = AddressService.nextUnusedAddress(walletId) + const addrDao = AddressDao.nextUnusedAddress(walletId, AddressVersion.Testnet) + expect(addr).toEqual(addrDao) }) - it('nextUnusedChangeAddress', async () => { - await AddressDao.create([address, usedAddress, changeAddress]) - const addr = await AddressService.nextUnusedChangeAddress(walletId) - const addrDao = await AddressDao.nextUnusedChangeAddress(walletId, AddressVersion.Testnet) - expect(addr).toEqual(addrDao && addrDao.toInterface()) + it('nextUnusedChangeAddress', () => { + AddressDao.create([address, usedAddress, changeAddress]) + const addr = AddressService.nextUnusedChangeAddress(walletId) + const addrDao = AddressDao.nextUnusedChangeAddress(walletId, AddressVersion.Testnet) + expect(addr).toEqual(addrDao) }) - it('allAddresses', async () => { - await generate() - await generate('2') - const all = await AddressService.allAddresses() + it('allAddresses', () => { + generate() + generate('2') + const all = AddressService.allAddresses() expect(all.length).toEqual(6) }) - it('allAddressesByWalletId', async () => { - await generate() - await generate('2') - const all = await AddressService.allAddressesByWalletId(walletId) + it('allAddressesByWalletId', () => { + generate() + generate('2') + const all = AddressService.allAddressesByWalletId(walletId) expect(all.length).toEqual(3) }) - it('usedAddress', async () => { - await AddressDao.create([address, usedAddress]) + it('usedAddress', () => { + AddressDao.create([address, usedAddress]) - const addr = await AddressService.usedAddresses(walletId) + const addr = AddressService.usedAddresses(walletId) expect(addr).toEqual([]) - const addr2 = await AddressService.usedAddresses('2') + const addr2 = AddressService.usedAddresses('2') expect(addr2).not.toEqual([]) }) }) diff --git a/packages/neuron-wallet/tests/services/wallets.test.ts b/packages/neuron-wallet/tests/services/wallets.test.ts index 81c01a53a5..0478b44101 100644 --- a/packages/neuron-wallet/tests/services/wallets.test.ts +++ b/packages/neuron-wallet/tests/services/wallets.test.ts @@ -3,13 +3,6 @@ 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 AddressService from '../../src/services/addresses' - -const mockDeleteAddressByWalletId = () => { - const mockDeleteAddress = jest.fn() - mockDeleteAddress.mockReturnValue(undefined) - AddressService.deleteByWalletId = mockDeleteAddress.bind(AddressService) -} describe('wallet service', () => { let walletService: WalletService @@ -121,7 +114,6 @@ describe('wallet service', () => { }) it('delete wallet', () => { - mockDeleteAddressByWalletId() const w1 = walletService.create(wallet1) walletService.create(wallet2) expect(walletService.getAll().length).toBe(2) @@ -154,7 +146,6 @@ describe('wallet service', () => { }) it('delete current wallet', () => { - mockDeleteAddressByWalletId() const w1 = walletService.create(wallet1) const w2 = walletService.create(wallet2) walletService.delete(w1.id) @@ -164,7 +155,6 @@ describe('wallet service', () => { }) it('delete none current wallet', () => { - mockDeleteAddressByWalletId() const w1 = walletService.create(wallet1) const w2 = walletService.create(wallet2) walletService.delete(w2.id) diff --git a/scripts/release-checksums.rb b/scripts/release-checksums.rb index ee0a6398c1..71dbcf2397 100644 --- a/scripts/release-checksums.rb +++ b/scripts/release-checksums.rb @@ -6,10 +6,10 @@ tag = ARGV[0] puts "Generating release checksums for #{tag}, this could take a while..." -windows_exe = "https://github.com/nervosnetwork/neuron/releases/download/#{tag}/Neuron-#{tag}-win-installer.exe" +windows_exe = "https://github.com/nervosnetwork/neuron/releases/download/#{tag}/Neuron-#{tag}-setup.exe" macos_zip = "https://github.com/nervosnetwork/neuron/releases/download/#{tag}/Neuron-#{tag}-mac.zip" -macos_dmg = "https://github.com/nervosnetwork/neuron/releases/download/#{tag}/Neuron-#{tag}-mac.dmg" -linux_appimage = "https://github.com/nervosnetwork/neuron/releases/download/#{tag}/Neuron-#{tag}-linux-x86_64.AppImage" +macos_dmg = "https://github.com/nervosnetwork/neuron/releases/download/#{tag}/Neuron-#{tag}.dmg" +linux_appimage = "https://github.com/nervosnetwork/neuron/releases/download/#{tag}/Neuron-#{tag}-x86_64.AppImage" def get_sha256_checksum(url) content = open(url).read