diff --git a/.ckb-version b/.ckb-version index 003d88e0df..a0633b073d 100644 --- a/.ckb-version +++ b/.ckb-version @@ -1 +1 @@ -v0.115.0 +v0.116.1 diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 4ca3d472ad..e77a974953 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: node: - - 18.12.0 + - 20.11.1 os: - macos-latest - ubuntu-20.04 diff --git a/CHANGELOG.md b/CHANGELOG.md index a35aea95a9..3c71658c54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +# 0.116.0 (2024-05-24) + +### CKB Node & Light Client + +- [CKB@v0.116.1](https://github.com/nervosnetwork/ckb/releases/tag/v0.116.1) was released on May. 11st, 2024. This version of CKB node is now bundled and preconfigured in Neuron. +- [CKB Light Client@v0.3.7](https://github.com/nervosnetwork/ckb-light-client/releases/tag/v0.3.7) was released on Apr. 13th, 2024. This version of CKB Light Client is now bundled and preconfigured in Neuron + +### Assumed valid target + +Block before `0x6dd077b407d019a0bce0cbad8c34e69a524ae4b2599b9feda2c7491f3559d32c`(at height `13,007,704`) will be skipped in validation.(https://github.com/nervosnetwork/neuron/pull/3157) + +--- + +[![Neuron@v0.116.0](https://github.com/Magickbase/neuron-public-issues/assets/7271329/ec10aa01-47fe-47a3-9636-3d4e86fc6c9b)](https://youtu.be/QXv8by2C8zU) + +YouTube: https://youtu.be/QXv8by2C8zU + +--- + +## New features + +- 3134: Support 'replace-by-fee' nervos dao transactions and sudt transactions.(@devchenyan) +- 3144: Reduce size of light client log in debug information and reveal start-block-number in log.(@yanguoyu) +- 3064: Support locking window by pin code.(@yanguoyu) +- 3131: Add detailed result for nervos dao transaction.(@devchenyan) + +## Bug fixes + +- 3121: Locate the first transaction on Explorer directly when users want to set the start-block-number for light client.(@yanguoyu) +- 3101: Show migration instruction properly.(@devchenyan) +- 3062: Migrate legacy ACP to active ACP account(@yanguoyu) +- 3141: Fix some issues about light client synchronizaiton.(@yanguoyu) +- 3120: Remove all sync data when start-block-number is set less than before.(@yanguoyu) + +**Full Changelog**: https://github.com/nervosnetwork/neuron/compare/v0.114.3...v0.116.0 + # 0.114.3 (2024-04-16) ### CKB Node & Light Client diff --git a/compatible.json b/compatible.json index bdf141600c..b2865b7a18 100644 --- a/compatible.json +++ b/compatible.json @@ -1,5 +1,6 @@ { "fullVersions": [ + "0.116", "0.115", "0.114", "0.113", @@ -21,6 +22,7 @@ "compatible": { "0.111": { "full": [ + "0.116", "0.115", "0.114", "0.113", @@ -36,6 +38,7 @@ }, "0.110": { "full": [ + "0.116", "0.115", "0.114", "0.113", @@ -67,6 +70,7 @@ }, "0.112": { "full": [ + "0.116", "0.115", "0.114", "0.113", @@ -82,6 +86,23 @@ }, "0.114": { "full": [ + "0.116", + "0.115", + "0.114", + "0.113", + "0.112", + "0.111", + "0.110", + "0.109" + ], + "light": [ + "0.3", + "0.2" + ] + }, + "0.116": { + "full": [ + "0.116", "0.115", "0.114", "0.113", diff --git a/lerna.json b/lerna.json index 0e77734cb4..922158bfd4 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "0.114.3", + "version": "0.116.0", "npmClient": "yarn", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } diff --git a/package.json b/package.json index 0f08c1da14..e8c55808df 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "neuron", "productName": "Neuron", "description": "CKB Neuron Wallet", - "version": "0.114.3", + "version": "0.116.0", "private": true, "author": { "name": "Nervos Core Dev", @@ -65,7 +65,8 @@ "resolutions": { "@types/react": "18.2.45", "react-i18next": ">=11.16.4", - "react-refresh": "0.14.0" + "react-refresh": "0.14.0", + "node-fetch": "2.6.13" }, "volta": { "node": "20.10.0" diff --git a/packages/neuron-ui/config-overrides.js b/packages/neuron-ui/config-overrides.js index aa4d60f53b..6222691d70 100644 --- a/packages/neuron-ui/config-overrides.js +++ b/packages/neuron-ui/config-overrides.js @@ -4,5 +4,6 @@ const path = require('path') module.exports = function override(config) { const webpackConfig = { ...config } webpackConfig.resolve.alias.electron = path.join(__dirname, 'src/electron-modules') + webpackConfig.resolve.fallback = { fs: false } return webpackConfig } diff --git a/packages/neuron-ui/package.json b/packages/neuron-ui/package.json index 9901985b4f..b98ed97822 100644 --- a/packages/neuron-ui/package.json +++ b/packages/neuron-ui/package.json @@ -1,6 +1,6 @@ { "name": "neuron-ui", - "version": "0.114.3", + "version": "0.116.0", "private": true, "author": { "name": "Nervos Core Dev", @@ -50,10 +50,13 @@ "last 2 chrome versions" ], "dependencies": { + "@ckb-lumos/bi": "0.21.1", + "@ckb-lumos/rpc": "0.21.1", "@ckb-lumos/base": "0.21.1", "@ckb-lumos/codec": "0.21.1", - "@nervosnetwork/ckb-sdk-core": "0.109.0", - "@nervosnetwork/ckb-sdk-utils": "0.109.0", + "@ckb-lumos/helpers": "0.21.1", + "@ckb-lumos/config-manager": "0.21.1", + "@ckb-lumos/common-scripts": "0.21.1", "canvg": "2.0.0", "i18next": "23.7.11", "immer": "9.0.21", @@ -90,7 +93,7 @@ "@types/styled-components": "5.1.34", "@wojtekmaj/enzyme-adapter-react-17": "0.8.0", "babel-jest": "25.5.1", - "electron": "28.1.0", + "electron": "30.0.0", "enzyme": "3.11.0", "enzyme-adapter-react-16": "1.15.7", "eslint-config-airbnb": "19.0.4", diff --git a/packages/neuron-ui/src/components/AddressBook/index.tsx b/packages/neuron-ui/src/components/AddressBook/index.tsx index 5bcd15bd13..19c5e5d7c3 100644 --- a/packages/neuron-ui/src/components/AddressBook/index.tsx +++ b/packages/neuron-ui/src/components/AddressBook/index.tsx @@ -118,7 +118,7 @@ const AddressBook = ({ onClose }: { onClose?: () => void }) => { return `${HIDE_BALANCE} CKB` } return ( - + {`${shannonToCKBFormatter(balance)} CKB`} ) diff --git a/packages/neuron-ui/src/components/AmendPendingTransactionDialog/amendPendingTransactionDialog.module.scss b/packages/neuron-ui/src/components/AmendPendingTransactionDialog/amendPendingTransactionDialog.module.scss new file mode 100644 index 0000000000..15c6aeef62 --- /dev/null +++ b/packages/neuron-ui/src/components/AmendPendingTransactionDialog/amendPendingTransactionDialog.module.scss @@ -0,0 +1,5 @@ +@import '../../styles/mixin.scss'; + +.content { + width: 680px; +} diff --git a/packages/neuron-ui/src/components/AmendPendingTransactionDialog/hooks.ts b/packages/neuron-ui/src/components/AmendPendingTransactionDialog/hooks.ts new file mode 100644 index 0000000000..cef45ef8e9 --- /dev/null +++ b/packages/neuron-ui/src/components/AmendPendingTransactionDialog/hooks.ts @@ -0,0 +1,236 @@ +import React, { useState, useCallback, useEffect, useMemo } from 'react' +import { PasswordIncorrectException } from 'exceptions' +import { TFunction } from 'i18next' +import { getTransaction as getOnChainTransaction } from 'services/chain' +import { getTransaction as getSentTransaction, sendTx, invokeShowErrorMessage } from 'services/remote' +import { isSuccessResponse, ErrorCode, shannonToCKBFormatter, scriptToAddress } from 'utils' +import { FEE_RATIO } from 'utils/const' + +export const useInitialize = ({ + tx, + walletID, + t, + onClose, +}: { + tx: State.Transaction + walletID: string + t: TFunction + onClose: () => void +}) => { + const [transaction, setTransaction] = useState(null) + const [generatedTx, setGeneratedTx] = useState(null) + const [size, setSize] = useState(0) + const [minPrice, setMinPrice] = useState('0') + const [price, setPrice] = useState('0') + const [password, setPassword] = useState('') + const [pwdError, setPwdError] = useState('') + const [isSending, setIsSending] = useState(false) + + const [isConfirmedAlertShown, setIsConfirmedAlertShown] = useState(false) + + const fee = useMemo(() => { + const ratio = BigInt(FEE_RATIO) + const base = BigInt(size) * BigInt(price) + const curFee = base / ratio + if (curFee * ratio < base) { + return curFee + BigInt(1) + } + return curFee + }, [price, size]) + + const fetchInitData = useCallback(async () => { + const res = await getOnChainTransaction(tx.hash) + const { + // @ts-expect-error Replace-By-Fee (RBF) + min_replace_fee: minFee, + transaction: { outputsData }, + } = res + + if (!minFee) { + setIsConfirmedAlertShown(true) + } + + const txRes = await getSentTransaction({ hash: tx.hash, walletID }) + + if (isSuccessResponse(txRes)) { + const txResult = txRes.result + setTransaction({ + ...txResult, + outputsData, + }) + + setSize(txResult.size) + if (minFee) { + const mPrice = ((BigInt(minFee) * BigInt(FEE_RATIO)) / BigInt(txResult.size)).toString() + setMinPrice(mPrice) + setPrice(mPrice) + } + } + }, [tx, setIsConfirmedAlertShown, setPrice, setTransaction, setSize, setMinPrice]) + + useEffect(() => { + fetchInitData() + }, []) + + const onPwdChange = useCallback( + (e: React.SyntheticEvent) => { + const { value } = e.target as HTMLInputElement + setPassword(value) + setPwdError('') + }, + [setPassword, setPwdError] + ) + + const onSubmit = useCallback(async () => { + try { + // @ts-expect-error Replace-By-Fee (RBF) + const { min_replace_fee: minFee } = await getOnChainTransaction(tx.hash) + if (!minFee) { + setIsConfirmedAlertShown(true) + return + } + + if (!generatedTx) { + return + } + setIsSending(true) + + try { + const skipLastInputs = generatedTx.inputs.length > generatedTx.witnesses.length + + const res = await sendTx({ walletID, tx: generatedTx, password, skipLastInputs, amendHash: tx.hash }) + + if (isSuccessResponse(res)) { + onClose() + } else if (res.status === ErrorCode.PasswordIncorrect) { + setPwdError(t(new PasswordIncorrectException().message)) + } else { + invokeShowErrorMessage({ + title: t('messages.error'), + content: typeof res.message === 'string' ? res.message : res.message.content!, + }) + } + } catch (err) { + console.warn(err) + } finally { + setIsSending(false) + } + } catch { + // ignore + } + }, [walletID, tx, setIsConfirmedAlertShown, setPwdError, password, generatedTx, setIsSending]) + + return { + fee, + price, + setPrice, + generatedTx, + setGeneratedTx, + transaction, + setTransaction, + minPrice, + isConfirmedAlertShown, + onSubmit, + password, + onPwdChange, + pwdError, + isSending, + setIsSending, + } +} + +export const useOutputs = ({ + transaction, + isMainnet, + addresses, + sUDTAccounts, + fee, +}: { + transaction: State.GeneratedTx | null + isMainnet: boolean + addresses: State.Address[] + sUDTAccounts: State.SUDTAccount[] + fee: bigint +}) => { + const getLastOutputAddress = (outputs: State.DetailedOutput[]) => { + if (outputs.length === 1) { + return scriptToAddress(outputs[0].lock, { isMainnet }) + } + + const change = outputs.find(output => { + const address = scriptToAddress(output.lock, { isMainnet }) + return !!addresses.find(item => item.address === address && item.type === 1) + }) + + if (change) { + return scriptToAddress(change.lock, { isMainnet }) + } + + const receive = outputs.find(output => { + const address = scriptToAddress(output.lock, { isMainnet }) + return !!addresses.find(item => item.address === address && item.type === 0) + }) + if (receive) { + return scriptToAddress(receive.lock, { isMainnet }) + } + + const sudt = outputs.find(output => { + const address = scriptToAddress(output.lock, { isMainnet }) + return !!sUDTAccounts.find(item => item.address === address) + }) + if (sudt) { + return scriptToAddress(sudt.lock, { isMainnet }) + } + return '' + } + + const items: { + address: string + amount: string + capacity: string + isLastOutput: boolean + output: State.DetailedOutput + }[] = useMemo(() => { + if (transaction && transaction.outputs.length) { + const lastOutputAddress = getLastOutputAddress(transaction.outputs) + return transaction.outputs.map(output => { + const address = scriptToAddress(output.lock, { isMainnet }) + return { + capacity: output.capacity, + address, + output, + amount: shannonToCKBFormatter(output.capacity || '0'), + isLastOutput: address === lastOutputAddress, + } + }) + } + return [] + }, [transaction?.outputs]) + + const outputsCapacity = useMemo(() => { + const outputList = items.filter(item => !item.isLastOutput) + return outputList.reduce((total, cur) => { + return total + BigInt(cur.capacity || '0') + }, BigInt(0)) + }, [items]) + + const lastOutputsCapacity = useMemo(() => { + if (transaction) { + const inputsCapacity = transaction.inputs.reduce((total, cur) => { + return total + BigInt(cur.capacity || '0') + }, BigInt(0)) + + return inputsCapacity - outputsCapacity - fee + } + return undefined + }, [transaction, fee, outputsCapacity]) + + return { + items, + lastOutputsCapacity, + } +} + +export default { + useInitialize, +} diff --git a/packages/neuron-ui/src/components/AmendPendingTransactionDialog/index.tsx b/packages/neuron-ui/src/components/AmendPendingTransactionDialog/index.tsx new file mode 100644 index 0000000000..49a52289b7 --- /dev/null +++ b/packages/neuron-ui/src/components/AmendPendingTransactionDialog/index.tsx @@ -0,0 +1,157 @@ +import React, { useEffect, useMemo, useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import { useState as useGlobalState } from 'states' +import TextField from 'widgets/TextField' +import Dialog from 'widgets/Dialog' +import { MIN_AMOUNT, DAO_DATA } from 'utils/const' +import { isMainnet as isMainnetUtil, localNumberFormatter, shannonToCKBFormatter } from 'utils' +import AlertDialog from 'widgets/AlertDialog' +import styles from './amendPendingTransactionDialog.module.scss' +import { useInitialize, useOutputs } from './hooks' + +const AmendPendingTransactionDialog = ({ tx, onClose }: { tx: State.Transaction; onClose: () => void }) => { + const { + wallet: { id: walletID = '', addresses }, + chain: { networkID }, + settings: { networks = [] }, + sUDTAccounts, + } = useGlobalState() + const { t } = useTranslation() + + const isMainnet = isMainnetUtil(networks, networkID) + + const { + fee, + price, + setPrice, + transaction, + onSubmit, + minPrice, + isConfirmedAlertShown, + password, + onPwdChange, + pwdError, + generatedTx, + setGeneratedTx, + isSending, + } = useInitialize({ + tx, + walletID, + t, + onClose, + }) + + const { items, lastOutputsCapacity } = useOutputs({ + transaction, + isMainnet, + addresses, + sUDTAccounts, + fee, + }) + + const priceError = useMemo(() => { + return Number(price) < Number(minPrice) ? t('price-switch.errorTip', { minPrice }) : null + }, [price, minPrice]) + + const inputHint = t('price-switch.hintTip', { suggestFeeRate: minPrice }) + + const handlePriceChange = useCallback( + (e: React.SyntheticEvent) => { + const { value: inputValue } = e.currentTarget + + const value = inputValue.split('.')[0].replace(/[^\d]/g, '') + setPrice(value) + }, + [setPrice] + ) + + useEffect(() => { + if (transaction && lastOutputsCapacity !== undefined) { + const outputs = items.map(item => { + const capacity = item.isLastOutput ? lastOutputsCapacity.toString() : item.capacity + if (item.output.data === DAO_DATA) { + // eslint-disable-next-line no-param-reassign + item.output.daoData = DAO_DATA + } + return { + ...item.output, + capacity, + } + }) + + setGeneratedTx({ + ...transaction, + outputs, + }) + } + }, [lastOutputsCapacity, transaction, items, setGeneratedTx]) + + const disabled = !!( + isSending || + !generatedTx || + priceError || + lastOutputsCapacity === undefined || + lastOutputsCapacity < MIN_AMOUNT + ) + + return ( + <> + +
+ + + + +
+
+ + + ) +} + +AmendPendingTransactionDialog.displayName = 'AmendPendingTransactionDialog' + +export default AmendPendingTransactionDialog diff --git a/packages/neuron-ui/src/components/AmendSUDTSend/hooks.ts b/packages/neuron-ui/src/components/AmendSUDTSend/hooks.ts index 3b17fe22a6..199ec5f37c 100644 --- a/packages/neuron-ui/src/components/AmendSUDTSend/hooks.ts +++ b/packages/neuron-ui/src/components/AmendSUDTSend/hooks.ts @@ -2,8 +2,9 @@ import React, { useState, useCallback, useEffect, useMemo } from 'react' import { TFunction } from 'i18next' import { AppActions } from 'states/stateProvider/reducer' import { getTransaction as getOnChainTransaction } from 'services/chain' -import { getTransaction as getSentTransaction, getTransactionSize, getTransactionList } from 'services/remote' +import { getTransaction as getSentTransaction, getTransactionList } from 'services/remote' import { isSuccessResponse } from 'utils' +import { FEE_RATIO } from 'utils/const' export const useInitialize = ({ hash, @@ -21,7 +22,7 @@ export const useInitialize = ({ const [minPrice, setMinPrice] = useState('0') const [price, setPrice] = useState('0') const [description, setDescription] = useState('') - const [showConfirmedAlert, setShowConfirmedAlert] = useState(false) + const [isConfirmedAlertShown, setIsConfirmedAlertShown] = useState(false) const [sudtInfo, setSudtInfo] = useState(null) const [txValue, setTxValue] = useState('0') @@ -34,7 +35,7 @@ export const useInitialize = ({ ) const fee = useMemo(() => { - const ratio = BigInt(1000) + const ratio = BigInt(FEE_RATIO) const base = BigInt(size) * BigInt(price) const curFee = base / ratio if (curFee * ratio < base) { @@ -50,7 +51,7 @@ export const useInitialize = ({ transaction: { outputsData }, } = await getOnChainTransaction(hash) if (!minFee) { - setShowConfirmedAlert(true) + setIsConfirmedAlertShown(true) } const listRes = await getTransactionList({ @@ -74,18 +75,14 @@ export const useInitialize = ({ setTransaction({ ...tx, outputsData }) - const sizeRes = await getTransactionSize(tx) - - if (isSuccessResponse(sizeRes) && typeof sizeRes.result === 'number') { - setSize(sizeRes.result) - if (minFee) { - const mPrice = ((BigInt(minFee) * BigInt(1000)) / BigInt(sizeRes.result)).toString() - setMinPrice(mPrice) - setPrice(mPrice) - } + setSize(tx.size) + if (minFee) { + const mPrice = ((BigInt(minFee) * BigInt(FEE_RATIO)) / BigInt(tx.size)).toString() + setMinPrice(mPrice) + setPrice(mPrice) } } - }, [hash, setShowConfirmedAlert, setPrice, setTransaction, setSize, setMinPrice]) + }, [hash, setIsConfirmedAlertShown, setPrice, setTransaction, setSize, setMinPrice]) useEffect(() => { fetchInitData() @@ -104,7 +101,7 @@ export const useInitialize = ({ // @ts-expect-error Replace-By-Fee (RBF) const { min_replace_fee: minFee } = await getOnChainTransaction(hash) if (!minFee) { - setShowConfirmedAlert(true) + setIsConfirmedAlertShown(true) return } @@ -123,7 +120,7 @@ export const useInitialize = ({ // ignore } }, - [dispatch, walletID, hash, setShowConfirmedAlert, transaction] + [dispatch, walletID, hash, setIsConfirmedAlertShown, transaction] ) return { @@ -135,7 +132,7 @@ export const useInitialize = ({ transaction, setTransaction, minPrice, - showConfirmedAlert, + isConfirmedAlertShown, onSubmit, sudtInfo, txValue, diff --git a/packages/neuron-ui/src/components/AmendSUDTSend/index.tsx b/packages/neuron-ui/src/components/AmendSUDTSend/index.tsx index 9ed69091e4..5cdbc79812 100644 --- a/packages/neuron-ui/src/components/AmendSUDTSend/index.tsx +++ b/packages/neuron-ui/src/components/AmendSUDTSend/index.tsx @@ -7,17 +7,18 @@ import PageContainer from 'components/PageContainer' import Button from 'widgets/Button' import Spinner from 'widgets/Spinner' import { GoBack } from 'widgets/Icons/icon' -import { scriptToAddress } from '@nervosnetwork/ckb-sdk-utils' import { isMainnet as isMainnetUtil, localNumberFormatter, useGoBack, + scriptToAddress, shannonToCKBFormatter, sudtValueToAmount, sUDTAmountFormatter, } from 'utils' import { DEFAULT_SUDT_FIELDS } from 'utils/const' import AlertDialog from 'widgets/AlertDialog' +import { useOutputs } from 'components/AmendPendingTransactionDialog/hooks' import styles from './amendSUDTSend.module.scss' import { useInitialize } from './hooks' @@ -48,7 +49,7 @@ const AmendSUDTSend = () => { transaction, onSubmit, minPrice, - showConfirmedAlert, + isConfirmedAlertShown, sudtInfo, description, onDescriptionChange, @@ -61,6 +62,14 @@ const AmendSUDTSend = () => { t, }) + const { items, lastOutputsCapacity } = useOutputs({ + transaction, + isMainnet, + addresses, + sUDTAccounts, + fee, + }) + const priceError = useMemo(() => { return Number(price || '0') < Number(minPrice) ? t('price-switch.errorTip', { minPrice }) : null }, [price, minPrice]) @@ -71,7 +80,7 @@ const AmendSUDTSend = () => { (e: React.SyntheticEvent) => { const { value: inputValue } = e.currentTarget - const value = inputValue.split('.')[0].replace(/[^\d]/, '') + const value = inputValue.split('.')[0].replace(/[^\d]/g, '') setPrice(value) }, [setPrice] @@ -83,95 +92,20 @@ const AmendSUDTSend = () => { const list = sUDTAccounts.map(item => item.address) const to = transaction?.outputs.find(output => { - const address = scriptToAddress(output.lock, isMainnet) + const address = scriptToAddress(output.lock, { isMainnet }) if (list.includes(address) || (sudtInfo && !output.type)) { return false } return true }) if (to) { - return scriptToAddress(to.lock, isMainnet) - } - return scriptToAddress(transaction?.outputs[0].lock, isMainnet) - }, [transaction?.outputs]) - - const getLastOutputAddress = (outputs: State.DetailedOutput[]) => { - const change = outputs.find(output => { - const address = scriptToAddress(output.lock, isMainnet) - - return !!addresses.find(item => item.address === address && item.type === 1) - }) - if (change) { - return scriptToAddress(change.lock, isMainnet) - } - - const receive = outputs.find(output => { - const address = scriptToAddress(output.lock, isMainnet) - return !!addresses.find(item => item.address === address && item.type === 0) - }) - if (receive) { - return scriptToAddress(receive.lock, isMainnet) + return scriptToAddress(to.lock, { isMainnet }) } - - const sudt = outputs.find(output => { - const address = scriptToAddress(output.lock, isMainnet) - return !!sUDTAccounts.find(item => item.address === address) - }) - if (sudt) { - return scriptToAddress(sudt.lock, isMainnet) - } - return '' - } - - const items: { - address: string - amount: string - capacity: string - isLastOutput: boolean - output: State.DetailedOutput - }[] = useMemo(() => { - if (transaction && transaction.outputs.length) { - const lastOutputAddress = getLastOutputAddress(transaction.outputs) - return transaction.outputs.map(output => { - const address = scriptToAddress(output.lock, isMainnet) - return { - capacity: output.capacity, - address, - output, - amount: shannonToCKBFormatter(output.capacity || '0'), - isLastOutput: address === lastOutputAddress, - } - }) - } - return [] + return scriptToAddress(transaction?.outputs[0].lock, { isMainnet }) }, [transaction?.outputs]) - const outputsCapacity = useMemo(() => { - const outputList = items.filter(item => !item.isLastOutput) - return outputList.reduce((total, cur) => { - if (Number.isNaN(+(cur.capacity || ''))) { - return total - } - return total + BigInt(cur.capacity || '0') - }, BigInt(0)) - }, [items]) - - const lastOutputsCapacity = useMemo(() => { - if (transaction) { - const inputsCapacity = transaction.inputs.reduce((total, cur) => { - if (Number.isNaN(+(cur.capacity || ''))) { - return total - } - return total + BigInt(cur.capacity || '0') - }, BigInt(0)) - - return inputsCapacity - outputsCapacity - fee - } - return -1 - }, [transaction, fee, outputsCapacity]) - useEffect(() => { - if (transaction) { + if (transaction && lastOutputsCapacity !== undefined) { const outputs = items.map(item => { const capacity = item.isLastOutput ? lastOutputsCapacity.toString() : item.capacity return { @@ -192,7 +126,8 @@ const AmendSUDTSend = () => { } }, [lastOutputsCapacity, transaction, items, dispatch, experimental?.params?.description, description]) - const disabled = sending || !experimental?.tx || priceError || lastOutputsCapacity < 0 + const disabled = + sending || !experimental?.tx || priceError || lastOutputsCapacity === undefined || lastOutputsCapacity < 0 return ( { { dispatch({ @@ -14,7 +15,7 @@ const clear = (dispatch: StateDispatch) => { const useUpdateTransactionPrice = (dispatch: StateDispatch) => useCallback( (value: string) => { - const price = value.split('.')[0].replace(/[^\d]/, '') + const price = value.split('.')[0].replace(/[^\d]/g, '') dispatch({ type: AppActions.UpdateSendPrice, payload: price, @@ -51,13 +52,13 @@ export const useInitialize = ({ const [transaction, setTransaction] = useState(null) const [size, setSize] = useState(0) const [minPrice, setMinPrice] = useState('0') - const [showConfirmedAlert, setShowConfirmedAlert] = useState(false) + const [isConfirmedAlertShown, setIsConfirmedAlertShown] = useState(false) const updateTransactionPrice = useUpdateTransactionPrice(dispatch) const onDescriptionChange = useSendDescriptionChange(dispatch) const fee = useMemo(() => { - const ratio = BigInt(1000) + const ratio = BigInt(FEE_RATIO) const base = BigInt(size) * BigInt(price) const curFee = base / ratio if (curFee * ratio < base) { @@ -74,7 +75,7 @@ export const useInitialize = ({ transaction: { outputsData }, } = res if (!minFee) { - setShowConfirmedAlert(true) + setIsConfirmedAlertShown(true) } const txRes = await getSentTransaction({ hash, walletID }) @@ -85,18 +86,14 @@ export const useInitialize = ({ outputsData, }) - const sizeRes = await getTransactionSize(tx) - - if (isSuccessResponse(sizeRes) && typeof sizeRes.result === 'number') { - setSize(sizeRes.result) - if (minFee) { - const mPrice = ((BigInt(minFee) * BigInt(1000)) / BigInt(sizeRes.result)).toString() - setMinPrice(mPrice) - updateTransactionPrice(mPrice) - } + setSize(tx.size) + if (minFee) { + const mPrice = ((BigInt(minFee) * BigInt(FEE_RATIO)) / BigInt(tx.size)).toString() + setMinPrice(mPrice) + updateTransactionPrice(mPrice) } } - }, [hash, setShowConfirmedAlert, updateTransactionPrice, setTransaction, setSize, setMinPrice]) + }, [hash, setIsConfirmedAlertShown, updateTransactionPrice, setTransaction, setSize, setMinPrice]) useEffect(() => { fetchInitData() @@ -119,7 +116,7 @@ export const useInitialize = ({ // @ts-expect-error Replace-By-Fee (RBF) const { min_replace_fee: minFee } = await getOnChainTransaction(hash) if (!minFee) { - setShowConfirmedAlert(true) + setIsConfirmedAlertShown(true) return } dispatch({ @@ -134,7 +131,7 @@ export const useInitialize = ({ // ignore } }, - [dispatch, walletID, hash, setShowConfirmedAlert] + [dispatch, walletID, hash, setIsConfirmedAlertShown] ) return { @@ -144,7 +141,7 @@ export const useInitialize = ({ transaction, setTransaction, minPrice, - showConfirmedAlert, + isConfirmedAlertShown, onSubmit, } } diff --git a/packages/neuron-ui/src/components/AmendSend/index.tsx b/packages/neuron-ui/src/components/AmendSend/index.tsx index 543ff7c464..7ba3873d1e 100644 --- a/packages/neuron-ui/src/components/AmendSend/index.tsx +++ b/packages/neuron-ui/src/components/AmendSend/index.tsx @@ -8,11 +8,11 @@ import Button from 'widgets/Button' import Spinner from 'widgets/Spinner' import { GoBack } from 'widgets/Icons/icon' import { MIN_AMOUNT } from 'utils/const' -import { scriptToAddress } from '@nervosnetwork/ckb-sdk-utils' import { isMainnet as isMainnetUtil, localNumberFormatter, useGoBack, + scriptToAddress, shannonToCKBFormatter, RoutePath, isSecp256k1Address, @@ -41,7 +41,7 @@ const AmendSend = () => { const isMainnet = isMainnetUtil(networks, networkID) - const { fee, updateTransactionPrice, onDescriptionChange, transaction, onSubmit, minPrice, showConfirmedAlert } = + const { fee, updateTransactionPrice, onDescriptionChange, transaction, onSubmit, minPrice, isConfirmedAlertShown } = useInitialize({ hash, walletID, @@ -68,11 +68,11 @@ const AmendSend = () => { const getLastOutputAddress = (outputs: State.DetailedOutput[]) => { if (outputs.length === 1) { - return scriptToAddress(outputs[0].lock, isMainnet) + return scriptToAddress(outputs[0].lock, { isMainnet }) } const change = outputs.find(output => { - const address = scriptToAddress(output.lock, isMainnet) + const address = scriptToAddress(output.lock, { isMainnet }) if (!isSecp256k1Address(address)) { navigate(`${RoutePath.History}/amendSUDTSend/${hash}`, { replace: true, @@ -82,20 +82,29 @@ const AmendSend = () => { return !!addresses.find(item => item.address === address && item.type === 1) }) if (change) { - return scriptToAddress(change.lock, isMainnet) + return scriptToAddress(change.lock, { isMainnet }) } const receive = outputs.find(output => { - const address = scriptToAddress(output.lock, isMainnet) + const address = scriptToAddress(output.lock, { isMainnet }) return !!addresses.find(item => item.address === address && item.type === 0) }) if (receive) { - return scriptToAddress(receive.lock, isMainnet) + return scriptToAddress(receive.lock, { isMainnet }) } return '' } + const inputsCapacity = useMemo(() => { + if (transaction) { + return transaction.inputs.reduce((total, cur) => { + return total + BigInt(cur.capacity || '0') + }, BigInt(0)) + } + return undefined + }, [transaction]) + const items: { address: string amount: string @@ -103,28 +112,29 @@ const AmendSend = () => { isLastOutput: boolean output: State.DetailedOutput }[] = useMemo(() => { - if (transaction && transaction.outputs.length) { + if (transaction && transaction.outputs.length && inputsCapacity) { const lastOutputAddress = getLastOutputAddress(transaction.outputs) return transaction.outputs.map(output => { - const address = scriptToAddress(output.lock, isMainnet) + const address = scriptToAddress(output.lock, { isMainnet }) + const capacity = + transaction.outputs.length === 1 && address === lastOutputAddress + ? (inputsCapacity - fee).toString() + : output.capacity return { - capacity: output.capacity, + capacity, address, output, - amount: shannonToCKBFormatter(output.capacity || '0'), + amount: shannonToCKBFormatter(capacity || '0'), isLastOutput: address === lastOutputAddress, } }) } return [] - }, [transaction?.outputs]) + }, [transaction?.outputs, inputsCapacity, fee]) const outputsCapacity = useMemo(() => { - const outputList = items.filter(item => !item.isLastOutput) + const outputList = items.length === 1 ? items : items.filter(item => !item.isLastOutput) return outputList.reduce((total, cur) => { - if (Number.isNaN(+(cur.capacity || ''))) { - return total - } return total + BigInt(cur.capacity || '0') }, BigInt(0)) }, [items]) @@ -132,21 +142,18 @@ const AmendSend = () => { const totalAmount = shannonToCKBFormatter(outputsCapacity.toString()) const lastOutputsCapacity = useMemo(() => { - if (transaction) { - const inputsCapacity = transaction.inputs.reduce((total, cur) => { - if (Number.isNaN(+(cur.capacity || ''))) { - return total - } - return total + BigInt(cur.capacity || '0') - }, BigInt(0)) + if (inputsCapacity) { + if (items.length === 1) { + return BigInt(items[0].capacity || '0') + } return inputsCapacity - outputsCapacity - fee } - return -1 - }, [transaction, fee, outputsCapacity]) + return undefined + }, [inputsCapacity, fee, outputsCapacity, items]) useEffect(() => { - if (transaction) { + if (transaction && lastOutputsCapacity !== undefined) { const outputs = items.map(item => { const capacity = item.isLastOutput ? lastOutputsCapacity.toString() : item.capacity return { @@ -164,7 +171,8 @@ const AmendSend = () => { } }, [lastOutputsCapacity, transaction, items, dispatch]) - const disabled = sending || !send.generatedTx || priceError || lastOutputsCapacity < MIN_AMOUNT + const disabled = + sending || !send.generatedTx || priceError || lastOutputsCapacity === undefined || lastOutputsCapacity < MIN_AMOUNT return ( { { - const address = useMemo( - () => (cell.lock ? ckbCore.utils.scriptToAddress(cell.lock, isMainnet) : ''), - [cell, isMainnet] - ) + const address = useMemo(() => (cell.lock ? scriptToAddress(cell.lock, { isMainnet }) : ''), [cell, isMainnet]) return (
diff --git a/packages/neuron-ui/src/components/Balance/index.tsx b/packages/neuron-ui/src/components/Balance/index.tsx index dec27a4b9d..6a2b073e35 100644 --- a/packages/neuron-ui/src/components/Balance/index.tsx +++ b/packages/neuron-ui/src/components/Balance/index.tsx @@ -17,7 +17,7 @@ const Balance = ({ balance, connectionStatus, syncStatus }: BalanceProps) => { return ( <> {`${t('overview.balance')}:`} - + {shannonToCKBFormatter(balance)} diff --git a/packages/neuron-ui/src/components/DepositDialog/depositDialog.module.scss b/packages/neuron-ui/src/components/DepositDialog/depositDialog.module.scss index b2fc0b5edf..a410263179 100644 --- a/packages/neuron-ui/src/components/DepositDialog/depositDialog.module.scss +++ b/packages/neuron-ui/src/components/DepositDialog/depositDialog.module.scss @@ -64,19 +64,10 @@ } .fee { - display: flex; - justify-content: space-between; font-size: 14px; line-height: 20px; margin-top: 4px; color: var(--main-text-color); - gap: 24px; - - & > div { - &:first-child { - flex-shrink: 0; - } - } } .rewards { @@ -128,6 +119,7 @@ .isBalanceReserved { display: flex; align-items: center; + margin-top: 12px; & > input { display: none; @@ -162,3 +154,22 @@ } } } + +.notification { + color: var(--notice-text-color); + display: flex; + gap: 6px; + justify-content: center; + align-items: center; + word-break: break-word; + margin-top: 12px; + background-color: var(--warn-background-color); + font-size: 12px; + line-height: 24px; + padding: 8px 14px; + border: 1px solid rgba(252, 136, 0, 0.2); + border-radius: 4px; + span { + font-weight: 500; + } +} diff --git a/packages/neuron-ui/src/components/DepositDialog/hooks.ts b/packages/neuron-ui/src/components/DepositDialog/hooks.ts index 41fa272d06..56117708e0 100644 --- a/packages/neuron-ui/src/components/DepositDialog/hooks.ts +++ b/packages/neuron-ui/src/components/DepositDialog/hooks.ts @@ -136,7 +136,7 @@ export const useGenerateDaoDepositTx = ({ payload: res, }) if (isDepositAll) { - setMaxDepositValue(shannonToCKBFormatter(res?.outputs[0]?.capacity ?? '0', false, '')) + setMaxDepositValue(shannonToCKBFormatter(res?.outputs[0]?.capacity ?? '0', false, false)) if (!isBalanceReserved) { setErrorMessage(t('messages.remain-ckb-for-withdraw')) } @@ -181,7 +181,7 @@ export const useDepositValue = (balance: string, showDepositDialog: boolean) => const amount = shannonToCKBFormatter( ((BigInt(percent) * BigInt(balance)) / BigInt(PERCENT_100)).toString(), false, - '' + false ) setDepositValue(padFractionDigitsIfDecimal(amount, 8)) }, @@ -238,10 +238,10 @@ export const useBalanceReserved = () => { } export const useOnDepositDialogSubmit = ({ - onCloseDepositDialog, + onDepositSuccess, walletID, }: { - onCloseDepositDialog: () => void + onDepositSuccess: () => void walletID: string }) => { const dispatch = useDispatch() @@ -251,10 +251,10 @@ export const useOnDepositDialogSubmit = ({ payload: { walletID, actionType: 'send', + onSuccess: onDepositSuccess, }, }) - onCloseDepositDialog() - }, [dispatch, walletID, onCloseDepositDialog]) + }, [dispatch, walletID, onDepositSuccess]) } export const useOnDepositDialogCancel = ({ diff --git a/packages/neuron-ui/src/components/DepositDialog/index.tsx b/packages/neuron-ui/src/components/DepositDialog/index.tsx index abc69ca05f..057fa087b6 100644 --- a/packages/neuron-ui/src/components/DepositDialog/index.tsx +++ b/packages/neuron-ui/src/components/DepositDialog/index.tsx @@ -8,6 +8,7 @@ import { localNumberFormatter, shannonToCKBFormatter } from 'utils' import { Attention, Success } from 'widgets/Icons/icon' import Dialog from 'widgets/Dialog' import Tooltip from 'widgets/Tooltip' +import Alert from 'widgets/Alert' import styles from './depositDialog.module.scss' import { useBalanceReserved, @@ -31,6 +32,7 @@ interface DepositDialogProps { suggestFeeRate: number walletID: string globalAPC: number + onDepositSuccess: () => void } const RfcLink = React.memo(() => ( @@ -57,6 +59,7 @@ const DepositDialog = ({ isTxGenerated, suggestFeeRate, globalAPC, + onDepositSuccess, }: DepositDialogProps) => { const [t, { language }] = useTranslation() const disabled = !isTxGenerated @@ -73,7 +76,7 @@ const DepositDialog = ({ showDepositDialog: show, slidePercent, }) - const onConfirm = useOnDepositDialogSubmit({ onCloseDepositDialog, walletID }) + const onConfirm = useOnDepositDialogSubmit({ onDepositSuccess, walletID }) const onCancel = useOnDepositDialogCancel({ onCloseDepositDialog, resetDepositValue, setIsBalanceReserved }) const onSubmit = useCallback( (e: React.FormEvent) => { @@ -189,6 +192,9 @@ const DepositDialog = ({

{globalAPC}%

+ + {t('nervos-dao.attention')} + )} diff --git a/packages/neuron-ui/src/components/DepositRulesDialog/depositRulesDialog.module.scss b/packages/neuron-ui/src/components/DepositRulesDialog/depositRulesDialog.module.scss new file mode 100644 index 0000000000..22455d7c0d --- /dev/null +++ b/packages/neuron-ui/src/components/DepositRulesDialog/depositRulesDialog.module.scss @@ -0,0 +1,28 @@ +@import '../../styles/mixin.scss'; + +.container { + width: 680px; + + .content { + background: var(--table-head-background-color); + border-radius: 8px; + + .item { + padding: 16px; + display: flex; + justify-content: space-between; + border-bottom: 1px solid var(--divide-line-color); + font-size: 14px; + color: var(--main-text-color); + &:last-child { + border-bottom: none; + } + p { + margin: 0; + } + .description { + font-weight: 500; + } + } + } +} diff --git a/packages/neuron-ui/src/components/DepositRulesDialog/index.tsx b/packages/neuron-ui/src/components/DepositRulesDialog/index.tsx new file mode 100644 index 0000000000..b934091fbe --- /dev/null +++ b/packages/neuron-ui/src/components/DepositRulesDialog/index.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import { useTranslation } from 'react-i18next' +import Dialog from 'widgets/Dialog' +import styles from './depositRulesDialog.module.scss' + +const DepositRulesDialog = ({ show, onClose }: { show: boolean; onClose: () => void }) => { + const [t] = useTranslation() + + return ( + +
+ {[ + // TODO: calculating by the capacity of the lock + ['deposit-rules.minimum-deposit', '102 CKB'], + ['deposit-rules.single-compensation-cycle', 'deposit-rules.single-compensation-cycle-description'], + ['deposit-rules.withdraw', 'deposit-rules.withdraw-description'], + ['deposit-rules.unlock', 'deposit-rules.unlock-description'], + ].map(([title, description]) => ( +
+

{t(title)}

+

{t(description)}

+
+ ))} +
+
+ ) +} + +DepositRulesDialog.displayName = 'DepositRulesDialog' + +export default DepositRulesDialog diff --git a/packages/neuron-ui/src/components/GeneralSetting/LockWindowDialog/hooks.ts b/packages/neuron-ui/src/components/GeneralSetting/LockWindowDialog/hooks.ts new file mode 100644 index 0000000000..a1213dc389 --- /dev/null +++ b/packages/neuron-ui/src/components/GeneralSetting/LockWindowDialog/hooks.ts @@ -0,0 +1,113 @@ +import { TFunction } from 'i18next' +import { useCallback, useState } from 'react' +import { verifyLockWindowPassword } from 'services/remote' +import { updateLockWindowInfo, useDispatch } from 'states' +import { isSuccessResponse } from 'utils' + +export const passwordLength = 4 + +export const useOldPassword = ({ t }: { t: TFunction }) => { + const [oldPasswordErr, setOldPasswordErr] = useState('') + const [oldPassword, setOldPassword] = useState(new Array(passwordLength).fill('')) + const [verifySuccess, setVerifySuccess] = useState(false) + const onUpdateOldPassword = useCallback( + (v: string, idx: number) => { + const updatedOldPassword = oldPassword.toSpliced(idx, 1, v).join('') + if (updatedOldPassword.length === passwordLength) { + verifyLockWindowPassword(updatedOldPassword) + .then(res => { + if (isSuccessResponse(res)) { + // verify success + setVerifySuccess(true) + } + throw new Error('verify failed') + }) + .catch(() => { + setOldPassword(new Array(passwordLength).fill('')) + setOldPasswordErr(t('settings.general.lock-window.password-error')) + }) + } else { + setOldPassword(value => value.toSpliced(idx, 1, v)) + } + }, + [oldPassword] + ) + const resetOldPassword = useCallback(() => { + setOldPasswordErr('') + setOldPassword(new Array(passwordLength).fill('')) + setVerifySuccess(false) + }, []) + return { + oldPasswordErr, + oldPassword, + onUpdateOldPassword, + verifySuccess, + setVerifySuccess, + resetOldPassword, + } +} + +export const usePassword = () => { + const [password, setPassword] = useState(new Array(passwordLength).fill('')) + const onUpdatePassword = useCallback((v: string, idx: number) => { + setPassword(value => value.toSpliced(idx, 1, v)) + }, []) + const resetPassword = useCallback(() => { + setPassword(new Array(passwordLength).fill('')) + }, []) + return { + password, + onUpdatePassword, + resetPassword, + } +} + +export const useRepeatPassword = ({ + password, + t, + encryptedPassword, + onCancel, +}: { + password: string + t: TFunction + encryptedPassword?: string + onCancel: () => void +}) => { + const dispatch = useDispatch() + const [errMsg, setErrMsg] = useState('') + const [repeatPassword, setRepeatPassword] = useState(new Array(passwordLength).fill('')) + const [isSuccess, setIsSuccess] = useState(false) + const onUpdateRepeatPassword = useCallback( + (v: string, idx: number) => { + const updatedRepeatPassword = repeatPassword.toSpliced(idx, 1, v).join('') + if (updatedRepeatPassword.length === passwordLength) { + if (updatedRepeatPassword !== password) { + setErrMsg(t('settings.general.lock-window.different-password')) + setRepeatPassword(new Array(passwordLength).fill('')) + } else { + setIsSuccess(true) + updateLockWindowInfo( + encryptedPassword ? { password: updatedRepeatPassword } : { password: updatedRepeatPassword, locked: true } + )(dispatch) + onCancel() + } + } else { + setErrMsg('') + setRepeatPassword(value => value.toSpliced(idx, 1, v)) + } + }, + [password, t, repeatPassword] + ) + const resetRepeatPassword = useCallback(() => { + setErrMsg('') + setRepeatPassword(new Array(passwordLength).fill('')) + setIsSuccess(false) + }, []) + return { + errMsg, + repeatPassword, + onUpdateRepeatPassword, + resetRepeatPassword, + isSuccess, + } +} diff --git a/packages/neuron-ui/src/components/GeneralSetting/LockWindowDialog/index.tsx b/packages/neuron-ui/src/components/GeneralSetting/LockWindowDialog/index.tsx new file mode 100644 index 0000000000..629eda59f6 --- /dev/null +++ b/packages/neuron-ui/src/components/GeneralSetting/LockWindowDialog/index.tsx @@ -0,0 +1,103 @@ +import React, { useCallback, useEffect, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import Dialog from 'widgets/Dialog' +import SplitPasswordInput from 'widgets/SplitPasswordInput' +import Alert from 'widgets/Alert' +import Button from 'widgets/Button' +import { passwordLength, useOldPassword, usePassword, useRepeatPassword } from './hooks' +import styles from './lockWindowDialog.module.scss' + +const LockWindowDialog = ({ + show, + onCancel, + encryptedPassword, +}: { + show: boolean + onCancel: () => void + encryptedPassword?: string +}) => { + const [t] = useTranslation() + const { oldPasswordErr, verifySuccess, oldPassword, onUpdateOldPassword, resetOldPassword } = useOldPassword({ t }) + const { password, onUpdatePassword, resetPassword } = usePassword() + const joinedPassword = useMemo(() => password.join(''), [password]) + const { errMsg, repeatPassword, onUpdateRepeatPassword, resetRepeatPassword, isSuccess } = useRepeatPassword({ + t, + password: joinedPassword, + encryptedPassword, + onCancel, + }) + useEffect(() => { + // when dialog open, reset all status + if (show) { + resetOldPassword() + resetPassword() + resetRepeatPassword() + } + }, [show, resetOldPassword, resetPassword, resetRepeatPassword]) + const onReset = useCallback(() => { + resetPassword() + resetRepeatPassword() + }, [resetPassword, resetRepeatPassword]) + const content = () => { + if (encryptedPassword && !verifySuccess) { + // is verify old password + return ( + <> + {t('settings.general.lock-window.enter-current-password')} +
+ +
{oldPasswordErr}
+
+ + ) + } + if (joinedPassword.length !== passwordLength) { + // enter password + return ( + <> + {t(`settings.general.lock-window.set-password`)} +
+ +
+ + ) + } + // is verify repeat password + return ( + <> + {t(`settings.general.lock-window.confirm-password`)} +
+ +
{errMsg}
+ {errMsg ? ( + + ) : null} +
+ + ) + } + + return ( + <> + +
{content()}
+
+ {isSuccess ? ( + + {t(`settings.general.lock-window.${encryptedPassword ? 'change-password-success' : 'set-password-success'}`)} + + ) : null} + + ) +} + +LockWindowDialog.displayName = 'LockWindowDialog' +export default LockWindowDialog diff --git a/packages/neuron-ui/src/components/GeneralSetting/LockWindowDialog/lockWindowDialog.module.scss b/packages/neuron-ui/src/components/GeneralSetting/LockWindowDialog/lockWindowDialog.module.scss new file mode 100644 index 0000000000..f4c099a097 --- /dev/null +++ b/packages/neuron-ui/src/components/GeneralSetting/LockWindowDialog/lockWindowDialog.module.scss @@ -0,0 +1,30 @@ +@import '../../../styles/mixin.scss'; + +.content { + text-align: center; + + .secTitle { + color: var(--secondary-text-color); + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; + } + + .password { + margin-top: 24px; + } + + .err { + color: var(--error-color); + margin: 16px 0 24px 0; + } +} + +.dialog { + width: 680px; +} + +.notice { + @include dialog-copy-animation; +} diff --git a/packages/neuron-ui/src/components/GeneralSetting/generalSetting.module.scss b/packages/neuron-ui/src/components/GeneralSetting/generalSetting.module.scss index bd41cc7b53..92e5f8d34f 100644 --- a/packages/neuron-ui/src/components/GeneralSetting/generalSetting.module.scss +++ b/packages/neuron-ui/src/components/GeneralSetting/generalSetting.module.scss @@ -38,6 +38,11 @@ $action-button-width: 11.25rem; margin-right: 4px; } } + + &.lockWindow { + width: auto; + min-width: 176px; + } } .showVersion { position: relative; diff --git a/packages/neuron-ui/src/components/GeneralSetting/index.tsx b/packages/neuron-ui/src/components/GeneralSetting/index.tsx index bc0dcc5ba0..45216d4d9b 100644 --- a/packages/neuron-ui/src/components/GeneralSetting/index.tsx +++ b/packages/neuron-ui/src/components/GeneralSetting/index.tsx @@ -9,11 +9,12 @@ import { ReactComponent as ArrowNext } from 'widgets/Icons/ArrowNext.svg' import { ReactComponent as Update } from 'widgets/Icons/Update.svg' import { cancelCheckUpdates, downloadUpdate, installUpdate, getVersion } from 'services/remote' import { uniformTimeFormatter, bytesFormatter, clsx, wakeScreen, releaseWakeLock } from 'utils' -import { LanguageSelect } from 'widgets/Icons/icon' import Switch from 'widgets/Switch' import { keepScreenAwake } from 'services/localCache' +import { LanguageSelect, UnLock } from 'widgets/Icons/icon' import styles from './generalSetting.module.scss' import { useCheckUpdate, useUpdateDownloadStatus } from './hooks' +import LockWindowDialog from './LockWindowDialog' interface UpdateDownloadStatusProps { show: boolean @@ -136,11 +137,13 @@ const UpdateDownloadStatus = ({ interface GeneralSettingProps { updater: State.AppUpdater + app: State.App } -const GeneralSetting = ({ updater }: GeneralSettingProps) => { +const GeneralSetting = ({ updater, app }: GeneralSettingProps) => { const [t, i18n] = useTranslation() const [showLangDialog, setShowLangDialog] = useState(false) + const [isLockDialogShow, setIsLockDialogShow] = useState(false) const [searchParams] = useSearchParams() const [errorMsg, setErrorMsg] = useState('') const { showCheckDialog, setShowCheckDialog, onCancelCheckUpdates } = useCheckUpdate() @@ -221,6 +224,22 @@ const GeneralSetting = ({ updater }: GeneralSettingProps) => { +
+

{t('settings.general.lock-password')}

+ +
+ { setShowLangDialog(false) }} /> + + { + setIsLockDialogShow(false) + }} + /> ) } diff --git a/packages/neuron-ui/src/components/HDWalletSign/index.tsx b/packages/neuron-ui/src/components/HDWalletSign/index.tsx index 9b8a70c99b..feb81838d0 100644 --- a/packages/neuron-ui/src/components/HDWalletSign/index.tsx +++ b/packages/neuron-ui/src/components/HDWalletSign/index.tsx @@ -1,7 +1,6 @@ import React, { useCallback, useState, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { ckbCore } from 'services/chain' -import { shannonToCKBFormatter, useDidMount, isSuccessResponse, CONSTANTS } from 'utils' +import { scriptToAddress, shannonToCKBFormatter, useDidMount, isSuccessResponse, CONSTANTS } from 'utils' import { getSystemCodeHash, getAllNetworks, getCurrentNetworkID } from 'services/remote' import CopyZone from 'widgets/CopyZone' import { DeviceSignIndex as DeviceSignIndexSubject } from 'services/subjects' @@ -13,7 +12,6 @@ const { MAINNET_CLIENT_LIST } = CONSTANTS const HDWalletSign = ({ tx }: { tx: State.DetailedTransaction }) => { const [t] = useTranslation() const [isMainnet, setIsMainnet] = useState(false) - const addressPrefix = isMainnet ? ckbCore.utils.AddressPrefix.Mainnet : ckbCore.utils.AddressPrefix.Testnet const [systemCodeHash, setSystemCodeHash] = useState('') const [inputVisible, setInputVisible] = useState(true) const [activeInputIndex, setActiveInputIndex] = useState(0) @@ -24,7 +22,6 @@ const HDWalletSign = ({ tx }: { tx: State.DetailedTransaction }) => { setSystemCodeHash(res.result) } }) - Promise.all([getAllNetworks(), getCurrentNetworkID()]) .then(([networksRes, idRes]) => { if (isSuccessResponse(networksRes) && isSuccessResponse(idRes)) { @@ -55,23 +52,10 @@ const HDWalletSign = ({ tx }: { tx: State.DetailedTransaction }) => { address = t('transaction.cell-from-cellbase') } else { try { - if (cell.lock.codeHash === systemCodeHash && cell.lock.hashType === 'type') { - address = ckbCore.utils.bech32Address(cell.lock.args, { - prefix: addressPrefix, - type: ckbCore.utils.AddressType.HashIdx, - codeHashOrCodeHashIndex: '0x00', - }) - } else { - address = ckbCore.utils.fullPayloadToAddress({ - args: cell.lock.args, - prefix: addressPrefix, - type: - cell.lock.hashType === 'data' - ? ckbCore.utils.AddressType.DataCodeHash - : ckbCore.utils.AddressType.TypeCodeHash, - codeHash: cell.lock.codeHash, - }) - } + address = scriptToAddress(cell.lock, { + isMainnet, + deprecated: cell.lock.codeHash === systemCodeHash && cell.lock.hashType === 'type', + }) } catch (err) { console.error(err) } @@ -106,7 +90,7 @@ const HDWalletSign = ({ tx }: { tx: State.DetailedTransaction }) => { ) }), - [t, addressPrefix, systemCodeHash] + [t, isMainnet, systemCodeHash] ) const inputBody = useMemo(() => { diff --git a/packages/neuron-ui/src/components/History/RowExtend.tsx b/packages/neuron-ui/src/components/History/RowExtend.tsx index be6f9aa5fb..8012a6c6b7 100644 --- a/packages/neuron-ui/src/components/History/RowExtend.tsx +++ b/packages/neuron-ui/src/components/History/RowExtend.tsx @@ -9,6 +9,7 @@ import { ExplorerIcon, Copy, DetailIcon } from 'widgets/Icons/icon' import { useTranslation } from 'react-i18next' import ShowOrEditDesc from 'widgets/ShowOrEditDesc' import Tooltip from 'widgets/Tooltip' +import AmendPendingTransactionDialog from 'components/AmendPendingTransactionDialog' import { getTransaction as getOnChainTransaction } from 'services/chain' import Button from 'widgets/Button' @@ -20,13 +21,15 @@ type RowExtendProps = { isMainnet: boolean bestBlockNumber: number id: string + isWatchOnly?: boolean } -const RowExtend = ({ column, columns, isMainnet, id, bestBlockNumber }: RowExtendProps) => { +const RowExtend = ({ column, columns, isMainnet, id, bestBlockNumber, isWatchOnly }: RowExtendProps) => { const dispatch = useDispatch() const navigate = useNavigate() const [t] = useTranslation() const [amendabled, setAmendabled] = useState(false) + const [amendPendingTx, setAmendPendingTx] = useState() const { onChangeEditStatus, onSubmitDescription } = useLocalDescription('transaction', id, dispatch) @@ -44,11 +47,16 @@ const RowExtend = ({ column, columns, isMainnet, id, bestBlockNumber }: RowExten break } case 'amend': { - if (column?.sudtInfo) { - navigate(`${RoutePath.History}/amendSUDTSend/${btn.dataset.hash}`) - return + if (column.type === 'send' && !column.nftInfo && !column.nervosDao) { + if (column?.sudtInfo) { + navigate(`${RoutePath.History}/amendSUDTSend/${btn.dataset.hash}`) + } else { + navigate(`${RoutePath.History}/amend/${btn.dataset.hash}`) + } + } else { + setAmendPendingTx(column) } - navigate(`${RoutePath.History}/amend/${btn.dataset.hash}`) + break } default: { @@ -75,93 +83,98 @@ const RowExtend = ({ column, columns, isMainnet, id, bestBlockNumber }: RowExten }, [hash, dispatch]) useEffect(() => { - if (status !== 'success') { - if (column.type === 'send' && !column.nftInfo && !column.nervosDao) { - getOnChainTransaction(hash).then(tx => { - // @ts-expect-error Replace-By-Fee (RBF) - const { min_replace_fee: minReplaceFee } = tx - if (minReplaceFee) { - setAmendabled(true) - } - }) - } - } setAmendabled(false) + if (status !== 'success' && column.type !== 'receive' && !isWatchOnly) { + getOnChainTransaction(hash).then(tx => { + // @ts-expect-error Replace-By-Fee (RBF) + const { min_replace_fee: minReplaceFee } = tx + if (minReplaceFee) { + setAmendabled(true) + } + }) + } }, [status, hash, setAmendabled]) + const onCloseAmendDialog = useCallback(() => { + setAmendPendingTx(undefined) + }, [setAmendPendingTx]) + return ( - - -
-
-
-
{t('history.confirmationTimes')}
-
{confirmationsLabel}
+ <> + + +
+
+
+
{t('history.confirmationTimes')}
+
{confirmationsLabel}
+
+
+
{t('history.description')}
+ + } + showTriangle + isTriggerNextToChild + > +
{description || t('addresses.default-description')}
+
+
-
{t('history.description')}
- - } - showTriangle - isTriggerNextToChild - > -
{description || t('addresses.default-description')}
-
-
-
-
-
{t('history.transaction-hash')}
-
- {hash} - -
-
-
-
- - +
{t('history.transaction-hash')}
+
+ {hash} + +
+
+
+ + +
- {amendabled ? ( - - ) : null} + {amendabled ? ( + + ) : null} +
-
- - + + + {amendPendingTx ? : null} + ) } export default RowExtend diff --git a/packages/neuron-ui/src/components/History/index.tsx b/packages/neuron-ui/src/components/History/index.tsx index 73a9bdbb55..6e1f19c2d3 100644 --- a/packages/neuron-ui/src/components/History/index.tsx +++ b/packages/neuron-ui/src/components/History/index.tsx @@ -30,7 +30,7 @@ import styles from './history.module.scss' const History = () => { const { app: { pageNotice }, - wallet: { id, name: walletName }, + wallet: { id, name: walletName, isWatchOnly }, chain: { networkID, syncState: { cacheTipBlockNumber, bestKnownBlockNumber }, @@ -228,6 +228,7 @@ const History = () => { isMainnet={isMainnet} id={id} bestBlockNumber={bestBlockNumber} + isWatchOnly={isWatchOnly} /> )} expandedRow={expandedRow} diff --git a/packages/neuron-ui/src/components/HistoryDetailPage/index.tsx b/packages/neuron-ui/src/components/HistoryDetailPage/index.tsx index 2923653803..8d6f553ec5 100644 --- a/packages/neuron-ui/src/components/HistoryDetailPage/index.tsx +++ b/packages/neuron-ui/src/components/HistoryDetailPage/index.tsx @@ -1,9 +1,9 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' import { useTranslation } from 'react-i18next' -import { scriptToAddress } from '@nervosnetwork/ckb-sdk-utils' import { calculateUnlockDaoMaximumWithdraw, getTransaction } from 'services/remote' import { showPageNotice, transactionState, useDispatch, useState as useGlobalState } from 'states' +import { type CKBComponents } from '@ckb-lumos/rpc/lib/types/api' import PageContainer from 'components/PageContainer' import LockInfoDialog from 'components/LockInfoDialog' import ScriptTag from 'components/ScriptTag' @@ -17,6 +17,7 @@ import Breadcrum from 'widgets/Breadcrum' import { ErrorCode, + scriptToAddress, localNumberFormatter, uniformTimeFormatter, shannonToCKBFormatter, @@ -254,7 +255,7 @@ const HistoryDetailPage = () => { address = t('transaction.cell-from-cellbase') } else { try { - address = scriptToAddress(cell.lock, isMainnet) + address = scriptToAddress(cell.lock, { isMainnet }) } catch (err) { console.error(err) } diff --git a/packages/neuron-ui/src/components/LockInfoDialog/index.tsx b/packages/neuron-ui/src/components/LockInfoDialog/index.tsx index 3f21882da3..7e747e18c8 100644 --- a/packages/neuron-ui/src/components/LockInfoDialog/index.tsx +++ b/packages/neuron-ui/src/components/LockInfoDialog/index.tsx @@ -1,8 +1,8 @@ import { useTranslation } from 'react-i18next' import React, { useRef } from 'react' -import { bech32Address, AddressPrefix } from '@nervosnetwork/ckb-sdk-utils' +import { type CKBComponents } from '@ckb-lumos/rpc/lib/types/api' import Dialog from 'widgets/Dialog' -import { useCopy, useDialog } from 'utils' +import { useCopy, useDialog, scriptToAddress } from 'utils' import { Copy } from 'widgets/Icons/icon' import Alert from 'widgets/Alert' import getLockSupportShortAddress from '../../utils/getLockSupportShortAddress' @@ -35,10 +35,7 @@ const ShortAddr = ({ return null } - const shortAddr = bech32Address(lockScript.args, { - prefix: isMainnet ? AddressPrefix.Mainnet : AddressPrefix.Testnet, - codeHashOrCodeHashIndex: lock.CodeHashIndex, - }) + const shortAddr = scriptToAddress(lockScript, { isMainnet, deprecated: true }) return ( <> diff --git a/packages/neuron-ui/src/components/MultisigAddress/hooks.ts b/packages/neuron-ui/src/components/MultisigAddress/hooks.ts index a672164efd..20853bdbca 100644 --- a/packages/neuron-ui/src/components/MultisigAddress/hooks.ts +++ b/packages/neuron-ui/src/components/MultisigAddress/hooks.ts @@ -1,5 +1,5 @@ import React, { useCallback, useState, useEffect, useMemo } from 'react' -import { isSuccessResponse, getMultisigAddress, DefaultLockInfo } from 'utils' +import { isSuccessResponse, getMultisigAddress, DefaultLockInfo, addressToScript, scriptToAddress } from 'utils' import { MultisigOutputUpdate } from 'services/subjects' import { MultisigConfig, @@ -15,7 +15,7 @@ import { OfflineSignJSON, getMultisigSyncProgress, } from 'services/remote' -import { addressToScript, scriptToAddress, scriptToHash } from '@nervosnetwork/ckb-sdk-utils' +import { computeScriptHash } from '@ckb-lumos/base/lib/utils' export const useSearch = (clearSelected: () => void, onFilterConfig: (searchKey: string) => void) => { const [keywords, setKeywords] = useState('') @@ -124,7 +124,7 @@ export const useConfigManage = ({ walletId, isMainnet }: { walletId: string; isM codeHash: DefaultLockInfo.CodeHash, hashType: DefaultLockInfo.HashType, }, - isMainnet + { isMainnet } ) ), fullPayload: getMultisigAddress(entity.blake160s, entity.r, entity.m, entity.n, isMainnet), @@ -324,7 +324,7 @@ export const useSubscription = ({ const hashToPayload = useMemo( () => configs.reduce>( - (pre, cur) => ({ ...pre, [scriptToHash(addressToScript(cur.fullPayload))]: cur.fullPayload }), + (pre, cur) => ({ ...pre, [computeScriptHash(addressToScript(cur.fullPayload))]: cur.fullPayload }), {} ), [configs] diff --git a/packages/neuron-ui/src/components/MultisigAddressCreateDialog/hooks.ts b/packages/neuron-ui/src/components/MultisigAddressCreateDialog/hooks.ts index 7bc6a1e5af..74f4f475f9 100644 --- a/packages/neuron-ui/src/components/MultisigAddressCreateDialog/hooks.ts +++ b/packages/neuron-ui/src/components/MultisigAddressCreateDialog/hooks.ts @@ -1,7 +1,6 @@ import React, { useCallback, useMemo, useState, useEffect } from 'react' -import { validateAddress, isSecp256k1Address, getMultisigAddress } from 'utils' +import { validateAddress, isSecp256k1Address, getMultisigAddress, addressToScript } from 'utils' import { useTranslation } from 'react-i18next' -import { addressToScript } from '@nervosnetwork/ckb-sdk-utils' import { ErrorWithI18n, isErrorWithI18n } from 'exceptions' import { MAX_M_N_NUMBER } from 'utils/const' diff --git a/packages/neuron-ui/src/components/NervosDAO/hooks.ts b/packages/neuron-ui/src/components/NervosDAO/hooks.ts index be667bff81..93498cadf1 100644 --- a/packages/neuron-ui/src/components/NervosDAO/hooks.ts +++ b/packages/neuron-ui/src/components/NervosDAO/hooks.ts @@ -2,11 +2,13 @@ import { useEffect, useCallback, useState } from 'react' import { AppActions, StateAction } from 'states/stateProvider/reducer' import { updateNervosDaoData, clearNervosDaoData } from 'states/stateProvider/actionCreators' -import { calculateAPC, CONSTANTS, isSuccessResponse } from 'utils' +import { NavigateFunction } from 'react-router-dom' +import { type CKBComponents } from '@ckb-lumos/rpc/lib/types/api' +import { calculateAPC, CONSTANTS, isSuccessResponse, RoutePath } from 'utils' +import { rpc, getHeader } from 'services/chain' import { generateDaoWithdrawTx, generateDaoClaimTx } from 'services/remote' -import { ckbCore, getHeader } from 'services/chain' -import { calculateMaximumWithdraw } from '@nervosnetwork/ckb-sdk-utils' +import { calculateMaximumWithdrawCompatible } from '@ckb-lumos/common-scripts/lib/dao' const { MILLISECONDS_IN_YEAR, MEDIUM_FEE_RATE } = CONSTANTS @@ -131,6 +133,7 @@ export const useOnWithdrawDialogSubmit = ({ payload: { walletID, actionType: 'send', + onSuccess: () => {}, }, }) } else { @@ -158,12 +161,14 @@ export const useOnActionClick = ({ dispatch, walletID, setActiveRecord, + navigate, }: { records: Readonly clearGeneratedTx: () => void dispatch: React.Dispatch walletID: string setActiveRecord: React.Dispatch + navigate: NavigateFunction }) => useCallback( (e: any) => { @@ -174,7 +179,9 @@ export const useOnActionClick = ({ } const record = records.find(r => r.outPoint.txHash === outPoint.txHash && r.outPoint.index === outPoint.index) if (record) { - if (record.depositOutPoint) { + if (record.status === 'sent') { + navigate(`${RoutePath.History}/${record.depositInfo?.txHash}`) + } else if (record.depositOutPoint) { generateDaoClaimTx({ walletID, withdrawingOutPoint: record.outPoint, @@ -192,6 +199,7 @@ export const useOnActionClick = ({ payload: { walletID, actionType: 'send', + onSuccess: () => {}, }, }) } else { @@ -232,23 +240,23 @@ export const useUpdateWithdrawList = ({ return } const depositOutPointHashes = records.map(v => v.depositOutPoint?.txHash ?? v.outPoint.txHash) - ckbCore.rpc + rpc .createBatchRequest<'getTransaction', string[], CKBComponents.TransactionWithStatus[]>( depositOutPointHashes.map(v => ['getTransaction', v]) ) .exec() - .then(txs => { + .then((txs: CKBComponents.TransactionWithStatus[]) => { const committedTx = txs.filter(v => v.txStatus.status === 'committed') const blockHashes = [ ...(committedTx.map(v => v.txStatus.blockHash).filter(v => !!v) as string[]), ...(records.map(v => (v.depositOutPoint ? v.blockHash : null)).filter(v => !!v) as string[]), ] - return ckbCore.rpc + return rpc .createBatchRequest<'getHeader', string[], CKBComponents.BlockHeader[]>( blockHashes.map(v => ['getHeader', v]) ) .exec() - .then(blockHeaders => { + .then((blockHeaders: CKBComponents.BlockHeader[]) => { const hashHeaderMap = new Map() blockHeaders.forEach((header, idx) => { hashHeaderMap.set(blockHashes[idx], header.dao) @@ -283,12 +291,14 @@ export const useUpdateWithdrawList = ({ } withdrawList.set( key, - calculateMaximumWithdraw( - tx.transaction.outputs[+formattedDepositOutPoint.index], - tx.transaction.outputsData[+formattedDepositOutPoint.index], + calculateMaximumWithdrawCompatible( + { + cellOutput: tx.transaction.outputs[+formattedDepositOutPoint.index], + data: tx.transaction.outputsData[+formattedDepositOutPoint.index], + }, depositDAO, withdrawDAO - ) + ).toHexString() ) }) setWithdrawList(withdrawList) @@ -301,10 +311,10 @@ export const useUpdateWithdrawList = ({ const getBlockHashes = (txHashes: string[]) => { const batchParams: ['getTransaction', string][] = txHashes.map(v => ['getTransaction', v]) - return ckbCore.rpc + return rpc .createBatchRequest<'getTransaction', [string], CKBComponents.TransactionWithStatus[]>(batchParams) .exec() - .then(res => { + .then((res: CKBComponents.TransactionWithStatus[]) => { return res.map((v, idx) => ({ txHash: txHashes[idx], blockHash: v.txStatus.blockHash, @@ -327,7 +337,7 @@ export const useUpdateDepositEpochList = ({ useEffect(() => { if (connectionStatus === 'online') { getBlockHashes(records.map(v => v.depositOutPoint?.txHash).filter(v => !!v) as string[]).then( - depositBlockHashes => { + (depositBlockHashes: { txHash: string; blockHash: string | null }[]) => { const recordKeyIdx: string[] = [] const batchParams: ['getHeader', string][] = [] records.forEach(record => { @@ -342,10 +352,10 @@ export const useUpdateDepositEpochList = ({ recordKeyIdx.push(v.txHash) } }) - ckbCore.rpc + rpc .createBatchRequest<'getHeader', any, CKBComponents.BlockHeader[]>(batchParams) .exec() - .then(res => { + .then((res: CKBComponents.BlockHeader[]) => { const epochList = new Map() records.forEach(record => { const key = record.depositOutPoint ? record.depositOutPoint.txHash : record.outPoint.txHash diff --git a/packages/neuron-ui/src/components/NervosDAO/index.tsx b/packages/neuron-ui/src/components/NervosDAO/index.tsx index c31269a8f0..c80a7b2a5e 100644 --- a/packages/neuron-ui/src/components/NervosDAO/index.tsx +++ b/packages/neuron-ui/src/components/NervosDAO/index.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState, useMemo, useCallback } from 'react' -import { useState as useGlobalState, useDispatch } from 'states' +import { useState as useGlobalState, useDispatch, showPageNotice } from 'states' import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' import appState from 'states/init/app' @@ -16,23 +17,22 @@ import { useClearGeneratedTx, } from 'utils' -import { openExternal } from 'services/remote' - +import DepositRulesDialog from 'components/DepositRulesDialog' import DepositDialog from 'components/DepositDialog' import WithdrawDialog from 'components/WithdrawDialog' import DAORecord, { DAORecordProps } from 'components/NervosDAORecord' import PageContainer from 'components/PageContainer' import CopyZone from 'widgets/CopyZone' -import { ArrowNext, Attention, Deposit, EyesClose, EyesOpen } from 'widgets/Icons/icon' +import { ArrowNext, Attention, Deposit, EyesClose, EyesOpen, DepositTimeSort } from 'widgets/Icons/icon' import TableNoData from 'widgets/Icons/TableNoData.png' import { HIDE_BALANCE } from 'utils/const' +import Tooltip from 'widgets/Tooltip' +import Button from 'widgets/Button' import useGetCountDownAndFeeRateStats from 'utils/hooks/useGetCountDownAndFeeRateStats' import hooks, { useDepositDialog } from './hooks' import styles from './nervosDAO.module.scss' -const DAO_DOCS_URL = 'https://docs.nervos.org/docs/basics/guides/crypto%20wallets/neuron/#deposit-ckb-into-nervos-dao' - const NervosDAO = () => { const [tabIdx, setTabIdx] = useState('0') const { @@ -42,6 +42,7 @@ const NervosDAO = () => { tipDao, tipBlockTimestamp, epoch, + pageNotice, }, wallet, nervosDAO: { records }, @@ -53,6 +54,7 @@ const NervosDAO = () => { settings: { networks }, } = useGlobalState() const dispatch = useDispatch() + const navigate = useNavigate() const [t, { language }] = useTranslation() const { suggestFeeRate } = useGetCountDownAndFeeRateStats() const [isPrivacyMode, setIsPrivacyMode] = useState(false) @@ -63,6 +65,8 @@ const NervosDAO = () => { const [depositEpochList, setDepositEpochList] = useState>(new Map()) const clearGeneratedTx = useClearGeneratedTx() const { showDepositDialog, onOpenDepositDialog, onCloseDepositDialog } = useDepositDialog() + const [showRules, setShowRules] = useState(false) + const [isDescDirection, setIsDescDirection] = useState(true) const onWithdrawDialogDismiss = hooks.useOnWithdrawDialogDismiss(setActiveRecord) @@ -84,17 +88,27 @@ const NervosDAO = () => { suggestFeeRate, }) + const onDepositSuccess = useCallback(() => { + onCloseDepositDialog() + showPageNotice('nervos-dao.deposit-submitted')(dispatch) + }, [dispatch, onCloseDepositDialog]) + const onActionClick = hooks.useOnActionClick({ records, clearGeneratedTx, dispatch, walletID: wallet.id, setActiveRecord, + navigate, }) const handleOpenRules = useCallback(() => { - openExternal(DAO_DOCS_URL) - }, []) + setShowRules(true) + }, [setShowRules]) + + const handleCloseRules = useCallback(() => { + setShowRules(false) + }, [setShowRules]) hooks.useUpdateDepositEpochList({ records, setDepositEpochList, connectionStatus }) @@ -116,6 +130,10 @@ const NervosDAO = () => { networkID, }) + const toggleDirection = useCallback(() => { + setIsDescDirection(!isDescDirection) + }, [setIsDescDirection, isDescDirection]) + const MemoizedRecords = useMemo(() => { const onTabClick = (e: React.SyntheticEvent) => { const { @@ -136,30 +154,51 @@ const NervosDAO = () => { return record.status === 'dead' }) - if (tabIdx === '1') { + if (tabIdx === '0') { + filteredRecord.sort((r1, r2) => + isDescDirection + ? +r2.depositInfo!.timestamp! - +r1.depositInfo!.timestamp! + : +r1.depositInfo!.timestamp! - +r2.depositInfo!.timestamp! + ) + } else if (tabIdx === '1') { filteredRecord.sort((r1, r2) => +r2.unlockInfo!.timestamp! - +r1.unlockInfo!.timestamp!) } return ( <> -
-
- - +
+ + +
+ + {tabIdx === '0' ? ( + + + + ) : null}
{filteredRecord.length ? (
@@ -205,6 +244,8 @@ const NervosDAO = () => { tabIdx, setTabIdx, isPrivacyMode, + isDescDirection, + toggleDirection, ]) useEffect(() => { @@ -223,6 +264,7 @@ const NervosDAO = () => { isTxGenerated={!!send.generatedTx} suggestFeeRate={suggestFeeRate} globalAPC={globalAPC} + onDepositSuccess={onDepositSuccess} /> ) }, [ @@ -276,6 +318,7 @@ const NervosDAO = () => { )}
} + notice={pageNotice} >
@@ -288,7 +331,7 @@ const NervosDAO = () => { ) : ( @@ -303,7 +346,7 @@ const NervosDAO = () => {
{onlineAndSynced && !isPrivacyMode ? ( @@ -366,8 +409,11 @@ const NervosDAO = () => {
{MemoizedRecords}
+ {MemoizedDepositDialog} {MemoizedWithdrawDialog} + + ) } diff --git a/packages/neuron-ui/src/components/NervosDAO/nervosDAO.module.scss b/packages/neuron-ui/src/components/NervosDAO/nervosDAO.module.scss index dd83689f4d..328427c6c6 100644 --- a/packages/neuron-ui/src/components/NervosDAO/nervosDAO.module.scss +++ b/packages/neuron-ui/src/components/NervosDAO/nervosDAO.module.scss @@ -180,6 +180,36 @@ .recordsContainer { margin-top: 20px; + .tabContainer { + display: flex; + justify-content: space-between; + align-items: center; + .sortBtn { + display: flex; + justify-content: center; + align-items: center; + background: var(--secondary-background-color); + width: 36px; + height: 36px; + border-radius: 100%; + min-width: 0; + cursor: pointer; + &[data-desc='false'] { + svg { + transform: rotateX(-180deg); + } + } + &:hover { + svg { + g, + path { + stroke: var(--primary-color); + } + } + } + } + } + .recordTab { // To achieve animation on switching the selected target with pure CSS, a layout with fixed width is required. $itemWidth: 96px; diff --git a/packages/neuron-ui/src/components/NervosDAODetail/CellsCard/index.tsx b/packages/neuron-ui/src/components/NervosDAODetail/CellsCard/index.tsx index da7689cd06..bb8af67050 100644 --- a/packages/neuron-ui/src/components/NervosDAODetail/CellsCard/index.tsx +++ b/packages/neuron-ui/src/components/NervosDAODetail/CellsCard/index.tsx @@ -1,8 +1,8 @@ import React, { FC, useMemo, useState } from 'react' import Tabs, { VariantProps } from 'widgets/Tabs' -import { clsx, localNumberFormatter, shannonToCKBFormatter, isMainnet as isMainnetUtils } from 'utils' +import { type CKBComponents } from '@ckb-lumos/rpc/lib/types/api' +import { clsx, localNumberFormatter, shannonToCKBFormatter, scriptToAddress, isMainnet as isMainnetUtils } from 'utils' import { useTranslation } from 'react-i18next' -import { scriptToAddress } from '@nervosnetwork/ckb-sdk-utils' import { onEnter } from 'utils/inputDevice' import { EyesClose, EyesOpen } from 'widgets/Icons/icon' import { HIDE_BALANCE } from 'utils/const' @@ -59,7 +59,7 @@ const TabsVariantWithCellsCard = ({
{selectedTab.cells.map((cell, index) => { - const address = cell.lock != null ? scriptToAddress(cell.lock, isMainnet) : null + const address = cell.lock != null ? scriptToAddress(cell.lock, { isMainnet }) : null const capacity = cell.capacity ? shannonToCKBFormatter(cell.capacity) : '--' return ( diff --git a/packages/neuron-ui/src/components/NervosDAODetail/index.tsx b/packages/neuron-ui/src/components/NervosDAODetail/index.tsx index f4ed335c33..550999bc2a 100644 --- a/packages/neuron-ui/src/components/NervosDAODetail/index.tsx +++ b/packages/neuron-ui/src/components/NervosDAODetail/index.tsx @@ -99,7 +99,7 @@ const TabsVariantWithTxTypes = ({ {isIncomeShow ? (
diff --git a/packages/neuron-ui/src/components/NervosDAORecord/daoRecordRow.module.scss b/packages/neuron-ui/src/components/NervosDAORecord/daoRecordRow.module.scss index c533d52c3f..f7500d7e01 100644 --- a/packages/neuron-ui/src/components/NervosDAORecord/daoRecordRow.module.scss +++ b/packages/neuron-ui/src/components/NervosDAORecord/daoRecordRow.module.scss @@ -19,6 +19,20 @@ justify-content: space-between; } + .spinner { + width: 14px; + height: 14px; + } + + .depositingMsg { + color: var(--primary-color); + font-weight: 500; + font-size: 16px; + display: flex; + align-items: center; + gap: 8px; + } + .compensationAndAPC { display: flex; align-items: center; @@ -244,6 +258,26 @@ } .action { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 12px; + .lockWarn { + color: var(--warn-text-color); + font-weight: 500; + font-size: 12px; + position: relative; + &::before { + content: ''; + height: 6px; + width: 6px; + border-radius: 100%; + position: absolute; + left: -10px; + top: calc(50% - 3px); + background: var(--warn-text-color); + } + } button { width: 216px; line-height: 56px; diff --git a/packages/neuron-ui/src/components/NervosDAORecord/hooks.ts b/packages/neuron-ui/src/components/NervosDAORecord/hooks.ts index f1db5a48af..784a90bc98 100644 --- a/packages/neuron-ui/src/components/NervosDAORecord/hooks.ts +++ b/packages/neuron-ui/src/components/NervosDAORecord/hooks.ts @@ -1,6 +1,7 @@ import { useEffect } from 'react' import { getHeader } from 'services/chain' import { calculateAPC, CONSTANTS } from 'utils' +import { type CKBComponents } from '@ckb-lumos/rpc/lib/types/api' const { MILLISECONDS_IN_YEAR } = CONSTANTS diff --git a/packages/neuron-ui/src/components/NervosDAORecord/index.tsx b/packages/neuron-ui/src/components/NervosDAORecord/index.tsx index 36256afcdb..9b622beed7 100644 --- a/packages/neuron-ui/src/components/NervosDAORecord/index.tsx +++ b/packages/neuron-ui/src/components/NervosDAORecord/index.tsx @@ -18,7 +18,7 @@ import CompensationPeriodTooltip from 'components/CompensationPeriodTooltip' import { Clock } from 'widgets/Icons/icon' import { Link } from 'react-router-dom' import { HIDE_BALANCE } from 'utils/const' - +import Spinner from 'widgets/Spinner' import Tooltip from 'widgets/Tooltip' import styles from './daoRecordRow.module.scss' import hooks from './hooks' @@ -92,7 +92,14 @@ export const DAORecord = ({ ) const leftEpochs = Math.max(compensationEndEpochValue - currentEpochValue, 0) - const leftDays = (Math.round(leftEpochs / EPOCHS_PER_DAY) ?? '').toString() + + let leftHours = '' + let leftDays = '' + if (leftEpochs < EPOCHS_PER_DAY) { + leftHours = (parseInt(`${leftEpochs * (24 / EPOCHS_PER_DAY)}`, 10) || 1).toString() + } else { + leftDays = (Math.round(leftEpochs / EPOCHS_PER_DAY) ?? '').toString() + } const compensation = BigInt(withdrawCapacity || capacity) - BigInt(capacity) @@ -106,15 +113,18 @@ export const DAORecord = ({ }) let message = '' + let showLoading = false if (ConnectionStatus.Online === connectionStatus) { switch (cellStatus) { case CellStatus.Unlocking: { message = t('nervos-dao.compensation-period.stage-messages.unlocking') + showLoading = true break } case CellStatus.Withdrawing: { message = t('nervos-dao.compensation-period.stage-messages.withdrawing') + showLoading = true break } case CellStatus.Unlockable: { @@ -122,17 +132,32 @@ export const DAORecord = ({ break } case CellStatus.Depositing: { - message = t('nervos-dao.compensation-period.stage-messages.pending') + message = t('nervos-dao.compensation-period.stage-messages.deposit-in-progress') + showLoading = true break } case CellStatus.Deposited: { - message = t('nervos-dao.compensation-period.stage-messages.next-compensation-cycle', { days: leftDays || '--' }) + if (leftHours) { + message = t('nervos-dao.compensation-period.stage-messages.next-compensation-cycle-hours', { + hours: leftHours || '--', + }) + } else { + message = t('nervos-dao.compensation-period.stage-messages.next-compensation-cycle', { + days: leftDays || '--', + }) + } break } case CellStatus.Locked: { - message = t('nervos-dao.compensation-period.stage-messages.compensation-cycle-will-end', { - days: leftDays || '--', - }) + if (leftHours) { + message = t('nervos-dao.compensation-period.stage-messages.compensation-cycle-will-end-hours', { + hours: leftHours || '--', + }) + } else { + message = t('nervos-dao.compensation-period.stage-messages.compensation-cycle-will-end', { + days: leftDays || '--', + }) + } break } default: { @@ -149,7 +174,7 @@ export const DAORecord = ({ const isWithdrawnDisabled = CellStatus.Deposited === cellStatus && !hasCkbBalance const isActionAvailable = connectionStatus === 'online' && - [CellStatus.Deposited, CellStatus.Unlockable].includes(cellStatus) && + [CellStatus.Deposited, CellStatus.Unlockable, CellStatus.Depositing].includes(cellStatus) && !isWithdrawnDisabled const depositOutPointKey = depositOutPoint @@ -220,7 +245,7 @@ export const DAORecord = ({ ) : ( {`${shannonToCKBFormatter(capacity)} CKB`} @@ -279,10 +304,20 @@ export const DAORecord = ({ /> )}
- {message} + {showLoading ? ( +

+ {message} + +

+ ) : ( + {message} + )}
+ {cellStatus === CellStatus.Locked ? ( +
{t('nervos-dao.deposit-record.lock-warn')}
+ ) : null} {isWithdrawnDisabled ? ( {t('nervos-dao.deposit-record.insufficient-balance-to-unlock')}
} @@ -305,7 +340,11 @@ export const DAORecord = ({ data-index={index} onClick={onClick} disabled={!isActionAvailable} - label={t(`nervos-dao.deposit-record.${isWithdrawn ? 'unlock' : 'withdraw'}-action-label`)} + label={ + cellStatus === CellStatus.Depositing + ? t('nervos-dao.deposit-record.view-tx-detail') + : t(`nervos-dao.deposit-record.${isWithdrawn ? 'unlock' : 'withdraw'}-action-label`) + } /> )}
diff --git a/packages/neuron-ui/src/components/Overview/index.tsx b/packages/neuron-ui/src/components/Overview/index.tsx index c9984eb15b..9ce1a0a94b 100644 --- a/packages/neuron-ui/src/components/Overview/index.tsx +++ b/packages/neuron-ui/src/components/Overview/index.tsx @@ -178,7 +178,7 @@ const Overview = () => { )} {showBalance ? ( - + {shannonToCKBFormatter(balance)} ) : ( @@ -191,7 +191,7 @@ const Overview = () => { {t('overview.locked-balance')} : {showBalance ? ( - + {shannonToCKBFormatter(lockedBalance)} ) : ( diff --git a/packages/neuron-ui/src/components/PageContainer/hooks.ts b/packages/neuron-ui/src/components/PageContainer/hooks.ts index 7122c13905..ce87534fd6 100644 --- a/packages/neuron-ui/src/components/PageContainer/hooks.ts +++ b/packages/neuron-ui/src/components/PageContainer/hooks.ts @@ -74,7 +74,7 @@ export const useSetBlockNumber = ({ ) const onOpenAddressInExplorer = useCallback(() => { const explorerUrl = getExplorerUrl(isMainnet) - openExternal(`${explorerUrl}/address/${firstAddress}`) + openExternal(`${explorerUrl}/address/${firstAddress}?sort=time`) }, [firstAddress, isMainnet]) const onViewBlock = useCallback(() => { const explorerUrl = getExplorerUrl(isMainnet) diff --git a/packages/neuron-ui/src/components/Receive/VerifyHardwareAddress.tsx b/packages/neuron-ui/src/components/Receive/VerifyHardwareAddress.tsx index a7f19c059e..e6bd7284fb 100644 --- a/packages/neuron-ui/src/components/Receive/VerifyHardwareAddress.tsx +++ b/packages/neuron-ui/src/components/Receive/VerifyHardwareAddress.tsx @@ -11,9 +11,8 @@ import { updateWallet, getPlatform, } from 'services/remote' -import { ErrorCode, clsx, errorFormatter, isSuccessResponse, useDidMount } from 'utils' +import { ErrorCode, clsx, errorFormatter, isSuccessResponse, addressToAddress, useDidMount } from 'utils' import { CkbAppNotFoundException, DeviceNotFoundException } from 'exceptions' -import { AddressPrefix, addressToScript, scriptToAddress } from '@nervosnetwork/ckb-sdk-utils' import Alert from 'widgets/Alert' import styles from './receive.module.scss' @@ -23,22 +22,12 @@ export interface VerifyHardwareAddressProps { onClose?: () => void } -const toLongAddr = (addr: string) => { - try { - const script = addressToScript(addr) - const isMainnet = addr.startsWith(AddressPrefix.Mainnet) - return scriptToAddress(script, isMainnet) - } catch { - return '' - } -} - const verifyAddressEqual = (source: string, target?: string) => { if (!target) { return false } if (source.length !== target.length) { - return toLongAddr(source) === toLongAddr(target) + return addressToAddress(source) === addressToAddress(target) } return source === target } diff --git a/packages/neuron-ui/src/components/Receive/hooks.ts b/packages/neuron-ui/src/components/Receive/hooks.ts index 416b695b4b..6dfd17ff59 100644 --- a/packages/neuron-ui/src/components/Receive/hooks.ts +++ b/packages/neuron-ui/src/components/Receive/hooks.ts @@ -1,4 +1,4 @@ -import { AddressPrefix, addressToScript, bech32Address } from '@nervosnetwork/ckb-sdk-utils' +import { addressToAddress } from 'utils' import { useCallback, useMemo, useRef, useState } from 'react' import { copyCanvas, downloadCanvas } from 'widgets/QRCode' @@ -29,19 +29,12 @@ export const useCopyAndDownloadQrCode = () => { } } -const toShortAddr = (addr: string) => { - try { - const script = addressToScript(addr) - const isMainnet = addr.startsWith(AddressPrefix.Mainnet) - return bech32Address(script.args, { prefix: isMainnet ? AddressPrefix.Mainnet : AddressPrefix.Testnet }) - } catch { - return '' - } -} - export const useSwitchAddress = (address: string) => { const [isInShortFormat, setIsInShortFormat] = useState(false) - const showAddress = useMemo(() => (isInShortFormat ? toShortAddr(address) : address), [address, isInShortFormat]) + const showAddress = useMemo( + () => (isInShortFormat ? addressToAddress(address, { deprecated: true }) : address), + [address, isInShortFormat] + ) return { address: showAddress, isInShortFormat, diff --git a/packages/neuron-ui/src/components/SUDTAccountList/index.tsx b/packages/neuron-ui/src/components/SUDTAccountList/index.tsx index ef11bc62d1..fa4f6972ef 100644 --- a/packages/neuron-ui/src/components/SUDTAccountList/index.tsx +++ b/packages/neuron-ui/src/components/SUDTAccountList/index.tsx @@ -26,7 +26,7 @@ import { useOnGenerateNewAccountTransaction, } from 'utils' -import { getSUDTAccountList, updateSUDTAccount, checkMigrateAcp } from 'services/remote' +import { getSUDTAccountList, updateSUDTAccount } from 'services/remote' import styles from './sUDTAccountList.module.scss' @@ -57,24 +57,6 @@ const SUDTAccountList = () => { const existingAccountNames = sUDTAccounts.filter(acc => acc.accountName).map(acc => acc.accountName || '') - useEffect(() => { - checkMigrateAcp().then(res => { - if (isSuccessResponse(res)) { - if (res.result === false) { - navigate(RoutePath.Overview) - } - } else { - dispatch({ - type: AppActions.AddNotification, - payload: { - type: 'alert', - timestamp: +new Date(), - content: typeof res.message === 'string' ? res.message : res.message.content, - }, - }) - } - }) - }, [dispatch, navigate]) useIsInsufficientToCreateSUDTAccount({ walletId, balance: BigInt(balance), setInsufficient }) const fetchAndUpdateList = useCallback(() => { diff --git a/packages/neuron-ui/src/components/SUDTMigrateToExistAccountDialog/index.tsx b/packages/neuron-ui/src/components/SUDTMigrateToExistAccountDialog/index.tsx index 63d430733f..34877de9f9 100644 --- a/packages/neuron-ui/src/components/SUDTMigrateToExistAccountDialog/index.tsx +++ b/packages/neuron-ui/src/components/SUDTMigrateToExistAccountDialog/index.tsx @@ -6,7 +6,7 @@ import Dialog from 'widgets/Dialog' import { AnyoneCanPayLockInfoOnAggron, getSUDTAmount, isSuccessResponse, validateSpecificAddress } from 'utils' import InputSelect from 'widgets/InputSelect' import { generateSudtMigrateAcpTx } from 'services/remote' -import { AppActions, useDispatch } from 'states' +import { AppActions, showGlobalAlertDialog, useDispatch } from 'states' import { isErrorWithI18n } from 'exceptions' import styles from './sUDTMigrateToExistAccountDialog.module.scss' @@ -79,14 +79,11 @@ const SUDTMigrateToExistAccountDialog = ({ }) } } else { - dispatch({ - type: AppActions.AddNotification, - payload: { - type: 'alert', - timestamp: +new Date(), - content: typeof res.message === 'string' ? res.message : res.message.content!, - }, - }) + showGlobalAlertDialog({ + type: 'failed', + message: typeof res.message === 'string' ? res.message : res.message.content!, + action: 'ok', + })(dispatch) } }) }, [cell.outPoint, address, t, onCloseDialog, dispatch, walletID]) diff --git a/packages/neuron-ui/src/components/SUDTMigrateToNewAccountDialog/index.tsx b/packages/neuron-ui/src/components/SUDTMigrateToNewAccountDialog/index.tsx index 30cecaeead..f44595e2b6 100644 --- a/packages/neuron-ui/src/components/SUDTMigrateToNewAccountDialog/index.tsx +++ b/packages/neuron-ui/src/components/SUDTMigrateToNewAccountDialog/index.tsx @@ -5,7 +5,7 @@ import TextField from 'widgets/TextField' import Dialog from 'widgets/Dialog' import { getSUDTAmount, isSuccessResponse } from 'utils' import { generateSudtMigrateAcpTx } from 'services/remote' -import { AppActions, useDispatch } from 'states' +import { AppActions, showGlobalAlertDialog, useDispatch } from 'states' import { useTokenInfo, TokenInfoType } from './hooks' import styles from './sUDTMigrateToNewAccountDialog.module.scss' @@ -83,14 +83,11 @@ const SUDTMigrateToNewAccountDialog = ({ }) } } else { - dispatch({ - type: AppActions.AddNotification, - payload: { - type: 'alert', - timestamp: +new Date(), - content: typeof res.message === 'string' ? res.message : res.message.content!, - }, - }) + showGlobalAlertDialog({ + type: 'failed', + message: typeof res.message === 'string' ? res.message : res.message.content!, + action: 'ok', + })(dispatch) } }) }, [cell, t, onCloseDialog, walletID, tokenInfo, dispatch, sudtAmount]) diff --git a/packages/neuron-ui/src/components/SUDTReceiveDialog/index.tsx b/packages/neuron-ui/src/components/SUDTReceiveDialog/index.tsx index cf9b2915ca..8dbbac6c7b 100644 --- a/packages/neuron-ui/src/components/SUDTReceiveDialog/index.tsx +++ b/packages/neuron-ui/src/components/SUDTReceiveDialog/index.tsx @@ -1,28 +1,15 @@ import React, { useState } from 'react' import { useTranslation } from 'react-i18next' -import { addressToScript, bech32Address, AddressPrefix } from '@nervosnetwork/ckb-sdk-utils' import SUDTAvatar from 'widgets/SUDTAvatar' import { AddressQrCodeWithCopyZone } from 'components/Receive' import Dialog from 'widgets/Dialog' import Alert from 'widgets/Alert' -import { CONSTANTS } from 'utils' +import { CONSTANTS, addressToAddress } from 'utils' import { getDisplayName, getDisplaySymbol } from 'components/UANDisplay' import styles from './sUDTReceiveDialog.module.scss' const { DEFAULT_SUDT_FIELDS } = CONSTANTS -const toShortAddr = (addr: string) => { - try { - const script = addressToScript(addr) - const isMainnet = addr.startsWith(AddressPrefix.Mainnet) - return bech32Address(script.args, { - prefix: isMainnet ? AddressPrefix.Mainnet : AddressPrefix.Testnet, - codeHashOrCodeHashIndex: '0x02', - }) - } catch { - return '' - } -} export interface DataProps { address: string @@ -36,7 +23,7 @@ const SUDTReceiveDialog = ({ data, onClose }: { data: DataProps; onClose?: () => const [isInShortFormat, setIsInShortFormat] = useState(false) const { address, accountName, tokenName, symbol } = data - const displayedAddr = isInShortFormat ? toShortAddr(address) : address + const displayedAddr = isInShortFormat ? addressToAddress(address, { deprecated: true }) : address return ( ({ ...item, - amount: shannonToCKBFormatter(res.result.outputs[i].capacity, false, ''), + amount: shannonToCKBFormatter(res.result.outputs[i].capacity, false, false), })) const totalAmount = outputsToTotalAmount(fmtItems) setTotalAmount(totalAmount) @@ -436,7 +437,7 @@ export const useInitialize = ({ hashType: DefaultLockInfo.HashType, args: PlaceHolderArgs, }, - isMainnet + { isMainnet } ), })), price, diff --git a/packages/neuron-ui/src/components/SendFromMultisigDialog/hooks.ts b/packages/neuron-ui/src/components/SendFromMultisigDialog/hooks.ts index e2463e3c31..b3fc8e71b6 100644 --- a/packages/neuron-ui/src/components/SendFromMultisigDialog/hooks.ts +++ b/packages/neuron-ui/src/components/SendFromMultisigDialog/hooks.ts @@ -189,7 +189,7 @@ export const useSendInfo = ({ ...v.slice(0, v.length - 1), { ...v[v.length - 1], - amount: shannonToCKBFormatter(res.outputs[res.outputs.length - 1].capacity, false, ''), + amount: shannonToCKBFormatter(res.outputs[res.outputs.length - 1].capacity, false, false), disabled: true, }, ]) diff --git a/packages/neuron-ui/src/components/SendTxDetail/TxTopology.tsx b/packages/neuron-ui/src/components/SendTxDetail/TxTopology.tsx index d8e483f54f..4a18c30eaa 100644 --- a/packages/neuron-ui/src/components/SendTxDetail/TxTopology.tsx +++ b/packages/neuron-ui/src/components/SendTxDetail/TxTopology.tsx @@ -1,6 +1,5 @@ -import { scriptToAddress } from '@nervosnetwork/ckb-sdk-utils' import React, { FC } from 'react' -import { shannonToCKBFormatter } from 'utils' +import { scriptToAddress, shannonToCKBFormatter } from 'utils' import Tooltip from 'widgets/Tooltip' import CopyZone from 'widgets/CopyZone' import { ArrowDownRound } from 'widgets/Icons/icon' @@ -56,7 +55,7 @@ const TxTopology: FC<{ // eslint-disable-next-line react/no-array-index-key key={idx.toString()} label={`Input${idx + 1}`} - address={scriptToAddress(v.lock, isMainnet)} + address={scriptToAddress(v.lock, { isMainnet })} amount={v.capacity} inputStatus={v.status === 'sent' ? 'Pending' : 'On-chain'} /> @@ -112,7 +111,7 @@ const TxTopology: FC<{ // eslint-disable-next-line react/no-array-index-key key={idx.toString()} label={v.isChangeCell ? 'Change' : 'Receive'} - address={scriptToAddress(v.lock, isMainnet)} + address={scriptToAddress(v.lock, { isMainnet })} amount={v.capacity} /> ) : null diff --git a/packages/neuron-ui/src/components/SpecialAssetList/hooks.ts b/packages/neuron-ui/src/components/SpecialAssetList/hooks.ts index 238a83770f..206ea8ceb1 100644 --- a/packages/neuron-ui/src/components/SpecialAssetList/hooks.ts +++ b/packages/neuron-ui/src/components/SpecialAssetList/hooks.ts @@ -1,5 +1,6 @@ import React, { useCallback, useEffect } from 'react' -import { ckbCore } from 'services/chain' +import { number, bytes } from '@ckb-lumos/codec' +import { type CKBComponents } from '@ckb-lumos/rpc/lib/types/api' import { getSUDTAccountList } from 'services/remote' import { NeuronWalletActions, useDispatch } from 'states' import { @@ -144,7 +145,7 @@ export const useSpecialAssetColumnInfo = ({ switch (assetInfo.lock) { case PresetScript.Locktime: { - const targetEpochInfo = epochParser(ckbCore.utils.toUint64Le(`0x${lock.args.slice(-16)}`)) + const targetEpochInfo = epochParser(bytes.hexify(number.Uint64LE.pack(`0x${lock.args.slice(-16)}`))) const currentEpochInfo = epochParser(epoch) const targetEpochFraction = Number(targetEpochInfo.length) > 0 ? Number(targetEpochInfo.index) / Number(targetEpochInfo.length) : 1 diff --git a/packages/neuron-ui/src/components/WithdrawDialog/index.tsx b/packages/neuron-ui/src/components/WithdrawDialog/index.tsx index d639999767..7d83c98df8 100644 --- a/packages/neuron-ui/src/components/WithdrawDialog/index.tsx +++ b/packages/neuron-ui/src/components/WithdrawDialog/index.tsx @@ -5,7 +5,7 @@ import { CONSTANTS, shannonToCKBFormatter, localNumberFormatter, useCalculateEpo import { getTransaction, getHeader } from 'services/chain' import Dialog from 'widgets/Dialog' import { Attention } from 'widgets/Icons/icon' -import { calculateMaximumWithdraw } from '@nervosnetwork/ckb-sdk-utils' +import { calculateMaximumWithdrawCompatible } from '@ckb-lumos/common-scripts/lib/dao' import styles from './withdrawDialog.module.scss' const { WITHDRAW_EPOCHS } = CONSTANTS @@ -49,12 +49,14 @@ const WithdrawDialog = ({ if (tx.txStatus.blockHash) { getHeader(tx.txStatus.blockHash).then(header => { setWithdrawValue( - calculateMaximumWithdraw( - tx.transaction.outputs[+record.outPoint.index], - tx.transaction.outputsData[+record.outPoint.index], + calculateMaximumWithdrawCompatible( + { + cellOutput: tx.transaction.outputs[+record.outPoint.index], + data: tx.transaction.outputsData[+record.outPoint.index], + }, header.dao, tipDao - ) + ).toHexString() ) }) } diff --git a/packages/neuron-ui/src/containers/LockWindow/index.tsx b/packages/neuron-ui/src/containers/LockWindow/index.tsx new file mode 100644 index 0000000000..3afb787ed2 --- /dev/null +++ b/packages/neuron-ui/src/containers/LockWindow/index.tsx @@ -0,0 +1,172 @@ +/* eslint-disable jsx-a11y/media-has-caption */ +import React, { useCallback, useEffect, useState } from 'react' +import { AppActions, getLockWindowInfo, useDispatch, useState as useGlobalState } from 'states' +import Spinner from 'widgets/Spinner' +import Locked from 'widgets/Icons/Locked.png' +import DarkUnLockMp4 from 'widgets/Icons/dark-unlock.mp4' +import UnLockMp4 from 'widgets/Icons/unlock.mp4' +import SplitPasswordInput from 'widgets/SplitPasswordInput' +import { useTranslation } from 'react-i18next' +import { clsx, isSuccessResponse } from 'utils' +import { isDark, unlockWindow } from 'services/remote' +import { retryUnlockWindow } from 'services/localCache' +import { MILLISECS_PER_HOUR, MILLISECS_PER_MIN, MILLISECS_PER_SEC } from 'utils/getSyncLeftTime' +import styles from './lockWindow.module.scss' + +const passwordLen = 4 +const wrongEnterTimes = 3 +const formatterLockMillisecs = (lockMillisecs: number) => { + const hrs = Math.floor(lockMillisecs / MILLISECS_PER_HOUR) + const mins = Math.floor((lockMillisecs - hrs * MILLISECS_PER_HOUR) / MILLISECS_PER_MIN) + const secs = Math.floor((lockMillisecs - hrs * MILLISECS_PER_HOUR - mins * MILLISECS_PER_MIN) / MILLISECS_PER_SEC) + return (hrs > 0 ? [hrs, mins] : [mins, secs]).map(v => v.toString().padStart(2, '0')).join(':') +} + +const getWaitMillisecs = (retryTimes: number) => { + if (retryTimes % wrongEnterTimes === 0) { + if (retryTimes >= 3 * wrongEnterTimes) { + return 24 * MILLISECS_PER_HOUR + } + if (retryTimes > wrongEnterTimes) { + return 30 * MILLISECS_PER_MIN + } + return 5 * MILLISECS_PER_MIN + } + return undefined +} + +const LockWindow = ({ children }: { children: React.ReactNode }) => { + const dispatch = useDispatch() + const [t] = useTranslation() + useEffect(() => { + getLockWindowInfo(dispatch) + }, []) + const { app } = useGlobalState() + const [password, setPassword] = useState(new Array(passwordLen).fill('')) + const [errMsg, setErrMsg] = useState('') + const [retryUnlockInfo, setRetryUnlockInfo] = useState(retryUnlockWindow.get()) + const [verifySuccess, setVerifySuccess] = useState(false) + const onUpdatePassword = useCallback( + (v: string, idx: number) => { + const updatedPassword = password.toSpliced(idx, 1, v).join('') + if (updatedPassword.length === passwordLen) { + unlockWindow(updatedPassword) + .then(res => { + if (isSuccessResponse(res)) { + setVerifySuccess(true) + setTimeout(() => { + dispatch({ + type: AppActions.SetLockWindowInfo, + payload: { locked: false }, + }) + setVerifySuccess(false) + }, 1000) + retryUnlockWindow.reset() + setRetryUnlockInfo({ retryTimes: 0 }) + setPassword(i => i.toSpliced(idx, 1, v)) + return + } + throw new Error('verify failed') + }) + .catch(() => { + const { retryTimes } = retryUnlockWindow.get() + const newRetryUnlockInfo = { lastRetryTime: Date.now(), retryTimes: retryTimes + 1 } + const needWaitMillisecs = getWaitMillisecs(retryTimes + 1) + if (needWaitMillisecs) { + setErrMsg( + t('lock-window.failed-times', { + frequency: retryTimes + 1, + time: formatterLockMillisecs(needWaitMillisecs), + }) + ) + } else { + setErrMsg(t('lock-window.lock-password-error')) + } + retryUnlockWindow.save(newRetryUnlockInfo) + setRetryUnlockInfo(newRetryUnlockInfo) + setPassword(new Array(passwordLen).fill('')) + }) + } else { + setErrMsg('') + setPassword(i => i.toSpliced(idx, 1, v)) + } + }, + [password] + ) + const [theme, setTheme] = useState<'dark' | 'light'>() + useEffect(() => { + isDark().then(res => { + if (isSuccessResponse(res)) { + setTheme(res.result ? 'dark' : 'light') + } + }) + }, []) + useEffect(() => { + if (app.lockWindowInfo?.locked) { + setPassword(new Array(passwordLen).fill('')) + setErrMsg('') + } + }, [app.lockWindowInfo?.locked]) + useEffect(() => { + let interval: ReturnType + const needWaitMillisecs = getWaitMillisecs(retryUnlockInfo.retryTimes) + if (needWaitMillisecs && retryUnlockInfo.lastRetryTime) { + interval = setInterval(() => { + const hasWaitMillisecs = Date.now() - retryUnlockInfo.lastRetryTime! + if (needWaitMillisecs > hasWaitMillisecs) { + setErrMsg( + t('lock-window.failed-times', { + frequency: retryUnlockInfo.retryTimes, + time: formatterLockMillisecs(needWaitMillisecs - hasWaitMillisecs), + }) + ) + } else { + const newRetryUnlockInfo = { retryTimes: retryUnlockInfo.retryTimes } + retryUnlockWindow.save(newRetryUnlockInfo) + setRetryUnlockInfo(newRetryUnlockInfo) + setErrMsg('') + } + }, 1_000) + } + return () => clearInterval(interval) + }, [retryUnlockInfo]) + if (!app.lockWindowInfo) { + return ( +
+ +
+ ) + } + if (app.lockWindowInfo.locked) { + return ( +
+ {verifySuccess ? ( + + ) : ( + Locked + )} +

{t('lock-window.neuron-is-locked')}

+
+ +
+
+ {errMsg || t('lock-window.enter-lock-password')} +
+
+ ) + } + return children +} + +LockWindow.displayName = 'LockWindow' + +export default LockWindow diff --git a/packages/neuron-ui/src/containers/LockWindow/lockWindow.module.scss b/packages/neuron-ui/src/containers/LockWindow/lockWindow.module.scss new file mode 100644 index 0000000000..a35391d16b --- /dev/null +++ b/packages/neuron-ui/src/containers/LockWindow/lockWindow.module.scss @@ -0,0 +1,55 @@ +.loading { + text-align: center; + margin-top: 20vh; +} + +.lockContainer { + text-align: center; + margin-top: 25vh; + + .title { + margin: 10px 0 24px 0; + font-size: 20px; + font-weight: 500; + color: var(--main-text-color); + } + + .notice { + margin-top: 24px; + color: var(--secondary-text-color); + &[data-has-err='true'] { + color: var(--error-color); + } + } + + .passwordContainer { + &[data-has-err='true'] { + input { + border-color: var(--error-color); + } + } + } + + @keyframes rotating { + from { + transform: rotate(0deg); + } + to { + transform: rotate(-10deg); + } + } + + .lockedImg { + width: 88px; + height: 88px; + + &.animation { + animation: rotating 0.5s linear 2 alternate; + } + } + + .video { + width: 88px; + height: 88px; + } +} diff --git a/packages/neuron-ui/src/containers/Main/hooks.ts b/packages/neuron-ui/src/containers/Main/hooks.ts index 9938e20ff3..307e69e956 100644 --- a/packages/neuron-ui/src/containers/Main/hooks.ts +++ b/packages/neuron-ui/src/containers/Main/hooks.ts @@ -8,6 +8,7 @@ import { updateAddressListAndBalance, initAppState, showGlobalAlertDialog, + updateLockWindowInfo, } from 'states/stateProvider/actionCreators' import { @@ -28,7 +29,7 @@ import { ShowGlobalDialog as ShowGlobalDialogSubject, NoDiskSpace, } from 'services/subjects' -import { ckbCore, getTipHeader } from 'services/chain' +import { rpc, getTipHeader } from 'services/chain' import { networks as networksCache, currentNetworkID as currentNetworkIDCache, @@ -77,13 +78,13 @@ export const useSyncChainData = ({ chainURL, dispatch }: { chainURL: string; dis } clearInterval(timer!) if (chainURL) { - ckbCore.setNode(chainURL) + rpc.setNode({ url: chainURL }) syncBlockchainInfo() timer = setInterval(() => { syncBlockchainInfo() }, SYNC_INTERVAL_TIME) } else { - ckbCore.setNode('') + rpc.setNode({ url: '' }) } return () => { clearInterval(timer) @@ -114,6 +115,8 @@ export const useSubscription = ({ dispatch, location, showSwitchNetwork, + lockWindowInfo, + setIsLockDialogShow, }: { walletID: string chain: State.Chain @@ -122,6 +125,8 @@ export const useSubscription = ({ location: ReturnType dispatch: StateDispatch showSwitchNetwork: () => void + lockWindowInfo: State.App['lockWindowInfo'] + setIsLockDialogShow: (v: boolean) => void }) => { const { pageNo, pageSize, keywords } = chain.transactions @@ -304,6 +309,15 @@ export const useSubscription = ({ case 'multisig-address': navigateToolsRouter(type) break + case 'lock-window': + if (lockWindowInfo?.encryptedPassword) { + if (!lockWindowInfo.locked) { + updateLockWindowInfo({ locked: true })(dispatch) + } + } else { + setIsLockDialogShow(true) + } + break default: { break } @@ -332,6 +346,8 @@ export const useSubscription = ({ dispatch, location.pathname, showSwitchNetwork, + lockWindowInfo, + setIsLockDialogShow, ]) } diff --git a/packages/neuron-ui/src/containers/Main/index.tsx b/packages/neuron-ui/src/containers/Main/index.tsx index 28421862c7..038ca40a31 100644 --- a/packages/neuron-ui/src/containers/Main/index.tsx +++ b/packages/neuron-ui/src/containers/Main/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo, useEffect } from 'react' +import React, { useCallback, useMemo, useEffect, useState } from 'react' import { useNavigate, useLocation, Outlet } from 'react-router-dom' import { Trans, useTranslation } from 'react-i18next' import { useState as useGlobalState, useDispatch, dismissGlobalAlertDialog } from 'states' @@ -13,6 +13,7 @@ import DataPathDialog from 'widgets/DataPathDialog' import NoDiskSpaceWarn from 'widgets/Icons/NoDiskSpaceWarn.png' import MigrateCkbDataDialog from 'widgets/MigrateCkbDataDialog' import { keepScreenAwake } from 'services/localCache' +import LockWindowDialog from 'components/GeneralSetting/LockWindowDialog' import styles from './main.module.scss' import { useSubscription, useSyncChainData, useOnCurrentWalletChange, useCheckNode, useNoDiskSpace } from './hooks' @@ -20,7 +21,7 @@ const MainContent = () => { const navigate = useNavigate() const location = useLocation() const { - app: { isAllowedToFetchList = true, globalAlertDialog }, + app: { isAllowedToFetchList = true, globalAlertDialog, lockWindowInfo }, wallet: { id: walletID = '' }, chain, settings: { networks = [] }, @@ -52,6 +53,7 @@ const MainContent = () => { onOpenEditorDialog, } = useCheckNode(sameUrlNetworks, networkID) + const [isLockDialogShow, setIsLockDialogShow] = useState(false) useSubscription({ walletID, chain, @@ -60,6 +62,8 @@ const MainContent = () => { dispatch, location, showSwitchNetwork, + lockWindowInfo, + setIsLockDialogShow, }) useOnCurrentWalletChange({ @@ -181,6 +185,13 @@ const MainContent = () => { onCancel={onCloseMigrateDialog} onConfirm={onConfirmMigrate} /> + { + setIsLockDialogShow(false) + }} + />
) } diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index 5e16c40df8..cb311cc300 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -255,7 +255,8 @@ "allow-use-sent-cell": "Unconfirmed Outputs are allowed in this transaction.", "submit-transaction": "Submit the transaction", "transaction-confirmed": "Transaction Confirmed", - "transaction-cannot-amend": "The current transaction is confirmed and cannot be amended." + "transaction-cannot-amend": "The current transaction is confirmed and cannot be amended.", + "amend-pending-transaction": "Amend Pending Transaction" }, "receive": { "title": "Receive", @@ -395,7 +396,21 @@ "language": "Language", "select-language": "Select Language", "apply": "Apply", - "keep-awake": "Keep the screen awake during synchronization" + "keep-awake": "Keep the screen awake during synchronization", + "lock-password": "Lock window password", + "set-lock-password": "Set lock window password", + "change-lock-password": "Change lock window password", + "lock-window": { + "set-password": "Set Password", + "confirm-password": "Confirm Password", + "enter-current-password": "Enter Password", + "enter-new-password": "Enter new password", + "password-error": "Password error", + "different-password": "Two entered passwords do not match", + "set-password-success": "Password set successfully", + "change-password-success": "Password change successfully.", + "reset": "Reset" + } }, "wallet-manager": { "edit-wallet": { @@ -691,7 +706,7 @@ "free": "Available", "locked": "Locked", "deposit": "Deposit", - "deposit-rules": "Deposit rules", + "deposit-rules": "Deposit Rules", "copy-balance": "Copy Balance", "deposit-records": "Deposits", "completed-records": "Completed", @@ -714,6 +729,8 @@ "estimated-apc": "Estimated APC", "estimated-apc-tooltip": "Estimated Annual Percentage Compensation", "deposit-amount": "Deposit Amount (CKB)", + "attention": "Attention: Once deposited into the Nervos DAO, your deposits will be locked for at least 180 Epochs (about 30 days).", + "deposit-submitted": "Deposit submitted", "deposit-record": { "deposited-at": "Deposited", "completed-at": "Completed", @@ -730,7 +747,8 @@ "days-hours": "{{days}} days {{hours}} hours", "no-deposit": "No Deposit", "no-completed": "No Completed", - "insufficient-balance-to-unlock": "Balance is not enough for transaction fee to unlock the deposit" + "insufficient-balance-to-unlock": "Balance is not enough for transaction fee to unlock the deposit", + "lock-warn": "Lock to end of current compensation cycle" }, "compensation-period": { "tooltip": { @@ -743,12 +761,14 @@ "withdrawn": "Withdrawn" }, "stage-messages": { - "pending": "Pending...", + "deposit-in-progress": "Deposit in progress", "immature-for-withdraw": "CKB cannot be withdrawn for the next {{ hours }} hours", "immature-for-unlock": "CKB cannot be unlocked for the next {{ hours }} hours", "next-compensation-cycle": "The next compensation cycle will start in approximately {{days}} days", + "next-compensation-cycle-hours": "The next compensation cycle will start in approximately {{hours}} hours", "withdrawing": "Withdrawing...", "compensation-cycle-will-end": "Compensation cycle ends in approximately {{days}} days", + "compensation-cycle-will-end-hours": "Compensation cycle ends in approximately {{hours}} hours", "compensation-cycle-has-ended": "Cycle has ended, CKB is ready to be unlocked", "unlocking": "Unlocking..." } @@ -771,6 +791,16 @@ "cancel": "Cancel", "next": "Next" }, + "deposit-rules": { + "minimum-deposit": "Minimum Deposit", + "single-compensation-cycle": "Single Compensation Cycle", + "single-compensation-cycle-description": "180 Epochs (About 30 days)", + "withdraw": "Withdraw", + "withdraw-description": "Anytime", + "unlock": "Unlock", + "unlock-description": "After the current compensation cycle", + "get": "Get" + }, "lock-info-dialog": { "address-info": "Address Information", "deprecated-address": "Deprecated address" @@ -1254,6 +1284,12 @@ "size": "Size", "lock-time": "Lock Time" } + }, + "lock-window": { + "neuron-is-locked": "Neuron's window has been locked", + "enter-lock-password": "Enter lock password", + "lock-password-error": "Lock windows password error", + "failed-times": "Failed more than {{frequency}} times, please retry after {{time}}" } } } diff --git a/packages/neuron-ui/src/locales/es.json b/packages/neuron-ui/src/locales/es.json index 590e452f5f..bb7093c4e9 100644 --- a/packages/neuron-ui/src/locales/es.json +++ b/packages/neuron-ui/src/locales/es.json @@ -247,7 +247,8 @@ "allow-use-sent-cell": "Se permite el uso de salidas no confirmadas en esta transacción.", "submit-transaction": "Enviar la transacción", "transaction-confirmed": "Transacción confirmada", - "transaction-cannot-amend": "La transacción actual está confirmada y no puede modificarse." + "transaction-cannot-amend": "La transacción actual está confirmada y no puede modificarse.", + "amend-pending-transaction": "Modificar transacción pendiente" }, "receive": { "title": "Recibir", @@ -387,7 +388,21 @@ "language": "Idioma", "select-language": "Seleccionar Idioma", "apply": "Aplicar", - "keep-awake": "Mantener la pantalla encendida durante la sincronización" + "keep-awake": "Mantener la pantalla encendida durante la sincronización", + "lock-password": "Contraseña de bloqueo de pantalla", + "set-lock-password": "Establecer contraseña de bloqueo de pantalla", + "change-lock-password": "Modificar contraseña de bloqueo de pantalla", + "lock-window": { + "set-password": "Establecer contraseña", + "confirm-password": "Confirmar contraseña", + "enter-current-password": "Ingresar contraseña actual", + "enter-new-password": "Ingresar nueva contraseña", + "password-error": "Contraseña incorrecta", + "different-password": "Las contraseñas ingresadas no coinciden", + "set-password-success": "Contraseña establecida exitosamente", + "change-password-success": "Contraseña modificada exitosamente", + "reset": "Reestablecer" + } }, "wallet-manager": { "edit-wallet": { @@ -697,6 +712,8 @@ "estimated-apc": "Estimación APC", "estimated-apc-tooltip": "Estimación de Compensación Anual", "deposit-amount": "Cantidad a Depositar (CKB)", + "attention": "Atención: Una vez depositado en el DAO de Nervos, sus depósitos estarán bloqueados durante al menos 180 épocas (unos 30 días).", + "deposit-submitted": "Depósito presentado", "deposit-record": { "deposited-at": "Depositado", "completed-at": "Completado", @@ -712,7 +729,9 @@ "compensated-period": "Período de Compensación", "days-hours": "{{days}} días {{hours}} horas", "no-deposit": "Sin Depósito", - "no-completed": "Sin Completar" + "no-completed": "Sin Completar", + "insufficient-balance-to-unlock": "El saldo no es suficiente para que la comisión de transacción desbloquee el depósito", + "lock-warn": "Bloqueo al final del ciclo de compensación actual" }, "compensation-period": { "tooltip": { @@ -725,12 +744,14 @@ "withdrawn": "Retirado" }, "stage-messages": { - "pending": "Pendiente...", - "immature-for-withdraw": "No se puede retirar CKB durante las próximas {{ horas }} horas", - "immature-for-unlock": "No se puede desbloquear CKB durante las próximas {{ horas }} horas", - "next-compensation-cycle": "El siguiente ciclo de compensación comenzará en aproximadamente {{días}} días", + "deposit-in-progress": "Depósito en curso...", + "immature-for-withdraw": "No se puede retirar CKB durante las próximas {{ hours }} horas", + "immature-for-unlock": "No se puede desbloquear CKB durante las próximas {{ hours }} horas", + "next-compensation-cycle": "El siguiente ciclo de compensación comenzará en aproximadamente {{days}} días", + "next-compensation-cycle-hours": "El siguiente ciclo de compensación comenzará en aproximadamente {{hours}} horas", "withdrawing": "Retirando...", - "compensation-cycle-will-end": "El ciclo de compensación termina en aproximadamente {{días}} días", + "compensation-cycle-will-end": "El ciclo de compensación termina en aproximadamente {{days}} días", + "compensation-cycle-will-end-hours": "El ciclo de compensación termina en aproximadamente {{hours}} horas", "compensation-cycle-has-ended": "El ciclo ha terminado, CKB está listo para ser desbloqueado", "unlocking": "Desbloqueando..." } @@ -753,6 +774,16 @@ "cancel": "Cancelar", "next": "Siguiente" }, + "deposit-rules": { + "minimum-deposit": "Depósito mínimo", + "single-compensation-cycle": "Ciclo de compensación único", + "single-compensation-cycle-description": "180 Época (unos 30 días)", + "withdraw": "Retirar", + "withdraw-description": "En cualquier momento", + "unlock": "Desbloquear", + "unlock-description": "Después del ciclo de compensación actual", + "get": "Visite" + }, "lock-info-dialog": { "address-info": "Información de dirección", "deprecated-address": "Dirección obsoleta" @@ -1233,6 +1264,12 @@ "size": "tamaño", "lock-time": "Período de Bloqueo" } + }, + "lock-window": { + "neuron-is-locked": "La ventana de Neuron está bloqueada", + "enter-lock-password": "Ingresar contraseña de bloqueo de pantalla", + "lock-password-error": "Contraseña de bloqueo de pantalla incorrecta", + "failed-times": "Fallo más de {{frequency}} veces, por favor inténtalo de nuevo después de {{time}}" } } } diff --git a/packages/neuron-ui/src/locales/fr.json b/packages/neuron-ui/src/locales/fr.json index c42bfc3594..534e1fd1d2 100644 --- a/packages/neuron-ui/src/locales/fr.json +++ b/packages/neuron-ui/src/locales/fr.json @@ -254,7 +254,8 @@ "allow-use-sent-cell": "Les sorties non confirmées sont autorisées dans cette transaction.", "submit-transaction": "Soumettre la transaction", "transaction-confirmed": "Transaction confirmée", - "transaction-cannot-amend": "La transaction en cours est confirmée et ne peut être modifiée." + "transaction-cannot-amend": "La transaction en cours est confirmée et ne peut être modifiée.", + "amend-pending-transaction": "Modifier une transaction en cours" }, "receive": { "title": "Recevoir", @@ -394,7 +395,21 @@ "language": "Langue", "select-language": "Sélectionner la langue", "apply": "Appliquer", - "keep-awake": "Maintenir l'écran allumé pendant la synchronisation" + "keep-awake": "Maintenir l'écran allumé pendant la synchronisation", + "lock-password": "Mot de passe de verrouillage de l'écran", + "set-lock-password": "Configurer le mot de passe de verrouillage de l'écran", + "change-lock-password": "Modifier le mot de passe de verrouillage de l'écran", + "lock-window": { + "set-password": "Créer un mot de passe", + "confirm-password": "Confirmer le mot de passe", + "enter-current-password": "Entrer le mot de passe actuel", + "enter-new-password": "Entrer le nouveau mot de passe", + "password-error": "Mot de passe incorrect", + "different-password": "Les deux mots de passe entrés ne correspondent pas", + "set-password-success": "Mot de passe défini avec succès", + "change-password-success": "Mot de passe modifié avec succès", + "reset": "Réinitialiser" + } }, "wallet-manager": { "edit-wallet": { @@ -704,6 +719,8 @@ "estimated-apc": "Estimation de APC", "estimated-apc-tooltip": "Estimation de Compensation Annuel", "deposit-amount": "Montant du dépôt (CKB)", + "attention": "Attention : Une fois déposés dans la DAO Nervos, vos dépôts seront bloqués pendant au moins 180 époques (environ 30 jours).", + "deposit-submitted": "Dépôt de garantie", "deposit-record": { "deposited-at": "Déposé", "completed-at": "Terminé", @@ -720,7 +737,8 @@ "days-hours": "{{days}} jours {{hours}} heures", "no-deposit": "Aucun dépôt", "no-completed": "Aucun terminé", - "insufficient-balance-to-unlock": "Le solde n'est pas suffisant pour payer les frais de transaction afin de débloquer le dépôt DAO." + "insufficient-balance-to-unlock": "Le solde n'est pas suffisant pour payer les frais de transaction afin de débloquer le dépôt DAO.", + "lock-warn": "Verrouillage à la fin du cycle de compensation en cours" }, "compensation-period": { "tooltip": { @@ -733,12 +751,14 @@ "withdrawn": "Retiré" }, "stage-messages": { - "pending": "En attente...", + "deposit-in-progress": "En attente...", "immature-for-withdraw": "Les CKB ne peuvent pas être retirés pendant les {{ hours }} prochaines heures", "immature-for-unlock": "Les CKB ne peuvent pas être déverrouillés pendant les {{ hours }} prochaines heures", "next-compensation-cycle": "Le prochain cycle de compensation commencera dans environ {{days}} jours", + "next-compensation-cycle-hours": "Le prochain cycle de compensation commencera dans environ {{hours}} heures", "withdrawing": "Retrait en cours...", "compensation-cycle-will-end": "La période de compensation se termine dans environ {{days}} jours", + "compensation-cycle-will-end-hours": "La période de compensation se termine dans environ {{hours}} heures", "compensation-cycle-has-ended": "Le cycle est terminé, les CKB sont prêts à être déverrouillés", "unlocking": "Déverrouillage en cours..." } @@ -761,6 +781,16 @@ "cancel": "Annuler", "next": "Suivant" }, + "deposit-rules": { + "minimum-deposit": "Dépôt minimum", + "single-compensation-cycle": "Cycle de compensation unique", + "single-compensation-cycle-description": "180 Époque (environ 30 jours)", + "withdraw": "Retirer", + "withdraw-description": "A tout moment", + "unlock": "Déverrouiller", + "unlock-description": "Après le cycle de compensation en cours", + "get": "Obtenir" + }, "lock-info-dialog": { "address-info": "Informations sur l'adresse", "deprecated-address": "Adresse obsolète" @@ -1244,6 +1274,12 @@ "size": "taille", "lock-time": "Période de verrouillage" } + }, + "lock-window": { + "neuron-is-locked": "a fenêtre de Neuron est verrouillée", + "enter-lock-password": "Entrer le mot de passe de verrouillage d'écran", + "lock-password-error": "Mot de passe de verrouillage d'écran incorrect", + "failed-times": "Échec plus de {{frequency}} fois, veuillez réessayer après {{time}}" } } } diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index b6b4baad1d..4350e2981a 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -249,7 +249,8 @@ "allow-use-sent-cell": "允許使用待確認的 Outputs 構建交易", "submit-transaction": "發起交易", "transaction-confirmed": "交易已確認", - "transaction-cannot-amend": "目前交易已確認,無法修改。" + "transaction-cannot-amend": "目前交易已確認,無法修改。", + "amend-pending-transaction": "修改待處理交易" }, "receive": { "title": "收款", @@ -390,7 +391,21 @@ "language": "語言", "select-language": "選擇語言", "apply": "應用", - "keep-awake": "同步期間保持屏幕喚醒" + "keep-awake": "同步期間保持屏幕喚醒", + "lock-password": "鎖屏密碼", + "set-lock-password": "設置鎖屏密碼", + "change-lock-password": "修改鎖屏密碼", + "lock-window": { + "set-password": "設置密碼", + "confirm-password": "確認密碼", + "enter-current-password": "輸入當前密碼", + "enter-new-password": "輸入新密碼", + "password-error": "密碼錯誤", + "different-password": "兩次輸入密碼不壹致", + "set-password-success": "密碼設置成功", + "change-password-success": "密碼修改成功", + "reset": "重新設置" + } }, "wallet-manager": { "edit-wallet": { @@ -708,6 +723,8 @@ "estimated-apc": "預計年化鎖定補貼率", "estimated-apc-tooltip": "預計年化鎖定補貼率", "deposit-amount": "存入金額 (CKB)", + "attention": "請注意: 一旦存入 Nervos DAO,您的存款將被鎖定至少 180 個 Epochs(約 30 天)。", + "deposit-submitted": "存入已提交", "deposit-record": { "deposited-at": "存入於", "completed-at": "已取出", @@ -724,7 +741,8 @@ "days-hours": "{{days}} 天 {{hours}} 小時", "no-deposit": "沒有存入記錄", "no-completed": "沒有解鎖記錄", - "insufficient-balance-to-unlock": "沒有足夠余額支付交易費用以解鎖 DAO 存款" + "insufficient-balance-to-unlock": "沒有足夠余額支付交易費用以解鎖 DAO 存款", + "lock-warn": "鎖定至當前補償週期結束" }, "compensation-period": { "tooltip": { @@ -737,17 +755,50 @@ "withdrawn": "已提取" }, "stage-messages": { - "pending": "正在存入...", + "deposit-in-progress": "正在存入...", "immature-for-withdraw": "{{hours}} 小時內無法提取 CKB", "immature-for-unlock": "{{hours}} 小時內無法解鎖 CKB", "next-compensation-cycle": "下一補償週期約在 {{days}} 天后開始", + "next-compensation-cycle-hours": "下一補償週期約在 {{hours}} 小時后開始", "withdrawing": "正在提取...", "compensation-cycle-will-end": "補償週期約在 {{days}} 天后結束", + "compensation-cycle-will-end-hours": "補償週期約在 {{hours}} 小時后結束", "compensation-cycle-has-ended": "補償週期已結束, 可以解鎖 CKB", "unlocking": "解鎖中..." } } }, + "nervos-dao-detail": { + "tx-detail": "交易詳情", + "deposited": "存入", + "withdrawn": "發起提取", + "unlocked": "解鎖", + "basic-information": "基本信息", + "transaction-hash": "交易哈希", + "blockNumber": "區塊高度", + "datetime": "時間", + "income": "收入", + "index": "序號", + "address": "地址", + "amount": "數量", + "cell-from-cellbase": "來自 Cellbase", + "cancel": "取消", + "next": "下一步" + }, + "deposit-rules": { + "minimum-deposit": "最低存入額", + "single-compensation-cycle": "單一補償週期", + "single-compensation-cycle-description": "180 個 Epochs(約 30 天)", + "withdraw": "提取", + "withdraw-description": "任意時間", + "unlock": "解鎖", + "unlock-description": "目前補償週期結束後", + "get": "好的" + }, + "lock-info-dialog": { + "address-info": "地址信息", + "deprecated-address": "已棄用格式的地址" + }, "updates": { "check-updates": "檢查更新", "checking-updates": "正在檢查更新…", @@ -1222,6 +1273,12 @@ "size": "大小", "lock-time": "鎖定時間" } + }, + "lock-window": { + "neuron-is-locked": "Neuron 窗口已鎖定", + "enter-lock-password": "輸入鎖屏密碼", + "lock-password-error": "鎖屏密碼錯誤", + "failed-times": "失敗超過 {{frequency}} 次, 請在 {{time}} 後重試" } } } diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index c837c54415..86b3a03864 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -248,7 +248,8 @@ "allow-use-sent-cell": "允许使用待确认的 Outputs 构建交易", "submit-transaction": "发起交易", "transaction-confirmed": "交易已确认", - "transaction-cannot-amend": "当前交易已确认,无法修改。" + "transaction-cannot-amend": "当前交易已确认,无法修改。", + "amend-pending-transaction": "修改待处理交易" }, "receive": { "title": "收款", @@ -388,7 +389,21 @@ "language": "系统语言", "select-language": "选择语言", "apply": "应用", - "keep-awake": "同步期间保持屏幕唤醒" + "keep-awake": "同步期间保持屏幕唤醒", + "lock-password": "锁屏密码", + "set-lock-password": "设置锁屏密码", + "change-lock-password": "修改锁屏密码", + "lock-window": { + "set-password": "设置密码", + "confirm-password": "确认密码", + "enter-current-password": "输入当前密码", + "enter-new-password": "输入新密码", + "password-error": "密码错误", + "different-password": "两次输入密码不一致", + "set-password-success": "密码设置成功", + "change-password-success": "密码修改成功", + "reset": "重新设置" + } }, "wallet-manager": { "edit-wallet": { @@ -707,6 +722,8 @@ "estimated-apc": "预计年化锁定补贴率", "estimated-apc-tooltip": "预计年化锁定补贴率", "deposit-amount": "存入金额 (CKB)", + "attention": "请注意: 一旦存入 Nervos DAO,您的存款将被锁定至少 180 个 Epochs(约 30 天)。", + "deposit-submitted": "存入已提交", "deposit-record": { "deposited-at": "存入于", "completed-at": "已取出", @@ -723,7 +740,8 @@ "days-hours": "{{days}} 天 {{hours}} 小时", "no-deposit": "没有存入记录", "no-completed": "没有解锁记录", - "insufficient-balance-to-unlock": "没有足够余额支付交易费用以解锁 DAO 存款" + "insufficient-balance-to-unlock": "没有足够余额支付交易费用以解锁 DAO 存款", + "lock-warn": "锁定至当前补偿周期结束" }, "compensation-period": { "tooltip": { @@ -736,12 +754,14 @@ "withdrawn": "已提取" }, "stage-messages": { - "pending": "正在存入...", + "deposit-in-progress": "正在存入...", "immature-for-withdraw": "{{hours}} 小时内无法提取 CKB", "immature-for-unlock": "{{hours}} 小时内无法解锁 CKB", "next-compensation-cycle": "下一补偿周期约在 {{days}} 天后开始", + "next-compensation-cycle-hours": "下一补偿周期约在 {{hours}} 小时后开始", "withdrawing": "正在提取...", "compensation-cycle-will-end": "补偿周期约在 {{days}} 天后结束", + "compensation-cycle-will-end-hours": "补偿周期约在 {{hours}} 小时后结束", "compensation-cycle-has-ended": "补偿周期已结束, 可以解锁 CKB", "unlocking": "解锁中..." } @@ -764,6 +784,16 @@ "cancel": "取消", "next": "下一步" }, + "deposit-rules": { + "minimum-deposit": "最低存入额", + "single-compensation-cycle": "单补偿周期", + "single-compensation-cycle-description": "180 个 Epochs(约 30 天)", + "withdraw": "提取", + "withdraw-description": "任意时间", + "unlock": "解锁", + "unlock-description": "当前补偿周期结束后", + "get": "好的" + }, "lock-info-dialog": { "address-info": "地址信息", "deprecated-address": "已弃用格式的地址" @@ -1246,6 +1276,12 @@ "size": "大小", "lock-time": "锁定时间" } + }, + "lock-window": { + "neuron-is-locked": "Neuron 窗口已锁定", + "enter-lock-password": "输入锁屏密码", + "lock-password-error": "锁屏密码错误", + "failed-times": "失败超过 {{frequency}} 次, 请在 {{time}} 后重试" } } } diff --git a/packages/neuron-ui/src/router.tsx b/packages/neuron-ui/src/router.tsx index 319ebc3274..042c32b7c7 100644 --- a/packages/neuron-ui/src/router.tsx +++ b/packages/neuron-ui/src/router.tsx @@ -1,5 +1,6 @@ import React from 'react' import { Outlet, RouteObject } from 'react-router-dom' +import LockWindow from 'containers/LockWindow' import Main from 'containers/Main' import Navbar from 'containers/Navbar' import { RoutePath } from 'utils' @@ -50,11 +51,11 @@ const mainRouterConfig: RouteObject[] = [ { path: '/', element: ( - <> +
- + ), children: [ { diff --git a/packages/neuron-ui/src/services/chain.ts b/packages/neuron-ui/src/services/chain.ts index 0ca47c4951..31dac003db 100644 --- a/packages/neuron-ui/src/services/chain.ts +++ b/packages/neuron-ui/src/services/chain.ts @@ -1,16 +1,6 @@ -import CKBCore from '@nervosnetwork/ckb-sdk-core' +import { CKBRPC } from '@ckb-lumos/rpc' -export const ckbCore = new CKBCore('') -export const { getHeader, getBlockchainInfo, getTipHeader, getHeaderByNumber, getFeeRateStats, getTransaction } = - ckbCore.rpc +export const rpc = new CKBRPC('') -export const { toUint64Le, parseEpoch } = ckbCore.utils - -export default { - ckbCore, - getHeader, - getTipHeader, - getTransaction, - toUint64Le, - getFeeRateStats, -} +export const { getHeader, getBlockchainInfo, getTipHeader, getHeaderByNumber, getFeeRateStatistics, getTransaction } = + rpc diff --git a/packages/neuron-ui/src/services/localCache.ts b/packages/neuron-ui/src/services/localCache.ts index a53f75513c..84851f4c03 100644 --- a/packages/neuron-ui/src/services/localCache.ts +++ b/packages/neuron-ui/src/services/localCache.ts @@ -12,6 +12,7 @@ export enum LocalCacheKey { ImportedWallet = 'ImportedWallet', ShownNodeId = 'ShownNodeId', ScreenAwake = 'ScreenAwake', + RetryUnlockWindowInfo = 'RetryUnlockWindowInfo', } export const addresses = { @@ -176,3 +177,20 @@ export const keepScreenAwake = { window.localStorage.setItem(LocalCacheKey.ScreenAwake, value.toString()) }, } + +export const retryUnlockWindow = { + reset: () => { + window.localStorage.setItem(LocalCacheKey.RetryUnlockWindowInfo, JSON.stringify({ retryTimes: 0 })) + }, + save: (info: { lastRetryTime?: number; retryTimes: number }) => { + window.localStorage.setItem(LocalCacheKey.RetryUnlockWindowInfo, JSON.stringify(info)) + }, + get: (): { lastRetryTime?: number; retryTimes: number } => { + try { + const info = window.localStorage.getItem(LocalCacheKey.RetryUnlockWindowInfo) + return info ? JSON.parse(info) : { retryTimes: 0 } + } catch (error) { + return { retryTimes: 0 } + } + }, +} diff --git a/packages/neuron-ui/src/services/remote/app.ts b/packages/neuron-ui/src/services/remote/app.ts index ce391b86dc..48a16e2962 100644 --- a/packages/neuron-ui/src/services/remote/app.ts +++ b/packages/neuron-ui/src/services/remote/app.ts @@ -47,3 +47,9 @@ export const invokeGetAllDisplaysSize = remoteApi('get-all-display export const invokeShowMessageBox = remoteApi('show-message-box') export const isDark = remoteApi('is-dark') export const setTheme = remoteApi<'light' | 'dark', void>('set-theme') +export const getLockWindowInfo = remoteApi('get-lock-window-info') +export const updateLockWindowInfo = remoteApi<{ locked?: boolean; password?: string }, State.App['lockWindowInfo']>( + 'update-lock-window-info' +) +export const verifyLockWindowPassword = remoteApi('verify-lock-window-password') +export const unlockWindow = remoteApi('unlock-window') diff --git a/packages/neuron-ui/src/services/remote/offline.ts b/packages/neuron-ui/src/services/remote/offline.ts index 6824bd9c0a..0d2a4d917f 100644 --- a/packages/neuron-ui/src/services/remote/offline.ts +++ b/packages/neuron-ui/src/services/remote/offline.ts @@ -45,7 +45,6 @@ export const exportTransactionAsJSON = remoteApi('export- export const signTransactionOnly = remoteApi('sign-transaction-only') export const broadcastTransaction = remoteApi('broadcast-transaction-only') export const broadcastSignedTransaction = remoteApi('broadcast-signed-transaction') -export const getTransactionSize = remoteApi('get-transaction-size') export const signAndExportTransaction = remoteApi( 'sign-and-export-transaction' ) diff --git a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts index 5be98553fc..8640a2c384 100644 --- a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts +++ b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts @@ -52,6 +52,10 @@ type Action = | 'start-node-ignore-external' | 'get-first-sync-info' | 'start-sync' + | 'get-lock-window-info' + | 'update-lock-window-info' + | 'verify-lock-window-password' + | 'unlock-window' // Wallets | 'get-all-wallets' | 'get-current-wallet' @@ -116,7 +120,6 @@ type Action = | 'send-to-anyone-can-pay' | 'get-token-info-list' | 'migrate-acp' - | 'check-migrate-acp' | 'get-sudt-token-info' | 'generate-destroy-asset-account-tx' | 'get-sudt-type-script-hash' @@ -139,7 +142,6 @@ type Action = | 'sign-transaction-only' | 'broadcast-transaction-only' | 'broadcast-signed-transaction' - | 'get-transaction-size' | 'sign-and-export-transaction' | 'sign-and-broadcast-transaction' // nft diff --git a/packages/neuron-ui/src/services/remote/sudt.ts b/packages/neuron-ui/src/services/remote/sudt.ts index ace0358d7c..0faf64622c 100644 --- a/packages/neuron-ui/src/services/remote/sudt.ts +++ b/packages/neuron-ui/src/services/remote/sudt.ts @@ -27,10 +27,6 @@ export const getHoldSUDTCellCapacity = remoteApi<{ address: string; tokenID: str export const sendSUDTTransaction = remoteApi('send-to-anyone-can-pay') -export const checkMigrateAcp = remoteApi( - 'check-migrate-acp' -) - export const migrateAcp = remoteApi('migrate-acp') export const getSUDTTokenInfo = remoteApi( diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts index dd63438b57..1a92c5b6b0 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts @@ -1,5 +1,9 @@ import { NeuronWalletActions, AppActions, StateDispatch } from 'states/stateProvider/reducer' -import { getNeuronWalletState } from 'services/remote' +import { + getLockWindowInfo as getLockWindowInfoAPI, + updateLockWindowInfo as updateLockWindowInfoAPI, + getNeuronWalletState, +} from 'services/remote' import initStates from 'states/init' import { RoutePath, ErrorCode, addressesToBalance, isSuccessResponse } from 'utils' import { WalletWizardPath } from 'components/WalletWizard' @@ -150,3 +154,25 @@ export const showPageNotice = }) }, 2000) } + +export const getLockWindowInfo = (dispatch: StateDispatch) => { + return getLockWindowInfoAPI().then(res => { + if (isSuccessResponse(res) && res.result) { + dispatch({ + type: AppActions.SetLockWindowInfo, + payload: res.result, + }) + } + }) +} + +export const updateLockWindowInfo = (params: { locked?: boolean; password?: string }) => (dispatch: StateDispatch) => { + updateLockWindowInfoAPI(params).then(res => { + if (isSuccessResponse(res) && res.result) { + dispatch({ + type: AppActions.SetLockWindowInfo, + payload: res.result, + }) + } + }) +} diff --git a/packages/neuron-ui/src/states/stateProvider/reducer.ts b/packages/neuron-ui/src/states/stateProvider/reducer.ts index 5579dcd438..2d64d0c029 100644 --- a/packages/neuron-ui/src/states/stateProvider/reducer.ts +++ b/packages/neuron-ui/src/states/stateProvider/reducer.ts @@ -69,6 +69,8 @@ export enum AppActions { // Cell manage UpdateConsumeCells = 'UpdateConsumeCells', + // lock window + SetLockWindowInfo = 'SetLockWindowInfo', } export type StateAction = @@ -121,6 +123,7 @@ export type StateAction = | { type: NeuronWalletActions.GetSUDTAccountList; payload: Controller.GetSUDTAccountList.Response } | { type: AppActions.SignVerify; payload: string } | { type: AppActions.UpdateConsumeCells; payload?: { outPoint: OutPoint; capacity: string }[] } + | { type: AppActions.SetLockWindowInfo; payload: Required['lockWindowInfo'] } export type StateDispatch = React.Dispatch // TODO: add type of payload @@ -420,6 +423,11 @@ export const reducer = produce((state: Draft, action: break } + case AppActions.SetLockWindowInfo: { + state.app.lockWindowInfo = { ...(state.app.lockWindowInfo ?? {}), ...action.payload } + break + } + default: { break } diff --git a/packages/neuron-ui/src/stories/ScriptTag.stories.tsx b/packages/neuron-ui/src/stories/ScriptTag.stories.tsx index 0c5978d95c..ec88624ea6 100644 --- a/packages/neuron-ui/src/stories/ScriptTag.stories.tsx +++ b/packages/neuron-ui/src/stories/ScriptTag.stories.tsx @@ -1,6 +1,7 @@ import { Meta, StoryObj } from '@storybook/react' import { withRouter } from 'storybook-addon-react-router-v6' import { action } from '@storybook/addon-actions' +import { type CKBComponents } from '@ckb-lumos/rpc/lib/types/api' import ScriptTag from 'components/ScriptTag' const scripts: Record = { diff --git a/packages/neuron-ui/src/tests/formatters/sudtValueToAmount/fixtures.ts b/packages/neuron-ui/src/tests/formatters/sudtValueToAmount/fixtures.ts index a91adedd80..e6ec65d7b8 100644 --- a/packages/neuron-ui/src/tests/formatters/sudtValueToAmount/fixtures.ts +++ b/packages/neuron-ui/src/tests/formatters/sudtValueToAmount/fixtures.ts @@ -10,18 +10,28 @@ export default { expected: '0', }, '0 decimal': { - value: '1', + value: '12345', decimal: '0', - expected: '1', + expected: '12,345', }, '1 decimal': { value: '1', decimal: '1', expected: '0.1', }, + '2 decimal': { + value: '1234567890', + decimal: '2', + expected: '12,345,678.9', + }, '32 decimal': { value: '100000000000000000000000000000001', decimal: '32', expected: '1.00000000000000000000000000000001', }, + '32 decimal and commas': { + value: '12345678900000000000000000000000000000001', + decimal: '32', + expected: '123,456,789.00000000000000000000000000000001', + }, } diff --git a/packages/neuron-ui/src/tests/getMultisigSignStatus/index.test.ts b/packages/neuron-ui/src/tests/getMultisigSignStatus/index.test.ts index 4820f0fe7f..71ad89f736 100644 --- a/packages/neuron-ui/src/tests/getMultisigSignStatus/index.test.ts +++ b/packages/neuron-ui/src/tests/getMultisigSignStatus/index.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect } from '@jest/globals' import { MultisigConfig } from 'services/remote' -import getMultisigSignStatus from 'utils/getMultisigSignStatus' -import { addressToScript, scriptToHash } from '@nervosnetwork/ckb-sdk-utils' +import { addressToScript, getMultisigSignStatus } from 'utils' +import { computeScriptHash } from '@ckb-lumos/base/lib/utils' const addresses = [ 'ckt1qyqwh5hmt8j59njztrfz6z0s9wug3nv5qysqrnfm2h', @@ -18,7 +18,7 @@ const multisigConfig: MultisigConfig = { blake160s: addresses.map(v => addressToScript(v).args), fullPayload: 'ckt1qpw9q60tppt7l3j7r09qcp7lxnp3vcanvgha8pmvsa3jplykxn32sq2f2scddm0lvmq36hmzx8nfhw8ucxzslhqussgky', } -const fullPayloadHash = scriptToHash(addressToScript(multisigConfig.fullPayload)) +const fullPayloadHash = computeScriptHash(addressToScript(multisigConfig.fullPayload)) describe('Test getCompensationPeriod', () => { // first sign but no match address diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts index 4ba289b716..76ce82827b 100644 --- a/packages/neuron-ui/src/types/App/index.d.ts +++ b/packages/neuron-ui/src/types/App/index.d.ts @@ -41,6 +41,7 @@ declare namespace State { outPoint: CKBComponents.OutPoint type?: CKBComponents.Script data?: string + daoData?: string isChangeCell?: boolean } interface DetailedTransaction extends Transaction { @@ -176,6 +177,10 @@ declare namespace State { loadedTransaction: any pageNotice?: PageNotice showWaitForFullySynced: boolean + lockWindowInfo?: { + locked: boolean + encryptedPassword?: string + } } interface NetworkProperty { diff --git a/packages/neuron-ui/src/types/Controller/index.d.ts b/packages/neuron-ui/src/types/Controller/index.d.ts index c308f2b88b..3f3d1a1940 100644 --- a/packages/neuron-ui/src/types/Controller/index.d.ts +++ b/packages/neuron-ui/src/types/Controller/index.d.ts @@ -70,6 +70,7 @@ declare namespace Controller { password?: string description?: string amendHash?: string + skipLastInputs?: boolean multisigConfig?: { id: number walletId: string diff --git a/packages/neuron-ui/src/types/Subject/index.d.ts b/packages/neuron-ui/src/types/Subject/index.d.ts index fcba2123dd..aaf34f6a82 100644 --- a/packages/neuron-ui/src/types/Subject/index.d.ts +++ b/packages/neuron-ui/src/types/Subject/index.d.ts @@ -16,6 +16,7 @@ declare namespace Command { | 'migrate-acp' | 'sign-verify' | 'multisig-address' + | 'lock-window' type Payload = string | null } diff --git a/packages/neuron-ui/src/types/global/index.d.ts b/packages/neuron-ui/src/types/global/index.d.ts index e4bc9f3091..168a43dc32 100644 --- a/packages/neuron-ui/src/types/global/index.d.ts +++ b/packages/neuron-ui/src/types/global/index.d.ts @@ -24,6 +24,11 @@ declare module '*.png' { export default value } +declare module '*.mp4' { + const value: string + export default value +} + declare module '*.scss' declare namespace Fixture { diff --git a/packages/neuron-ui/src/utils/calculateUsedCapacity.ts b/packages/neuron-ui/src/utils/calculateUsedCapacity.ts index ba91cf77f6..2832513763 100644 --- a/packages/neuron-ui/src/utils/calculateUsedCapacity.ts +++ b/packages/neuron-ui/src/utils/calculateUsedCapacity.ts @@ -1,3 +1,5 @@ +import { type CKBComponents } from '@ckb-lumos/rpc/lib/types/api' + const CODE_HASH_LENGTH = 32 const HASH_TYPE_LENGTH = 1 const CAPACITY_LENGTH = 8 diff --git a/packages/neuron-ui/src/utils/const.ts b/packages/neuron-ui/src/utils/const.ts index 7442b63012..2dda7ff4d3 100644 --- a/packages/neuron-ui/src/utils/const.ts +++ b/packages/neuron-ui/src/utils/const.ts @@ -83,3 +83,7 @@ export const ADDRESS_MIN_LENGTH = 86 export const ADDRESS_HEAD_TAIL_LENGTH = 34 export const PlaceHolderArgs = `0x${'00'.repeat(21)}` + +export const DAO_DATA = '0x0000000000000000' + +export const FEE_RATIO = 1000 diff --git a/packages/neuron-ui/src/utils/formatters.ts b/packages/neuron-ui/src/utils/formatters.ts index ea630e6fad..55273b9a7f 100644 --- a/packages/neuron-ui/src/utils/formatters.ts +++ b/packages/neuron-ui/src/utils/formatters.ts @@ -1,5 +1,6 @@ import { molecule } from '@ckb-lumos/codec' import { blockchain } from '@ckb-lumos/base' +import { formatUnit, ckbDecimals } from '@ckb-lumos/bi' import { TFunction } from 'i18next' import { FailureFromController } from 'services/remote/remoteApiWrapper' import { CapacityUnit } from './enums' @@ -103,40 +104,16 @@ export const CKBToShannonFormatter = (amount: string = '0', unit: CapacityUnit = } } -export const shannonToCKBFormatter = (shannon: string, showPositiveSign?: boolean, delimiter: string = ',') => { +export const shannonToCKBFormatter = (shannon: string, showPositiveSign?: boolean, showCommaSeparator = true) => { if (Number.isNaN(+shannon)) { - console.warn(`Shannon is not a valid number`) + console.warn(`Invalid shannon value: ${shannon}`) return shannon } - if (shannon === null) { - return '0' - } - let sign = '' - if (shannon.startsWith('-')) { - sign = '-' - } else if (showPositiveSign) { - sign = '+' - } - const unsignedShannon = shannon.replace(/^-?0*/, '') - let unsignedCKB = '' - if (unsignedShannon.length <= 8) { - unsignedCKB = `0.${unsignedShannon.padStart(8, '0')}`.replace(/\.?0+$/, '') - } else { - const decimal = `.${unsignedShannon.slice(-8)}`.replace(/\.?0+$/, '') - const int = unsignedShannon.slice(0, -8).replace(/\^0+/, '') - unsignedCKB = `${( - int - .split('') - .reverse() - .join('') - .match(/\d{1,3}/g) || ['0'] - ) - .join(delimiter) - .split('') - .reverse() - .join('')}${decimal}` - } - return +unsignedCKB === 0 ? '0' : `${sign}${unsignedCKB}` + return new Intl.NumberFormat('en-US', { + useGrouping: showCommaSeparator, + signDisplay: showPositiveSign && +shannon > 0 ? 'always' : 'auto', + maximumFractionDigits: ckbDecimals, + }).format(formatUnit(BigInt(shannon ?? '0'), 'ckb') as any) } export const localNumberFormatter = (num: string | number | bigint = 0) => { @@ -236,45 +213,19 @@ export const sudtValueToAmount = ( value: string | null = '0', decimal: string = '0', showPositiveSign = false, - separator = ',' + showCommaSeparator = true ) => { - if (value === null) { - return showPositiveSign ? '+0' : '0' - } - if (Number.isNaN(+value)) { - console.warn(`sUDT value is not a valid number`) - return showPositiveSign ? '+0' : '0' + if (Number.isNaN(Number(value))) { + console.warn(`Invalid sudt value: ${value}`) } - let sign = '' - if (value.startsWith('-')) { - sign = '-' - } else if (showPositiveSign) { - sign = '+' - } - const unsignedValue = value.replace(/^-?0*/, '') - const dec = +decimal - if (dec === 0) { - return +unsignedValue ? `${sign}${unsignedValue}` : '0' - } - let unsignedSUDTValue = '' - if (unsignedValue.length <= dec) { - unsignedSUDTValue = `0.${unsignedValue.padStart(dec, '0')}`.replace(/\.?0+$/, '') - } else { - const decimalFraction = `.${unsignedValue.slice(-dec)}`.replace(/\.?0+$/, '') - const int = unsignedValue.slice(0, -dec).replace(/\^0+/, '') - unsignedSUDTValue = `${( - int - .split('') - .reverse() - .join('') - .match(/\d{1,3}/g) || ['0'] - ) - .join(separator) - .split('') - .reverse() - .join('')}${decimalFraction}` - } - return `${sign}${+unsignedSUDTValue === 0 ? '0' : unsignedSUDTValue}` + const val = value === null || Number.isNaN(+value) ? '0' : value + const [int, dec = ''] = formatUnit(val, +decimal).split('.') + const fmt = new Intl.NumberFormat('en-US', { + useGrouping: showCommaSeparator, + signDisplay: showPositiveSign ? 'always' : 'auto', + }) + // use any type to avoid TS errors since string is not listed in the args IntlFormatter.prototype.format definition but it works + return `${fmt.format(int as any)}${dec ? `.${dec}` : ''}` } export const sUDTAmountFormatter = (amount: string) => { diff --git a/packages/neuron-ui/src/utils/getLockSupportShortAddress.ts b/packages/neuron-ui/src/utils/getLockSupportShortAddress.ts index 3954d1a23c..0247eb8364 100644 --- a/packages/neuron-ui/src/utils/getLockSupportShortAddress.ts +++ b/packages/neuron-ui/src/utils/getLockSupportShortAddress.ts @@ -1,3 +1,4 @@ +import { type CKBComponents } from '@ckb-lumos/rpc/lib/types/api' import { AnyoneCanPayLockInfoOnAggron, AnyoneCanPayLockInfoOnLina, DefaultLockInfo, MultiSigLockInfo } from './enums' const getLockSupportShortAddress = (lock: CKBComponents.Script) => { diff --git a/packages/neuron-ui/src/utils/getMultisigSignStatus.ts b/packages/neuron-ui/src/utils/getMultisigSignStatus.ts index ebba2dc096..1f351ba59a 100644 --- a/packages/neuron-ui/src/utils/getMultisigSignStatus.ts +++ b/packages/neuron-ui/src/utils/getMultisigSignStatus.ts @@ -1,4 +1,5 @@ -import { addressToScript, scriptToHash } from '@nervosnetwork/ckb-sdk-utils' +import { addressToScript } from 'utils' +import { computeScriptHash } from '@ckb-lumos/base/lib/utils' import { MultisigConfig } from 'services/remote' export const getMultisigSignStatus = ({ @@ -10,7 +11,7 @@ export const getMultisigSignStatus = ({ signatures?: State.Signatures addresses: State.Address[] }) => { - const multisigLockHash = scriptToHash(addressToScript(multisigConfig.fullPayload)) + const multisigLockHash = computeScriptHash(addressToScript(multisigConfig.fullPayload)) const multisigBlake160s = multisigConfig.addresses.map(v => addressToScript(v).args) const addressBlake160s = addresses.map(v => addressToScript(v.address).args) const notSpecifiedCount = multisigConfig.m - multisigConfig.r diff --git a/packages/neuron-ui/src/utils/getSUDTAmount.ts b/packages/neuron-ui/src/utils/getSUDTAmount.ts index d8c66a1ce6..ac151fdc1d 100644 --- a/packages/neuron-ui/src/utils/getSUDTAmount.ts +++ b/packages/neuron-ui/src/utils/getSUDTAmount.ts @@ -11,7 +11,7 @@ export const getSUDTAmount = ({ let amountToCopy = amount if (tokenInfo) { amount = `${sudtValueToAmount(amount, tokenInfo.decimal)} ${tokenInfo.symbol}` - amountToCopy = sudtValueToAmount(amountToCopy, tokenInfo.decimal, false, '') + amountToCopy = sudtValueToAmount(amountToCopy, tokenInfo.decimal, false, false) } return { amount, diff --git a/packages/neuron-ui/src/utils/getSyncLeftTime.ts b/packages/neuron-ui/src/utils/getSyncLeftTime.ts index 9b45a9f274..812c3f2ca4 100644 --- a/packages/neuron-ui/src/utils/getSyncLeftTime.ts +++ b/packages/neuron-ui/src/utils/getSyncLeftTime.ts @@ -1,6 +1,6 @@ -const MILLISECS_PER_SEC = 1_000 -const MILLISECS_PER_MIN = 60_000 -const MILLISECS_PER_HOUR = 3600_000 +export const MILLISECS_PER_SEC = 1_000 +export const MILLISECS_PER_MIN = 60_000 +export const MILLISECS_PER_HOUR = 3600_000 export const getSyncLeftTime = (estimate: number | undefined) => { let leftTime = '-' diff --git a/packages/neuron-ui/src/utils/hooks/useGetCountDownAndFeeRateStats.ts b/packages/neuron-ui/src/utils/hooks/useGetCountDownAndFeeRateStats.ts index 9caf6cf076..1171ceccdf 100644 --- a/packages/neuron-ui/src/utils/hooks/useGetCountDownAndFeeRateStats.ts +++ b/packages/neuron-ui/src/utils/hooks/useGetCountDownAndFeeRateStats.ts @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback } from 'react' -import { getFeeRateStats } from 'services/chain' +import { getFeeRateStatistics } from 'services/chain' import { MEDIUM_FEE_RATE, METHOD_NOT_FOUND } from 'utils/const' type CountdownOptions = { @@ -16,7 +16,7 @@ const useGetCountDownAndFeeRateStats = ({ seconds = 30, interval = 1000 }: Count }>({ suggestFeeRate: MEDIUM_FEE_RATE }) const handleGetFeeRateStatis = useCallback(() => { - getFeeRateStats() + getFeeRateStatistics() .then(res => { const { mean, median } = res ?? {} const suggested = mean && median ? Math.max(1000, Number(mean), Number(median)) : MEDIUM_FEE_RATE diff --git a/packages/neuron-ui/src/utils/index.ts b/packages/neuron-ui/src/utils/index.ts index 4fe2f818d6..0b2ff40d4e 100644 --- a/packages/neuron-ui/src/utils/index.ts +++ b/packages/neuron-ui/src/utils/index.ts @@ -26,8 +26,10 @@ export * from './baseActions' export * from './getSUDTAmount' export * from './multisig' export * from './getNetworkLabel' +export * from './getMultisigSignStatus' export * from './calculateUsedCapacity' export * from './outPointTransform' +export * from './scriptAndAddress' export * from './wakeScreen' export { CONSTANTS } diff --git a/packages/neuron-ui/src/utils/is.ts b/packages/neuron-ui/src/utils/is.ts index f20ca58fad..5a82900882 100644 --- a/packages/neuron-ui/src/utils/is.ts +++ b/packages/neuron-ui/src/utils/is.ts @@ -1,4 +1,4 @@ -import { addressToScript } from '@nervosnetwork/ckb-sdk-utils' +import { addressToScript } from 'utils' import { ControllerResponse, SuccessFromController } from 'services/remote/remoteApiWrapper' import { ResponseCode, diff --git a/packages/neuron-ui/src/utils/multisig.ts b/packages/neuron-ui/src/utils/multisig.ts index e5f3f89ae7..a59c6998c6 100644 --- a/packages/neuron-ui/src/utils/multisig.ts +++ b/packages/neuron-ui/src/utils/multisig.ts @@ -1,4 +1,5 @@ -import { scriptToAddress, blake2b, PERSONAL, hexToBytes } from '@nervosnetwork/ckb-sdk-utils' +import { ckbHash } from '@ckb-lumos/base/lib/utils' +import { scriptToAddress } from 'utils' import { MultiSigLockInfo } from './enums' import { MAX_M_N_NUMBER } from './const' @@ -18,11 +19,10 @@ function multisigSerialize(blake160s: string[], r: number = 0, m: number = 1, n: return `0x${MULTI_DEFAULT_S}${hexR}${hexM}${hexN}${blake160s.reduce((pre, cur) => pre + cur.slice(2), '')}` } +const MULTISIGN_HASH_LENGTH = 42 + function multisigHash(blake160s: string[], r: number = 0, m: number = 1, n: number = 1): string { - const serializeResult = multisigSerialize(blake160s, r, m, n) - const blake2bHash = blake2b(32, null, null, PERSONAL) - blake2bHash.update(hexToBytes(serializeResult)) - return `0x${blake2bHash.digest('hex')}`.slice(0, 42) + return ckbHash(multisigSerialize(blake160s, r, m, n)).slice(0, MULTISIGN_HASH_LENGTH) } export function getMultisigAddress(blake160s: string[], r: number, m: number, n: number, isMainnet: boolean) { @@ -32,7 +32,7 @@ export function getMultisigAddress(blake160s: string[], r: number, m: number, n: codeHash: MultiSigLockInfo.CodeHash, hashType: MultiSigLockInfo.HashType, }, - isMainnet + { isMainnet } ) } diff --git a/packages/neuron-ui/src/utils/outPointTransform.ts b/packages/neuron-ui/src/utils/outPointTransform.ts index 14d745ecff..e659458bcf 100644 --- a/packages/neuron-ui/src/utils/outPointTransform.ts +++ b/packages/neuron-ui/src/utils/outPointTransform.ts @@ -1,3 +1,5 @@ +import { type CKBComponents } from '@ckb-lumos/rpc/lib/types/api' + export const outPointToStr = (value: CKBComponents.OutPoint): string => { return `${value.txHash}_${value.index}` } diff --git a/packages/neuron-ui/src/utils/parsers.ts b/packages/neuron-ui/src/utils/parsers.ts index 28663dd69c..10dfafaba8 100644 --- a/packages/neuron-ui/src/utils/parsers.ts +++ b/packages/neuron-ui/src/utils/parsers.ts @@ -1,4 +1,5 @@ -import { toUint64Le, parseEpoch } from 'services/chain' +import { since } from '@ckb-lumos/base' +import { number, bytes } from '@ckb-lumos/codec' import { MILLISECONDS, PAGE_SIZE } from './const' export const listParams = (search: string) => { @@ -17,7 +18,7 @@ export const listParams = (search: string) => { export const epochParser = (epoch: string) => { const e = epoch.startsWith('0x') ? epoch : `0x${BigInt(epoch).toString(16)}` - const parsed = parseEpoch(e) + const parsed = since.parseEpoch(e) const res = { length: BigInt(parsed.length), @@ -44,7 +45,7 @@ export const toUint128Le = (hexString: string) => { s = s.slice(0, 34) } - return `${toUint64Le(`0x${s.substr(18, 16)}`)}${toUint64Le(s.substr(0, 18)).slice(2)}` + return bytes.hexify(number.Uint128LE.pack(s)) } export const getLockTimestamp = ({ @@ -56,7 +57,7 @@ export const getLockTimestamp = ({ epoch: string bestKnownBlockTimestamp: number }) => { - const targetEpochInfo = epochParser(toUint64Le(`0x${lockArgs.slice(-16)}`)) + const targetEpochInfo = epochParser(bytes.hexify(number.Uint64LE.pack(`0x${lockArgs.slice(-16)}`))) const currentEpochInfo = epochParser(epoch) const targetEpochFraction = Number(targetEpochInfo.length) > 0 ? Number(targetEpochInfo.index) / Number(targetEpochInfo.length) : 1 diff --git a/packages/neuron-ui/src/utils/scriptAndAddress.ts b/packages/neuron-ui/src/utils/scriptAndAddress.ts new file mode 100644 index 0000000000..acea450b72 --- /dev/null +++ b/packages/neuron-ui/src/utils/scriptAndAddress.ts @@ -0,0 +1,35 @@ +import { type Script } from '@ckb-lumos/base' +import { predefined } from '@ckb-lumos/config-manager' +import { encodeToAddress, parseAddress, generateAddress } from '@ckb-lumos/helpers' + +const { LINA: MAINNET, AGGRON4: TESTNET } = predefined + +const CONFIGS = { + [MAINNET.PREFIX]: MAINNET, + [TESTNET.PREFIX]: TESTNET, +} + +export const scriptToAddress = ( + script: Script, + { isMainnet = true, deprecated = false }: { isMainnet?: boolean; deprecated?: boolean } +) => { + const config = { config: isMainnet ? MAINNET : TESTNET } + return deprecated ? generateAddress(script, config) : encodeToAddress(script, config) +} + +export const addressToScript = (address: string, { isMainnet = true }: { isMainnet?: boolean } = {}) => { + const prefix = address.slice(0, 3) + const config = CONFIGS[prefix] ?? (isMainnet ? MAINNET : TESTNET) + return parseAddress(address, { config }) +} + +export const addressToAddress = (address: string, { deprecated = false }: { deprecated?: boolean } = {}) => { + try { + return scriptToAddress(addressToScript(address), { + isMainnet: address.startsWith(MAINNET.PREFIX), + deprecated, + }) + } catch { + return '' + } +} diff --git a/packages/neuron-ui/src/utils/validators/address.ts b/packages/neuron-ui/src/utils/validators/address.ts index cdebaeb3e3..d6f20b917e 100644 --- a/packages/neuron-ui/src/utils/validators/address.ts +++ b/packages/neuron-ui/src/utils/validators/address.ts @@ -1,4 +1,4 @@ -import { ckbCore } from 'services/chain' +import { addressToScript } from 'utils' import { FieldInvalidException, MainnetAddressRequiredException, @@ -6,15 +6,6 @@ import { AddressEmptyException, AddressNotMatchException, } from 'exceptions' -import { - NEW_LONG_ADDR_PREFIX, - LONG_DATA_PREFIX, - LONG_TYPE_PREFIX, - SHORT_ADDR_LENGTH, - SHORT_ADDR_DEFAULT_LOCK_PREFIX, - SHORT_ADDR_MULTISIGN_LOCK_PREFIX, - SHORT_ADDR_SUDT_LOCK_PREFIX, -} from 'utils/const' import { DefaultLockInfo, MultiSigLockInfo, @@ -44,32 +35,11 @@ export const validateAddress = (address: string, isMainnet: boolean): boolean => throw new TestnetAddressRequiredException() } - let parsed = '' - try { - parsed = ckbCore.utils.parseAddress(address, 'hex') + return Boolean(addressToScript(address, { isMainnet })) } catch (err) { throw new FieldInvalidException(FIELD_NAME, address) } - - if ( - parsed.startsWith(LONG_DATA_PREFIX) || - parsed.startsWith(LONG_TYPE_PREFIX) || - parsed.startsWith(NEW_LONG_ADDR_PREFIX) - ) { - return true - } - - if ( - (!parsed.startsWith(SHORT_ADDR_DEFAULT_LOCK_PREFIX) && - !parsed.startsWith(SHORT_ADDR_MULTISIGN_LOCK_PREFIX) && - !parsed.startsWith(SHORT_ADDR_SUDT_LOCK_PREFIX)) || - address.length !== SHORT_ADDR_LENGTH - ) { - throw new FieldInvalidException(FIELD_NAME, address) - } - - return true } const addressTagMap = { @@ -82,7 +52,7 @@ const addressTagMap = { export function validateSpecificAddress(address: string, isMainnet: boolean, tagName: keyof typeof addressTagMap) { validateAddress(address, isMainnet) - const script = ckbCore.utils.addressToScript(address) + const script = addressToScript(address, { isMainnet }) const lockInfo = addressTagMap[tagName][isMainnet ? 0 : 1] // first is lock on Lina if (script.codeHash !== lockInfo.CodeHash || script.hashType !== lockInfo.HashType) { throw new AddressNotMatchException(tagName) diff --git a/packages/neuron-ui/src/utils/validators/assetAccountAddress.ts b/packages/neuron-ui/src/utils/validators/assetAccountAddress.ts index 6478e36b14..8f8e91da72 100644 --- a/packages/neuron-ui/src/utils/validators/assetAccountAddress.ts +++ b/packages/neuron-ui/src/utils/validators/assetAccountAddress.ts @@ -1,4 +1,4 @@ -import { ckbCore } from 'services/chain' +import { addressToScript } from 'utils' import { FieldRequiredException, FieldInvalidException, AddressDeprecatedException } from 'exceptions' import { DEPRECATED_CODE_HASH } from 'utils/const' import { isSecp256k1Address } from 'utils/is' @@ -24,7 +24,7 @@ export const validateAssetAccountAddress = ({ return true } - const lockScript = ckbCore.utils.addressToScript(address) + const lockScript = addressToScript(address, { isMainnet }) if ([DEPRECATED_CODE_HASH.AcpOnAggron, DEPRECATED_CODE_HASH.AcpOnLina].includes(lockScript.codeHash)) { throw new AddressDeprecatedException() diff --git a/packages/neuron-ui/src/utils/validators/capacity.ts b/packages/neuron-ui/src/utils/validators/capacity.ts index 2f3a53f0c6..d91c15cee1 100644 --- a/packages/neuron-ui/src/utils/validators/capacity.ts +++ b/packages/neuron-ui/src/utils/validators/capacity.ts @@ -1,12 +1,12 @@ import { CKBToShannonFormatter } from 'utils/formatters' import { CapacityTooSmallException } from 'exceptions' import { bytes as byteUtils } from '@ckb-lumos/codec' -import { ckbCore } from 'services/chain' +import { addressToScript } from 'utils' export const validateCapacity = (item: State.Output) => { const { amount, unit, address } = item const capacity = CKBToShannonFormatter(amount, unit) - const script = ckbCore.utils.addressToScript(address as string) + const script = addressToScript(address as string) const size = 1 + byteUtils.concat(script.args, script.codeHash).byteLength const outputSize = 8 + byteUtils.bytify('0x').byteLength + size diff --git a/packages/neuron-ui/src/widgets/AlertDialog/alertDialog.module.scss b/packages/neuron-ui/src/widgets/AlertDialog/alertDialog.module.scss index b89b270757..2ed26542cf 100644 --- a/packages/neuron-ui/src/widgets/AlertDialog/alertDialog.module.scss +++ b/packages/neuron-ui/src/widgets/AlertDialog/alertDialog.module.scss @@ -15,6 +15,9 @@ font-weight: 500; margin: 14px 0 16px 0; color: var(--main-text-color); + white-space: pre-wrap; + word-wrap: break-word; + overflow-wrap: break-word; } .message { diff --git a/packages/neuron-ui/src/widgets/Icons/DepositTimeSort.svg b/packages/neuron-ui/src/widgets/Icons/DepositTimeSort.svg new file mode 100644 index 0000000000..a6591db275 --- /dev/null +++ b/packages/neuron-ui/src/widgets/Icons/DepositTimeSort.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/neuron-ui/src/widgets/Icons/Locked.png b/packages/neuron-ui/src/widgets/Icons/Locked.png new file mode 100644 index 0000000000..ff6636f8cc Binary files /dev/null and b/packages/neuron-ui/src/widgets/Icons/Locked.png differ diff --git a/packages/neuron-ui/src/widgets/Icons/dark-unlock.mp4 b/packages/neuron-ui/src/widgets/Icons/dark-unlock.mp4 new file mode 100644 index 0000000000..191a383af0 Binary files /dev/null and b/packages/neuron-ui/src/widgets/Icons/dark-unlock.mp4 differ diff --git a/packages/neuron-ui/src/widgets/Icons/icon.tsx b/packages/neuron-ui/src/widgets/Icons/icon.tsx index ecdd2b8c2e..806510ad82 100644 --- a/packages/neuron-ui/src/widgets/Icons/icon.tsx +++ b/packages/neuron-ui/src/widgets/Icons/icon.tsx @@ -56,6 +56,7 @@ import { ReactComponent as DetectSvg } from './Detect.svg' import { ReactComponent as DeleteSvg } from './Delete.svg' import { ReactComponent as ImportKeystoreSvg } from './SoftWalletImportKeystore.svg' import { ReactComponent as ImportHardwareSvg } from './HardWalletImport.svg' +import { ReactComponent as DepositTimeSortSvg } from './DepositTimeSort.svg' import styles from './icon.module.scss' @@ -128,3 +129,4 @@ export const Detect = WrapSvg(DetectSvg) export const Delete = WrapSvg(DeleteSvg) export const ImportKeystore = WrapSvg(ImportKeystoreSvg) export const ImportHardware = WrapSvg(ImportHardwareSvg) +export const DepositTimeSort = WrapSvg(DepositTimeSortSvg) diff --git a/packages/neuron-ui/src/widgets/Icons/unlock.mp4 b/packages/neuron-ui/src/widgets/Icons/unlock.mp4 new file mode 100644 index 0000000000..c7a054b2c7 Binary files /dev/null and b/packages/neuron-ui/src/widgets/Icons/unlock.mp4 differ diff --git a/packages/neuron-ui/src/widgets/SplitPasswordInput/index.tsx b/packages/neuron-ui/src/widgets/SplitPasswordInput/index.tsx new file mode 100644 index 0000000000..71a1e56c76 --- /dev/null +++ b/packages/neuron-ui/src/widgets/SplitPasswordInput/index.tsx @@ -0,0 +1,65 @@ +import React, { useCallback, useEffect, useRef } from 'react' +import styles from './splitPasswordInput.module.scss' + +const SplitPasswordInput = ({ + values, + inputCount, + onChange, + disabled, +}: { + values: (string | undefined)[] + inputCount?: number + onChange: (value: string, idx: number) => void + disabled?: boolean +}) => { + const ref = useRef(null) + const onChangeInput = useCallback>( + e => { + const { dataset, value } = e.currentTarget + if (value.length > 1 || !dataset.idx) return + onChange(value, +dataset.idx) + if (ref.current && value.length > 0) { + const nextInput = ref.current.querySelector(`input:nth-child(${+dataset.idx + 2})`) as HTMLInputElement + nextInput?.focus() + } + }, + [ref, values] + ) + const onKeyDown = useCallback>( + e => { + const { dataset, value } = e.currentTarget + if (e.key === 'Backspace' && dataset.idx && +dataset.idx > 0 && ref.current && value.length === 0) { + const lastInput = ref.current.querySelector(`input:nth-child(${+dataset.idx})`) as HTMLInputElement + lastInput?.focus() + onChange('', +dataset.idx - 1) + } + }, + [ref, values] + ) + useEffect(() => { + if (ref.current && values.join('').length === 0) { + const firstInput = ref.current.querySelector(`input:nth-child(1)`) as HTMLInputElement + firstInput?.focus() + } + }, [values]) + return ( +
+ {Array.from({ length: inputCount ?? values.length }).map((_, idx) => ( + + ))} +
+ ) +} + +SplitPasswordInput.displayName = 'SplitPasswordInput' +export default SplitPasswordInput diff --git a/packages/neuron-ui/src/widgets/SplitPasswordInput/splitPasswordInput.module.scss b/packages/neuron-ui/src/widgets/SplitPasswordInput/splitPasswordInput.module.scss new file mode 100644 index 0000000000..b2128a1281 --- /dev/null +++ b/packages/neuron-ui/src/widgets/SplitPasswordInput/splitPasswordInput.module.scss @@ -0,0 +1,19 @@ +.root { + display: flex; + align-items: center; + justify-content: center; + gap: 16px; + + & > input { + width: 40px; + height: 60px; + border-radius: 8px; + border: 1px solid var(--secondary-text-color); + text-align: center; + + &:focus { + border-color: var(--primary-color); + caret-color: var(--primary-color); + } + } +} diff --git a/packages/neuron-wallet/.env b/packages/neuron-wallet/.env index f32862920f..3c4d9296b8 100644 --- a/packages/neuron-wallet/.env +++ b/packages/neuron-wallet/.env @@ -117,5 +117,5 @@ DAO_CODE_HASH=0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e MULTISIG_CODE_HASH=0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8 # CKB NODE OPTIONS -CKB_NODE_ASSUME_VALID_TARGET='0x9443ad8da9172d484367bc5467988cba7a0c46028398309edfdda7d2d79be897' -CKB_NODE_DATA_SIZE=53 +CKB_NODE_ASSUME_VALID_TARGET='0x6dd077b407d019a0bce0cbad8c34e69a524ae4b2599b9feda2c7491f3559d32c' +CKB_NODE_DATA_SIZE=54 diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json index f59790491c..cad2f98bff 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.114.3", + "version": "0.116.0", "private": true, "author": { "name": "Nervos Core Dev", @@ -73,7 +73,7 @@ "sqlite3": "5.1.6", "subleveldown": "4.1.4", "tslib": "2.6.2", - "typeorm": "0.2.45", + "typeorm": "0.3.17", "undici": "5.28.4", "uuid": "8.3.2" }, @@ -93,11 +93,11 @@ "@types/sqlite3": "3.1.11", "@types/uuid": "8.3.4", "devtron": "1.4.0", - "electron": "28.1.0", + "electron": "30.0.0", "electron-builder": "24.9.1", "electron-devtools-installer": "3.2.0", "jest-when": "3.6.0", - "neuron-ui": "0.114.3", + "neuron-ui": "0.116.0", "typescript": "5.3.3" } } diff --git a/packages/neuron-wallet/src/block-sync-renderer/index.ts b/packages/neuron-wallet/src/block-sync-renderer/index.ts index 95576c84a7..cf690f014b 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/index.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/index.ts @@ -64,7 +64,7 @@ export const resetSyncTask = async (startTask = true) => { if (startTask) { await WalletService.getInstance().maintainAddressesIfNecessary() - await TransactionPersistor.checkTxLock() + await CommonUtils.retry(3, 5000, TransactionPersistor.checkTxLock) await CommonUtils.sleep(3000) await createBlockSyncTask() } diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/indexer-cache-service.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/indexer-cache-service.ts index 45f74efba8..b3401f6626 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/indexer-cache-service.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/indexer-cache-service.ts @@ -149,7 +149,7 @@ export default class IndexerCacheService { cacheBlockNumberEntity = (await getConnection() .getRepository(SyncInfoEntity) - .findOne({ name: SyncInfoEntity.getLastCachedKey(blake160) })) ?? + .findOneBy({ name: SyncInfoEntity.getLastCachedKey(blake160) })) ?? SyncInfoEntity.fromObject({ name: SyncInfoEntity.getLastCachedKey(blake160), value: '0x0', diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts index 1adccf187b..f87dddf99b 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts @@ -175,9 +175,11 @@ export default class LightSynchronizer extends Synchronizer { if (!this.addressMetas.length && !appendScripts.length) { return } + const existSyncArgses = await SyncProgressService.getExistingSyncArgses() const syncScripts = await this.lightRpc.getScripts() + const retainedSyncScripts = syncScripts.filter(v => existSyncArgses.has(v.script.args)) const existSyncscripts: Record = {} - syncScripts.forEach(v => { + retainedSyncScripts.forEach(v => { existSyncscripts[scriptToHash(v.script)] = v }) const currentWalletId = WalletService.getInstance().getCurrent()?.id @@ -205,7 +207,11 @@ export default class LightSynchronizer extends Synchronizer { const otherTypeSyncBlockNumber = await SyncProgressService.getOtherTypeSyncBlockNumber() const addScripts = [ ...allScripts - .filter(v => !existSyncscripts[scriptToHash(v.script)]) + .filter( + v => + !existSyncscripts[scriptToHash(v.script)] || + +existSyncscripts[scriptToHash(v.script)].blockNumber < +(walletStartBlockMap[v.walletId] ?? 0) + ) .map(v => { const blockNumber = Math.max( parseInt(walletStartBlockMap[v.walletId] ?? '0x0'), @@ -228,7 +234,7 @@ export default class LightSynchronizer extends Synchronizer { ...allScripts.map(v => scriptToHash(v.script)), ...appendScripts.map(v => scriptToHash(v.script)), ]) - const deleteScript = syncScripts.filter(v => !allScriptHashes.has(scriptToHash(v.script))) + const deleteScript = retainedSyncScripts.filter(v => !allScriptHashes.has(scriptToHash(v.script))) await this.lightRpc.setScripts(deleteScript, 'delete') const walletIds = [...new Set(this.addressMetas.map(v => v.walletId))] await SyncProgressService.initSyncProgress(addScripts) @@ -262,10 +268,15 @@ export default class LightSynchronizer extends Synchronizer { previousTxHashes.add(previousTxHash) } }) + if (!previousTxHashes.size) return await this.lightRpc.createBatchRequest([...previousTxHashes].map(v => ['fetchTransaction' as keyof Base, v])).exec() } private async updateBlockStartNumber(blockNumber: number) { + if (this._needGenerateAddress || !this.pollingIndexer) { + logger.info('LightConnector:\twait for generating address') + return + } const scripts = await this.lightRpc.getScripts() await SyncProgressService.updateBlockNumber( scripts.map(v => v.script.args), diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts index a09055eb58..027bc14967 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts @@ -22,6 +22,7 @@ import LightSynchronizer from './light-synchronizer' import { generateRPC } from '../../utils/ckb-rpc' import { BUNDLED_LIGHT_CKB_URL } from '../../utils/const' import { NetworkType } from '../../models/network' +import WalletService from '../../services/wallets' export default class Queue { #lockHashes: string[] @@ -254,6 +255,9 @@ export default class Queue { .map(addr => addr.walletId) ) if (process.send) { + this.#indexerConnector!.needGenerateAddress = await WalletService.getInstance().checkNeedGenerateAddress([ + ...walletIds, + ]) process.send({ channel: 'check-and-save-wallet-address', message: [...walletIds] }) } else { throw new ShouldInChildProcess() diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/synchronizer.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/synchronizer.ts index 7b1fd0b4e8..b8d1a789a2 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/synchronizer.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/synchronizer.ts @@ -58,6 +58,7 @@ export abstract class Synchronizer { protected addressesByWalletId: Map = new Map() protected pollingIndexer: boolean = false private indexerQueryQueue: QueueObject | undefined + protected _needGenerateAddress: boolean = false abstract connect(): Promise abstract processTxsInNextBlockNumber(): Promise @@ -96,6 +97,10 @@ export abstract class Synchronizer { this.pollingIndexer = false } + public set needGenerateAddress(v: boolean) { + this._needGenerateAddress = v + } + protected async processNextBlockNumber() { // the processNextBlockNumberQueue is a queue to ensure that ONLY one // block processing task runs at a time to avoid the data conflict while syncing diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/tx-address-finder.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/tx-address-finder.ts index f8d1a9d295..200aaeded3 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/tx-address-finder.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/tx-address-finder.ts @@ -90,7 +90,7 @@ export default class TxAddressFinder { let shouldSync = false for (const input of inputs) { const outPoint: OutPoint = input.previousOutput! - const output = await getConnection().getRepository(OutputEntity).findOne({ + const output = await getConnection().getRepository(OutputEntity).findOneBy({ outPointTxHash: outPoint.txHash, outPointIndex: outPoint.index, }) diff --git a/packages/neuron-wallet/src/controllers/api.ts b/packages/neuron-wallet/src/controllers/api.ts index bca1118731..fa209b0887 100644 --- a/packages/neuron-wallet/src/controllers/api.ts +++ b/packages/neuron-wallet/src/controllers/api.ts @@ -66,7 +66,7 @@ import { UpdateCellLocalInfo } from '../database/chain/entities/cell-local-info' import { CKBLightRunner } from '../services/light-runner' import { OutPoint } from '@ckb-lumos/base' -export type Command = 'export-xpubkey' | 'import-xpubkey' | 'delete-wallet' | 'backup-wallet' | 'migrate-acp' +export type Command = 'export-xpubkey' | 'import-xpubkey' | 'delete-wallet' | 'backup-wallet' // Handle channel messages from renderer process and user actions. export default class ApiController { #walletsController = new WalletsController() @@ -114,10 +114,6 @@ export default class ApiController { this.#walletsController.requestPassword(params, command) break } - case 'migrate-acp': { - this.#assetAccountController.showACPMigrationDialog(false) - break - } default: { logger.error(`API Controller:\treceive invalid command "${command}" with params "${params}"`) } @@ -306,6 +302,42 @@ export default class ApiController { } }) + handle('unlock-window', async (_, password: string) => { + if (!SettingsService.getInstance().verifyLockWindowPassword(password)) { + return { + status: ResponseCode.Fail, + } + } + SettingsService.getInstance().updateLockWindowInfo({ locked: false }) + return { + status: ResponseCode.Success, + result: SettingsService.getInstance().lockWindowInfo, + } + }) + + handle('update-lock-window-info', async (_, params: { locked?: boolean; password?: string }) => { + SettingsService.getInstance().updateLockWindowInfo(params) + return { + status: ResponseCode.Success, + result: SettingsService.getInstance().lockWindowInfo, + } + }) + + handle('get-lock-window-info', async () => { + return { + result: SettingsService.getInstance().lockWindowInfo, + status: ResponseCode.Success, + } + }) + + handle('verify-lock-window-password', async (_, password: string) => { + return { + status: SettingsService.getInstance().verifyLockWindowPassword(password) + ? ResponseCode.Success + : ResponseCode.Fail, + } + }) + // Wallets handle('get-all-wallets', async () => { @@ -341,12 +373,12 @@ export default class ApiController { startBlockNumber: string } ) => { - const res = this.#walletsController.update(params) const network = NetworksService.getInstance().getCurrent() if (network.type !== NetworkType.Light) { throw new Error('Only Light client can set start block number') } const lastSetStartBlockNumber = WalletsService.getInstance().getCurrent()?.toJSON().startBlockNumber + const res = this.#walletsController.update(params) if (lastSetStartBlockNumber && +params.startBlockNumber < +lastSetStartBlockNumber) { await CKBLightRunner.getInstance().clearNodeCache() } else { @@ -417,6 +449,7 @@ export default class ApiController { description?: string multisigConfig?: MultisigConfigModel amendHash?: string + skipLastInputs?: boolean } ) => { return this.#walletsController.sendTx({ @@ -746,11 +779,6 @@ export default class ApiController { return this.#assetAccountController.getAccount(params) }) - handle('check-migrate-acp', async () => { - const allowMultipleOpen = true - return this.#assetAccountController.showACPMigrationDialog(allowMultipleOpen) - }) - handle('migrate-acp', async (_, params: MigrateACPParams) => { return this.#assetAccountController.migrateAcp(params) }) @@ -841,10 +869,6 @@ export default class ApiController { return this.#offlineSignController.broadcastTransaction({ ...params, walletID: '' }) }) - handle('get-transaction-size', async (_, params) => { - return this.#transactionsController.getTransactionSize(params) - }) - handle('sign-and-export-transaction', async (_, params) => { return this.#offlineSignController.signAndExportTransaction({ ...params, diff --git a/packages/neuron-wallet/src/controllers/app/menu.ts b/packages/neuron-wallet/src/controllers/app/menu.ts index 24ae895113..1e1b728330 100644 --- a/packages/neuron-wallet/src/controllers/app/menu.ts +++ b/packages/neuron-wallet/src/controllers/app/menu.ts @@ -15,6 +15,7 @@ import NetworksService from '../../services/networks' import { clearCkbNodeCache } from '../../services/ckb-runner' import { CKBLightRunner } from '../../services/light-runner' import { NetworkType } from '../../models/network' +import SettingsService from '../../services/settings' enum URL { Settings = '/settings', @@ -112,6 +113,13 @@ const requestPassword = (walletID: string, actionType: 'delete-wallet' | 'backup } } +const lockWindow = () => { + const window = BrowserWindow.getFocusedWindow() + if (window) { + CommandSubject.next({ winID: window.id, type: 'lock-window', payload: null, dispatchToUI: true }) + } +} + const updateApplicationMenu = (mainWindow: BrowserWindow | null) => { const isMac = process.platform === 'darwin' const currentWindow = BrowserWindow.getFocusedWindow() @@ -389,6 +397,13 @@ const updateApplicationMenu = (mainWindow: BrowserWindow | null) => { label: t('application-menu.window.close'), role: 'close', }, + { + label: t('application-menu.window.lock'), + accelerator: 'CmdOrCtrl+L', + click: () => { + lockWindow() + }, + }, ], } @@ -523,10 +538,15 @@ const updateApplicationMenu = (mainWindow: BrowserWindow | null) => { ], } - const applicationMenuTemplate = env.isDevMode + let applicationMenuTemplate = env.isDevMode ? [walletMenuItem, editMenuItem, developMenuItem, toolsMenuItem, windowMenuItem, helpMenuItem] : [walletMenuItem, editMenuItem, toolsMenuItem, windowMenuItem, helpMenuItem] + if (SettingsService.getInstance().lockWindowInfo.locked) { + applicationMenuTemplate = env.isDevMode + ? [editMenuItem, developMenuItem, windowMenuItem, helpMenuItem] + : [editMenuItem, windowMenuItem, helpMenuItem] + } if (isMac) { applicationMenuTemplate.unshift(appMenuItem) } diff --git a/packages/neuron-wallet/src/controllers/app/subscribe.ts b/packages/neuron-wallet/src/controllers/app/subscribe.ts index 84f5d8e436..a71a18661d 100644 --- a/packages/neuron-wallet/src/controllers/app/subscribe.ts +++ b/packages/neuron-wallet/src/controllers/app/subscribe.ts @@ -47,9 +47,6 @@ export const subscribe = (dispatcher: AppResponder) => { estimation.estimate = cachedEstimation.estimate } dispatcher.sendMessage('sync-estimate-updated', estimation) - // dispatcher.sendMessage('synced-block-number-updated', params) - - dispatcher.runCommand('migrate-acp', '') }) CommandSubject.subscribe(params => { diff --git a/packages/neuron-wallet/src/controllers/asset-account.ts b/packages/neuron-wallet/src/controllers/asset-account.ts index 3c2300a0d7..1469996125 100644 --- a/packages/neuron-wallet/src/controllers/asset-account.ts +++ b/packages/neuron-wallet/src/controllers/asset-account.ts @@ -7,11 +7,8 @@ import { ResponseCode } from '../utils/const' import NetworksService from '../services/networks' import AssetAccountInfo from '../models/asset-account-info' import TransactionSender from '../services/transaction-sender' -import { BrowserWindow, dialog } from 'electron' +import { dialog } from 'electron' import { t } from 'i18next' -import WalletsService from '../services/wallets' -import CommandSubject from '../models/subjects/command' -import SyncApiController, { SyncStatus } from './sync-api' import { TransactionGenerator } from '../services/tx' import OutPoint from '../models/chain/out-point' import SudtTokenInfoService from '../services/sudt-token-info' @@ -68,8 +65,6 @@ export interface GenerateWithdrawChequeTxParams { } export default class AssetAccountController { - private displayedACPMigrationDialogByWalletIds: Set = new Set() - public async getAll(params: { walletID: string }): Promise> { @@ -221,82 +216,6 @@ export default class AssetAccountController { } } - public async showACPMigrationDialog( - allowMultipleOpen: boolean | undefined - ): Promise> { - const walletsService = WalletsService.getInstance() - const currentWallet = walletsService.getCurrent() - if (!currentWallet) { - return { - status: ResponseCode.Success, - } - } - const walletId = currentWallet.id - - if (!allowMultipleOpen && this.displayedACPMigrationDialogByWalletIds.has(walletId)) { - return { - status: ResponseCode.Success, - } - } - - const syncStatus = await SyncApiController.getInstance().getSyncStatus() - - if (syncStatus !== SyncStatus.SyncCompleted || BrowserWindow.getAllWindows().length !== 1) { - return { - status: ResponseCode.Success, - } - } - - const window = BrowserWindow.getFocusedWindow() - if (!window) { - return { - status: ResponseCode.Success, - } - } - - const tx = await TransactionGenerator.generateMigrateLegacyACPTx(walletId) - if (!tx) { - return { - status: ResponseCode.Success, - } - } - - this.displayedACPMigrationDialogByWalletIds.add(walletId) - - const I18N_PATH = `messageBox.acp-migration` - return dialog - .showMessageBox({ - type: 'info', - buttons: ['skip', 'migrate'].map(label => t(`${I18N_PATH}.buttons.${label}`)), - defaultId: 1, - title: t(`${I18N_PATH}.title`), - message: t(`${I18N_PATH}.message`), - detail: t(`${I18N_PATH}.detail`), - cancelId: 0, - noLink: true, - }) - .then(({ response }) => { - switch (response) { - case 1: { - CommandSubject.next({ - winID: window.id, - type: 'migrate-acp', - payload: walletId, - dispatchToUI: true, - }) - return true - } - case 0: - default: - return false - } - }) - .then(result => ({ - status: ResponseCode.Success, - result, - })) - } - public async generateCreateChequeTx(params: GenerateCreateChequeTxParams): Promise> { const tx = await AssetAccountService.generateCreateChequeTx( params.walletID, diff --git a/packages/neuron-wallet/src/controllers/export-debug.ts b/packages/neuron-wallet/src/controllers/export-debug.ts index 38c9af3d38..c393984bd3 100644 --- a/packages/neuron-wallet/src/controllers/export-debug.ts +++ b/packages/neuron-wallet/src/controllers/export-debug.ts @@ -13,6 +13,7 @@ import SettingsService from '../services/settings' import { generateRPC } from '../utils/ckb-rpc' import { CKBLightRunner } from '../services/light-runner' import { LIGHT_CLIENT_MAINNET, LIGHT_CLIENT_TESTNET } from '../utils/const' +import WalletsService from '../services/wallets' export default class ExportDebugController { #I18N_PATH = 'export-debug-info' @@ -78,6 +79,7 @@ export default class ExportDebugController { ]) const { platform, arch } = process const release = os.release() + const wallets = WalletsService.getInstance().getAll() const status = { neuron: { version: neuronVersion, @@ -95,30 +97,30 @@ export default class ExportDebugController { release, vcredist, }, + wallets: wallets.map((v, idx) => ({ + idx, + startBlockNumber: v.startBlockNumber, + })), } this.archive.append(JSON.stringify(status), { name: 'status.json', }) } - private addBundledCKBLog = () => { - const name = 'bundled-ckb.log' - const SIZE_TO_READ = 32_000 - - return new Promise((resolve, reject) => { - const logPath = path.resolve(SettingsService.getInstance().getNodeDataPath(), 'data', 'logs', 'run.log') - if (!fs.existsSync(logPath)) { + private readLastSizeOfFile(filepath: string, size = 32_000) { + return new Promise((resolve, reject) => { + if (!fs.existsSync(filepath)) { return reject(new Error('File not found')) } - const fileStats = fs.statSync(logPath) - const position = fileStats.size - SIZE_TO_READ + const fileStats = fs.statSync(filepath) + const position = fileStats.size - size - fs.open(logPath, 'r', (openErr, fd) => { + fs.open(filepath, 'r', (openErr, fd) => { if (openErr) { return reject(openErr) } - fs.read(fd, Buffer.alloc(SIZE_TO_READ), 0, SIZE_TO_READ, position, (readErr, _, buffer) => { + fs.read(fd, Buffer.alloc(size), 0, size, position, (readErr, _, buffer) => { fs.close(fd, closeErr => { const err = closeErr || readErr if (err) { @@ -129,20 +131,30 @@ export default class ExportDebugController { }) }) }) - .then((log: string) => { - this.archive.append(log, { name }) - }) - .catch(err => { - this.archive.append(err.message, { name }) - }) + } + + private addBundledCKBLog = async () => { + const name = 'bundled-ckb.log' + try { + const logPath = path.resolve(SettingsService.getInstance().getNodeDataPath(), 'data', 'logs', 'run.log') + const log = await this.readLastSizeOfFile(logPath) + this.archive.append(log, { name }) + } catch (error) { + this.archive.append(error.message, { name }) + } } private async addHdPublicKeyInfoCsv() { try { const addressMetas = await AddressService.getAddressesByAllWallets() - let csv = 'walletId,addressType,addressIndex,publicKeyInBlake160\n' + const wallets = WalletsService.getInstance().getAll() + const idToIdx = new Map() + wallets.forEach((v, idx) => idToIdx.set(v.id, idx)) + let csv = 'index,addressType,addressIndex,publicKeyInBlake160\n' for (const addressMeta of addressMetas) { - const row = `${addressMeta.walletId},${addressMeta.addressType},${addressMeta.addressIndex},${addressMeta.blake160}\n` + const row = `${idToIdx.get(addressMeta.walletId) ?? addressMeta.walletId},${addressMeta.addressType},${ + addressMeta.addressIndex + },${addressMeta.blake160}\n` csv += row } const csvFileName = 'hd_public_key_info.csv' @@ -162,14 +174,16 @@ export default class ExportDebugController { }) } - private addBundledCKBLightClientLog() { + private async addBundledCKBLightClientLog() { const mainnetLogPath = CKBLightRunner.getInstance().getLogPath(LIGHT_CLIENT_MAINNET) - const testnetLogPath = CKBLightRunner.getInstance().getLogPath(LIGHT_CLIENT_TESTNET) if (fs.existsSync(mainnetLogPath)) { - this.archive.file(mainnetLogPath, { name: 'bundled-ckb-lignt-client-mainnet.log' }) + const mainnetLog = await this.readLastSizeOfFile(mainnetLogPath) + this.archive.append(mainnetLog, { name: 'bundled-ckb-lignt-client-mainnet.log' }) } + const testnetLogPath = CKBLightRunner.getInstance().getLogPath(LIGHT_CLIENT_TESTNET) if (fs.existsSync(testnetLogPath)) { - this.archive.file(testnetLogPath, { name: 'bundled-ckb-lignt-client-testnet.log' }) + const testnetLog = await this.readLastSizeOfFile(testnetLogPath) + this.archive.append(testnetLog, { name: 'bundled-ckb-lignt-client-testnet.log' }) } } } diff --git a/packages/neuron-wallet/src/controllers/hardware.ts b/packages/neuron-wallet/src/controllers/hardware.ts index 41cd9f0d8a..182381ba61 100644 --- a/packages/neuron-wallet/src/controllers/hardware.ts +++ b/packages/neuron-wallet/src/controllers/hardware.ts @@ -2,7 +2,7 @@ import { DeviceInfo, ExtendedPublicKey, PublicKey } from '../services/hardware/c import { ResponseCode } from '../utils/const' import HardwareWalletService from '../services/hardware' import { connectDeviceFailed } from '../exceptions' -import { AccountExtendedPublicKey } from '../models/keys/key' +import { AccountExtendedPublicKey } from '@ckb-lumos/hd' export default class HardwareController { public async connectDevice(deviceInfo: DeviceInfo): Promise> { diff --git a/packages/neuron-wallet/src/controllers/transactions.ts b/packages/neuron-wallet/src/controllers/transactions.ts index 193d7c7565..eb3a4fabfc 100644 --- a/packages/neuron-wallet/src/controllers/transactions.ts +++ b/packages/neuron-wallet/src/controllers/transactions.ts @@ -10,7 +10,6 @@ import Transaction from '../models/chain/transaction' import { set as setDescription, get as getDescription } from '../services/tx/transaction-description' import ShowGlobalDialogSubject from '../models/subjects/show-global-dialog' -import TransactionSize from '../models/transaction-size' export default class TransactionsController { public async getAll( @@ -147,12 +146,4 @@ export default class TransactionsController { throw err } } - - public async getTransactionSize(tx: Transaction) { - const size = TransactionSize.tx(Transaction.fromObject(tx)) - return { - status: ResponseCode.Success, - result: size, - } - } } diff --git a/packages/neuron-wallet/src/controllers/wallets.ts b/packages/neuron-wallet/src/controllers/wallets.ts index 08203144c7..db66030bfa 100644 --- a/packages/neuron-wallet/src/controllers/wallets.ts +++ b/packages/neuron-wallet/src/controllers/wallets.ts @@ -1,12 +1,12 @@ import fs from 'fs' import { t } from 'i18next' +import { prefixWith0x } from '../utils/scriptAndAddress' import { dialog, SaveDialogReturnValue, BrowserWindow, OpenDialogReturnValue } from 'electron' import WalletsService, { Wallet, WalletProperties, FileKeystoreWallet } from '../services/wallets' import NetworksService from '../services/networks' -import Keystore from '../models/keys/keystore' -import Keychain from '../models/keys/keychain' -import { validateMnemonic, mnemonicToSeedSync } from '../models/keys/mnemonic' -import { AccountExtendedPublicKey, ExtendedPrivateKey, generateMnemonic } from '../models/keys/key' +import { bytes } from '@ckb-lumos/codec' +import { Keychain, Keystore, ExtendedPrivateKey, AccountExtendedPublicKey } from '@ckb-lumos/hd' +import { generateMnemonic, validateMnemonic, mnemonicToSeedSync } from '@ckb-lumos/hd/lib/mnemonic' import CommandSubject from '../models/subjects/command' import { ResponseCode } from '../utils/const' import { @@ -106,15 +106,15 @@ export default class WalletsController { throw new InvalidMnemonic() } const extendedKey = new ExtendedPrivateKey( - masterKeychain.privateKey.toString('hex'), - masterKeychain.chainCode.toString('hex') + bytes.hexify(masterKeychain.privateKey), + bytes.hexify(masterKeychain.chainCode) ) const keystore = Keystore.create(extendedKey, password) const accountKeychain = masterKeychain.derivePath(AccountExtendedPublicKey.ckbAccountPath) const accountExtendedPublicKey = new AccountExtendedPublicKey( - accountKeychain.publicKey.toString('hex'), - accountKeychain.chainCode.toString('hex') + bytes.hexify(accountKeychain.publicKey), + bytes.hexify(accountKeychain.chainCode) ) const walletsService = WalletsService.getInstance() @@ -168,13 +168,13 @@ export default class WalletsController { const keystoreObject = Keystore.fromJson(keystore) const masterPrivateKey = keystoreObject.extendedPrivateKey(password) const masterKeychain = new Keychain( - Buffer.from(masterPrivateKey.privateKey, 'hex'), - Buffer.from(masterPrivateKey.chainCode, 'hex') + Buffer.from(bytes.bytify(masterPrivateKey.privateKey)), + Buffer.from(bytes.bytify(masterPrivateKey.chainCode)) ) const accountKeychain = masterKeychain.derivePath(AccountExtendedPublicKey.ckbAccountPath) const accountExtendedPublicKey = new AccountExtendedPublicKey( - accountKeychain.publicKey.toString('hex'), - accountKeychain.chainCode.toString('hex') + bytes.hexify(accountKeychain.publicKey), + bytes.hexify(accountKeychain.chainCode) ) const walletsService = WalletsService.getInstance() @@ -273,7 +273,7 @@ export default class WalletsController { walletName, }: ExtendedPublicKey & { walletName: string }): Promise> { const device = HardwareWalletService.getInstance().getCurrent()! - const accountExtendedPublicKey = new AccountExtendedPublicKey(publicKey, chainCode) + const accountExtendedPublicKey = new AccountExtendedPublicKey(prefixWith0x(publicKey), prefixWith0x(chainCode)) const walletsService = WalletsService.getInstance() const wallet = walletsService.create({ device: device.deviceInfo, @@ -417,6 +417,7 @@ export default class WalletsController { description?: string multisigConfig?: MultisigConfigModel amendHash?: string + skipLastInputs?: boolean }, skipSign = false ) { @@ -438,7 +439,7 @@ export default class WalletsController { params.walletID, Transaction.fromObject(params.tx), params.password, - false, + params.skipLastInputs || false, skipSign, params.amendHash ) diff --git a/packages/neuron-wallet/src/database/address/meta.ts b/packages/neuron-wallet/src/database/address/meta.ts index 3fd8f99abc..36de7a34f3 100644 --- a/packages/neuron-wallet/src/database/address/meta.ts +++ b/packages/neuron-wallet/src/database/address/meta.ts @@ -1,6 +1,6 @@ import { bytes } from '@ckb-lumos/codec' import { Address, AddressVersion } from '../../models/address' -import { AddressType } from '../../models/keys/address' +import { AddressType } from '@ckb-lumos/hd' import Script from '../../models/chain/script' import SystemScriptInfo from '../../models/system-script-info' import AssetAccountInfo from '../../models/asset-account-info' diff --git a/packages/neuron-wallet/src/database/chain/connection.ts b/packages/neuron-wallet/src/database/chain/connection.ts index a65452b356..32344e7946 100644 --- a/packages/neuron-wallet/src/database/chain/connection.ts +++ b/packages/neuron-wallet/src/database/chain/connection.ts @@ -1,15 +1,13 @@ -import { getConnection as originGetConnection } from 'typeorm' +import { ConnectionName, dataSource } from './ormconfig' import { NetworkType } from '../../models/network' import NetworksService from '../../services/networks' -export type ConnectionName = 'light' | 'full' - export function getCurrentConnectionName(): ConnectionName { return NetworksService.getInstance().getCurrent()?.type === NetworkType.Light ? 'light' : 'full' } export function getConnection(connectionName: ConnectionName = getCurrentConnectionName()) { - const connection = originGetConnection(connectionName) + const connection = dataSource[connectionName] if (!connection) { throw new Error(`The connection ${connectionName} should be initialized before use`) } diff --git a/packages/neuron-wallet/src/database/chain/entities/hd-public-key-info.ts b/packages/neuron-wallet/src/database/chain/entities/hd-public-key-info.ts index e06e3d5632..e5fa061106 100644 --- a/packages/neuron-wallet/src/database/chain/entities/hd-public-key-info.ts +++ b/packages/neuron-wallet/src/database/chain/entities/hd-public-key-info.ts @@ -1,6 +1,6 @@ import { Entity, Column, PrimaryGeneratedColumn, Index, CreateDateColumn } from 'typeorm' import HdPublicKeyInfoModel from '../../../models/keys/hd-public-key-info' -import { AddressType } from '../../../models/keys/address' +import { AddressType } from '@ckb-lumos/hd' @Entity() export default class HdPublicKeyInfo { diff --git a/packages/neuron-wallet/src/database/chain/entities/multisig-config.ts b/packages/neuron-wallet/src/database/chain/entities/multisig-config.ts index 2c0b2d8f6a..698be44a0f 100644 --- a/packages/neuron-wallet/src/database/chain/entities/multisig-config.ts +++ b/packages/neuron-wallet/src/database/chain/entities/multisig-config.ts @@ -24,10 +24,10 @@ export default class MultisigConfig { blake160s!: string[] @Column() - alias!: string + alias: string = '' @Column() - lastestBlockNumber!: string + lastestBlockNumber: string = '' public static fromModel(model: MultisigConfigModel): MultisigConfig { const multisigConfig = new MultisigConfig() diff --git a/packages/neuron-wallet/src/database/chain/ormconfig.ts b/packages/neuron-wallet/src/database/chain/ormconfig.ts index ac120deeca..e49a21cad4 100644 --- a/packages/neuron-wallet/src/database/chain/ormconfig.ts +++ b/packages/neuron-wallet/src/database/chain/ormconfig.ts @@ -1,4 +1,4 @@ -import { createConnection, getConnectionOptions, getConnection } from 'typeorm' +import { AbstractLogger, DataSource, LogLevel, LogMessage } from 'typeorm' import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions' import path from 'path' import fs from 'fs' @@ -63,7 +63,6 @@ import { CreateCellLocalInfo1701234043432 } from './migrations/1701234043432-Cre import { RenameSyncProgress1702781527414 } from './migrations/1702781527414-RenameSyncProgress' import { RemoveAddressInIndexerCache1704357651876 } from './migrations/1704357651876-RemoveAddressInIndexerCache' import { AmendTransaction1709008125088 } from './migrations/1709008125088-AmendTransaction' -import { ConnectionName } from './connection' import AddressSubscribe from './subscriber/address-subscriber' import MultisigConfigSubscribe from './subscriber/multisig-config-subscriber' import TxDescriptionSubscribe from './subscriber/tx-description-subscriber' @@ -71,24 +70,58 @@ import SudtTokenInfoSubscribe from './subscriber/sudt-token-info-subscriber' import AssetAccountSubscribe from './subscriber/asset-account-subscriber' export const CONNECTION_NOT_FOUND_NAME = 'ConnectionNotFoundError' +export type ConnectionName = 'light' | 'full' const dbPath = (name: string, connectionName: string): string => { const filename = `${connectionName}-${name}.sqlite` return path.join(env.fileBasePath, 'cells', filename) } -const connectOptions = async ( - genesisBlockHash: string, - connectionName: ConnectionName -): Promise => { - const connectionOptions = await getConnectionOptions() +class TypeormLogger extends AbstractLogger { + /** + * Write log to specific output. + */ + protected writeLog(level: LogLevel, logMessage: LogMessage | LogMessage[]) { + const messages = this.prepareLogMessages(logMessage, { + highlightSql: false, + }) + + for (let message of messages) { + switch (message.type ?? level) { + case 'log': + case 'schema-build': + case 'migration': + case 'info': + case 'query': + case 'warn': + case 'query-slow': + if (message.prefix) { + console.info(message.prefix, message.message) + } else { + console.info(message.message) + } + break + + case 'error': + case 'query-error': + if (message.prefix) { + console.error(message.prefix, message.message) + } else { + console.error(message.message) + } + break + } + } + } +} + +const getConnectionOptions = (genesisBlockHash: string, connectionName: ConnectionName): SqliteConnectionOptions => { const database = env.isTestMode ? ':memory:' : dbPath(genesisBlockHash, connectionName) const logging: boolean | ('query' | 'schema' | 'error' | 'warn' | 'info' | 'log' | 'migration')[] = ['warn', 'error'] // (env.isDevMode) ? ['warn', 'error', 'log', 'info', 'schema', 'migration'] : ['warn', 'error'] return { - ...connectionOptions, name: connectionName, type: 'sqlite', database, @@ -159,35 +192,38 @@ const connectOptions = async ( SudtTokenInfoSubscribe, TxDescriptionSubscribe, ], - logger: 'simple-console', + logger: new TypeormLogger(), logging, + migrationsRun: true, maxQueryExecutionTime: 30, } } +export const dataSource: Record = { + light: null, + full: null, +} + const initConnectionWithType = async (genesisBlockHash: string, connectionName: ConnectionName) => { // try to close connection, if not exist, will throw ConnectionNotFoundError when call getConnection() try { - await getConnection(connectionName).close() + await dataSource[connectionName]?.destroy() } catch (err) { + dataSource[connectionName] = null // do nothing } - const connectionOptions = await connectOptions(genesisBlockHash, connectionName) + const connectionOptions = getConnectionOptions(genesisBlockHash, connectionName) + dataSource[connectionName] = new DataSource(connectionOptions) try { - await createConnection(connectionOptions) - await getConnection(connectionName).manager.query(`PRAGMA busy_timeout = 3000;`) - await getConnection(connectionName).manager.query(`PRAGMA temp_store = MEMORY;`) + await dataSource[connectionName]?.initialize() + await dataSource[connectionName]?.manager.query(`PRAGMA busy_timeout = 3000;`) + await dataSource[connectionName]?.manager.query(`PRAGMA temp_store = MEMORY;`) } catch (err) { logger.error(err.message) } } -export async function initConnection(genesisBlockHash: string) { - await initConnectionWithType(genesisBlockHash, 'full') - await initConnectionWithType(genesisBlockHash, 'light') -} - export function migrateDBFile(genesisBlockHash: string) { const originDBFile = dbPath(genesisBlockHash, 'cell') const currentFullDBFile = dbPath(genesisBlockHash, 'full') @@ -203,4 +239,7 @@ export function migrateDBFile(genesisBlockHash: string) { } } -export default initConnection +export default async function initConnection(genesisBlockHash: string) { + await initConnectionWithType(genesisBlockHash, 'full') + await initConnectionWithType(genesisBlockHash, 'light') +} diff --git a/packages/neuron-wallet/src/database/chain/subscriber/asset-account-subscriber.ts b/packages/neuron-wallet/src/database/chain/subscriber/asset-account-subscriber.ts index 7e06e8db0d..a53e499f1e 100644 --- a/packages/neuron-wallet/src/database/chain/subscriber/asset-account-subscriber.ts +++ b/packages/neuron-wallet/src/database/chain/subscriber/asset-account-subscriber.ts @@ -15,9 +15,9 @@ export default class AssetAccountSubscribe extends UserSettingSubscriber): Promise { const repo = this.getNeedSyncConnection(event.connection.name)?.getRepository(AssetAccount) if (repo && event.entity) { - const exist = await repo.findOne({ tokenID: event.entity.tokenID, blake160: event.entity.blake160 }) + const exist = await repo.findOneBy({ tokenID: event.entity.tokenID, blake160: event.entity.blake160 }) if (exist) { - await repo.upsert(AssetAccount.fromModel(event.entity.toModel()), this.unionKeys) + await repo.update(exist.id, AssetAccount.fromModel(event.entity.toModel())) } else { await repo.save(event.entity) } diff --git a/packages/neuron-wallet/src/database/chain/subscriber/sudt-token-info-subscriber.ts b/packages/neuron-wallet/src/database/chain/subscriber/sudt-token-info-subscriber.ts index 5682bb5fa6..c24f777126 100644 --- a/packages/neuron-wallet/src/database/chain/subscriber/sudt-token-info-subscriber.ts +++ b/packages/neuron-wallet/src/database/chain/subscriber/sudt-token-info-subscriber.ts @@ -18,7 +18,9 @@ export default class SudtTokenInfoSubscribe extends UserSettingSubscriber = new (...args: unknown[]) => T diff --git a/packages/neuron-wallet/src/locales/en.ts b/packages/neuron-wallet/src/locales/en.ts index c7a01ed7c7..5c87164364 100644 --- a/packages/neuron-wallet/src/locales/en.ts +++ b/packages/neuron-wallet/src/locales/en.ts @@ -45,6 +45,7 @@ export default { label: 'Window', minimize: 'Minimize', close: 'Close Window', + lock: 'Lock window', }, help: { label: 'Help', diff --git a/packages/neuron-wallet/src/locales/es.ts b/packages/neuron-wallet/src/locales/es.ts index 259a23ee13..60134165fd 100644 --- a/packages/neuron-wallet/src/locales/es.ts +++ b/packages/neuron-wallet/src/locales/es.ts @@ -44,6 +44,7 @@ export default { label: 'Ventana', minimize: 'Minimizar', close: 'Cerrar Ventana', + lock: 'Ventana bloqueada', }, help: { label: 'Ayuda', diff --git a/packages/neuron-wallet/src/locales/fr.ts b/packages/neuron-wallet/src/locales/fr.ts index 892de7cb34..12c9cf30a9 100644 --- a/packages/neuron-wallet/src/locales/fr.ts +++ b/packages/neuron-wallet/src/locales/fr.ts @@ -44,6 +44,7 @@ export default { label: 'Fenêtre', minimize: 'Réduire', close: 'Fermer la fenêtre', + lock: 'Fenêtre verrouillée', }, help: { label: 'Aide', diff --git a/packages/neuron-wallet/src/locales/zh-tw.ts b/packages/neuron-wallet/src/locales/zh-tw.ts index 10f73600a9..a3c0d5c876 100644 --- a/packages/neuron-wallet/src/locales/zh-tw.ts +++ b/packages/neuron-wallet/src/locales/zh-tw.ts @@ -45,6 +45,7 @@ export default { label: '視窗', minimize: '最小化', close: '關閉視窗', + lock: '鎖定窗口', }, help: { label: '幫助', diff --git a/packages/neuron-wallet/src/locales/zh.ts b/packages/neuron-wallet/src/locales/zh.ts index 218c52483d..77812478eb 100644 --- a/packages/neuron-wallet/src/locales/zh.ts +++ b/packages/neuron-wallet/src/locales/zh.ts @@ -45,6 +45,7 @@ export default { label: '窗口', minimize: '最小化', close: '关闭窗口', + lock: '锁定窗口', }, help: { label: '帮助', diff --git a/packages/neuron-wallet/src/models/address.ts b/packages/neuron-wallet/src/models/address.ts index 7b3940cb95..f8827a1c78 100644 --- a/packages/neuron-wallet/src/models/address.ts +++ b/packages/neuron-wallet/src/models/address.ts @@ -1,4 +1,4 @@ -import { AddressType } from '../models/keys/address' +import { AddressType } from '@ckb-lumos/hd' export enum AddressVersion { Testnet = 'testnet', diff --git a/packages/neuron-wallet/src/models/asset-account-info.ts b/packages/neuron-wallet/src/models/asset-account-info.ts index de4de46073..285abb9cf6 100644 --- a/packages/neuron-wallet/src/models/asset-account-info.ts +++ b/packages/neuron-wallet/src/models/asset-account-info.ts @@ -359,6 +359,19 @@ export default class AssetAccountInfo { extensions: [], } } + + public getAcpCellDep(codeHash: string) { + switch (codeHash) { + case this.anyoneCanPayInfo.codeHash: + return this.anyoneCanPayCellDep + case this.legacyAnyoneCanPayInfo.codeHash: + return this.legacyAnyoneCanPayInfo.cellDep + case this.pwAnyoneCanPayInfo.codeHash: + return this.pwAnyoneCanPayInfo.cellDep + default: + break + } + } } function toSporeScript(info: ScriptCellInfo): SporeScript { diff --git a/packages/neuron-wallet/src/models/keys/address.ts b/packages/neuron-wallet/src/models/keys/address.ts deleted file mode 100644 index 3e460284d9..0000000000 --- a/packages/neuron-wallet/src/models/keys/address.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { scriptToAddress } from '../../utils/scriptAndAddress' -import { AccountExtendedPublicKey } from './key' -import { systemScripts } from '../../utils/systemScripts' -import hd from '@ckb-lumos/hd' - -export enum AddressType { - Receiving = 0, // External chain - Change = 1, // Internal chain -} - -export const publicKeyToAddress = (publicKey: string, isMainnet = false) => { - const pubkey = publicKey.startsWith('0x') ? publicKey : `0x${publicKey}` - return scriptToAddress( - { - codeHash: systemScripts.SECP256K1_BLAKE160.CODE_HASH, - hashType: systemScripts.SECP256K1_BLAKE160.HASH_TYPE, - args: hd.key.publicKeyToBlake160(pubkey), - }, - isMainnet - ) -} - -export default class Address { - publicKey?: string - address: string - path: string // BIP44 path - - constructor(address: string, path: string = Address.pathForReceiving(0)) { - this.address = address - this.path = path - } - - public static fromPublicKey = (publicKey: string, path: string, isMainnet = false) => { - const address = publicKeyToAddress(publicKey, isMainnet) - const instance = new Address(address, path) - instance.publicKey = publicKey - return instance - } - - public static pathFor = (type: AddressType, index: number) => { - return `${AccountExtendedPublicKey.ckbAccountPath}/${type}/${index}` - } - - public static pathForReceiving = (index: number) => { - return Address.pathFor(AddressType.Receiving, index) - } - - public static pathForChange = (index: number) => { - return Address.pathFor(AddressType.Change, index) - } -} diff --git a/packages/neuron-wallet/src/models/keys/hd-public-key-info.ts b/packages/neuron-wallet/src/models/keys/hd-public-key-info.ts index d8f8a7f4e3..86075d9589 100644 --- a/packages/neuron-wallet/src/models/keys/hd-public-key-info.ts +++ b/packages/neuron-wallet/src/models/keys/hd-public-key-info.ts @@ -1,7 +1,7 @@ +import { AddressType, AccountExtendedPublicKey } from '@ckb-lumos/hd' import { scriptToAddress } from '../../utils/scriptAndAddress' import SystemScriptInfo from '../../models/system-script-info' import NetworksService from '../../services/networks' -import Address, { AddressType } from './address' export default class HdPublicKeyInfoModel { public walletId: string @@ -22,7 +22,7 @@ export default class HdPublicKeyInfoModel { } public get path(): string { - return Address.pathFor(this.addressType, this.addressIndex) + return AccountExtendedPublicKey.pathFor(this.addressType, this.addressIndex) } constructor( diff --git a/packages/neuron-wallet/src/models/keys/key.ts b/packages/neuron-wallet/src/models/keys/key.ts deleted file mode 100644 index 5e5ae9c939..0000000000 --- a/packages/neuron-wallet/src/models/keys/key.ts +++ /dev/null @@ -1,114 +0,0 @@ -import crypto from 'crypto' - -import Address, { AddressType } from './address' -import Keychain, { privateToPublic } from './keychain' -import { entropyToMnemonic } from './mnemonic' - -export interface PathAndPrivateKey { - path: string - privateKey: string -} - -const UNCOMPRESSED_KEY_LENGTH = 130 - -export class ExtendedPublicKey { - publicKey: string - chainCode: string - - constructor(publicKey: string, chainCode: string) { - this.publicKey = ExtendedPublicKey.compressPublicKey(publicKey) - this.chainCode = chainCode - } - - static compressPublicKey = (key: string) => { - if (key.length !== UNCOMPRESSED_KEY_LENGTH) { - return key - } - - const publicKey = Buffer.from(key, 'hex') - const compressedPublicKey = Buffer.alloc(33) - // '03' for odd value, '02' for even value - .fill(publicKey[64] & 1 ? '03' : '02', 0, 1, 'hex') - .fill(publicKey.subarray(1, 33), 1, 33) - return compressedPublicKey.toString('hex') - } - - isUncompressedKey = (publicKey: string) => { - return publicKey.startsWith('04') - } - - serialize = () => { - return this.publicKey + this.chainCode - } - - static parse = (serialized: string) => { - return new ExtendedPublicKey(serialized.slice(0, 66), serialized.slice(66)) - } -} - -// Extended public key of the BIP44 path down to account level, -// which is `m/44'/309'/0'`. This key will be persisted to wallet -// and used to derive receiving/change addresses. -export class AccountExtendedPublicKey extends ExtendedPublicKey { - public static ckbAccountPath = `m/44'/309'/0'` - - static parse = (serialized: string) => { - return new AccountExtendedPublicKey(serialized.slice(0, 66), serialized.slice(66)) - } - - address = (type: AddressType = AddressType.Receiving, index: number, isMainnet = false): Address => { - return Address.fromPublicKey(this.addressPublicKey(type, index), Address.pathFor(type, index), isMainnet) - } - - private addressPublicKey = (type = AddressType.Receiving, index: number) => { - const keychain = Keychain.fromPublicKey( - Buffer.from(this.publicKey, 'hex'), - Buffer.from(this.chainCode, 'hex'), - AccountExtendedPublicKey.ckbAccountPath - ) - .deriveChild(type, false) - .deriveChild(index, false) - - return keychain.publicKey.toString('hex') - } -} - -export class ExtendedPrivateKey { - privateKey: string - chainCode: string - - constructor(privateKey: string, chainCode: string) { - this.privateKey = privateKey - this.chainCode = chainCode - } - - serialize = () => { - return this.privateKey + this.chainCode - } - - toExtendedPublicKey = (): ExtendedPublicKey => { - const publicKey = privateToPublic(Buffer.from(this.privateKey, 'hex')).toString('hex') - return new ExtendedPublicKey(publicKey, this.chainCode) - } - - static parse = (serialized: string) => { - return new ExtendedPrivateKey(serialized.slice(0, 64), serialized.slice(64)) - } -} - -export enum DefaultAddressNumber { - Receiving = 20, - Change = 10, -} - -export interface Addresses { - receiving: Address[] - change: Address[] -} - -// Generate 12 words mnemonic code -export const generateMnemonic = () => { - const entropySize = 16 - const entropy = crypto.randomBytes(entropySize).toString('hex') - return entropyToMnemonic(entropy) -} diff --git a/packages/neuron-wallet/src/models/keys/keychain.ts b/packages/neuron-wallet/src/models/keys/keychain.ts deleted file mode 100644 index c7a0872e5f..0000000000 --- a/packages/neuron-wallet/src/models/keys/keychain.ts +++ /dev/null @@ -1,149 +0,0 @@ -import crypto from 'crypto' -import { ec as EC } from 'elliptic' -import BN from 'bn.js' - -const ec = new EC('secp256k1') - -export const privateToPublic = (privateKey: Buffer) => { - if (privateKey.length !== 32) { - throw new Error('Private key must be 32 bytes') - } - - return Buffer.from(ec.keyFromPrivate(privateKey).getPublic(true, 'hex') as string, 'hex') -} - -const EMPTY_BUFFER = Buffer.from('') - -// BIP32 Keychain. Not a full implementation. -export default class Keychain { - privateKey: Buffer = EMPTY_BUFFER - publicKey: Buffer = EMPTY_BUFFER - chainCode: Buffer = EMPTY_BUFFER - index: number = 0 - depth: number = 0 - identifier: Buffer = EMPTY_BUFFER - fingerprint: number = 0 - parentFingerprint: number = 0 - - constructor(privateKey: Buffer, chainCode: Buffer) { - this.privateKey = privateKey - this.chainCode = chainCode - - if (!this.isNeutered()) { - this.publicKey = privateToPublic(this.privateKey) - } - } - - calculateFingerprint = () => { - this.identifier = this.hash160(this.publicKey) - this.fingerprint = this.identifier.slice(0, 4).readUInt32BE(0) - } - - public static fromSeed = (seed: Buffer): Keychain => { - const i = crypto.createHmac('sha512', Buffer.from('Bitcoin seed', 'utf8')).update(seed).digest() - const keychain = new Keychain(i.slice(0, 32), i.slice(32)) - keychain.calculateFingerprint() - return keychain - } - - // Create a child keychain with extended public key and path. - // Children of this keychain should not have any hardened paths. - public static fromPublicKey = (publicKey: Buffer, chainCode: Buffer, path: string): Keychain => { - const keychain = new Keychain(EMPTY_BUFFER, chainCode) - keychain.publicKey = publicKey - keychain.calculateFingerprint() - - const pathComponents = path.split('/') - keychain.depth = pathComponents.length - 1 - keychain.index = parseInt(pathComponents[pathComponents.length - 1], 10) - - return keychain - } - - public deriveChild = (index: number, hardened: boolean): Keychain => { - let data: Buffer - - const indexBuffer = Buffer.allocUnsafe(4) - - if (hardened) { - const pk = Buffer.concat([Buffer.alloc(1, 0), this.privateKey]) - indexBuffer.writeUInt32BE(index + 0x80000000, 0) - data = Buffer.concat([pk, indexBuffer]) - } else { - indexBuffer.writeUInt32BE(index, 0) - data = Buffer.concat([this.publicKey, indexBuffer]) - } - - const i = crypto.createHmac('sha512', this.chainCode).update(data).digest() - const il = i.slice(0, 32) - const ir = i.slice(32) - - let child: Keychain - if (this.isNeutered()) { - child = new Keychain(EMPTY_BUFFER, ir) - child.publicKey = Keychain.publicKeyAdd(this.publicKey, il) - child.calculateFingerprint() - } else { - const privateKey = Keychain.privateKeyAdd(this.privateKey, il) - child = new Keychain(privateKey, ir) - child.calculateFingerprint() - } - - child.index = index - child.depth = this.depth + 1 - child.parentFingerprint = this.fingerprint - - return child - } - - public derivePath = (path: string): Keychain => { - const master = ['m', `/`, ''] - if (master.includes(path)) { - return this - } - - // eslint-disable-next-line @typescript-eslint/no-this-alias - let bip32: Keychain = this - - let entries = path.split('/') - if (entries[0] === 'm') { - entries = entries.slice(1) - } - entries.forEach(c => { - const childIndex = parseInt(c, 10) - const hardened = c.length > 1 && c[c.length - 1] === "'" - bip32 = bip32.deriveChild(childIndex, hardened) - }) - - return bip32 - } - - isNeutered = (): boolean => { - return this.privateKey === EMPTY_BUFFER - } - - hash160 = (data: Buffer): Buffer => { - const sha256 = crypto.createHash('sha256').update(data).digest() - return crypto.createHash('ripemd160').update(sha256).digest() - } - - private static privateKeyAdd = (privateKey: Buffer, factor: Buffer): Buffer => { - const result = new BN(factor) - result.iadd(new BN(privateKey)) - if (result.cmp(ec.curve.n) >= 0) { - result.isub(ec.curve.n) - } - - return result.toArrayLike(Buffer, 'be', 32) - } - - private static publicKeyAdd = (publicKey: Buffer, factor: Buffer): Buffer => { - const x = new BN(publicKey.slice(1)).toRed(ec.curve.red) - let y = x.redSqr().redIMul(x).redIAdd(ec.curve.b).redSqrt() - if ((publicKey[0] === 0x03) !== y.isOdd()) { - y = y.redNeg() - } - const point = ec.curve.g.mul(new BN(factor)).add({ x, y }) - return Buffer.from(point.encode(true, true)) - } -} diff --git a/packages/neuron-wallet/src/models/keys/keystore.ts b/packages/neuron-wallet/src/models/keys/keystore.ts deleted file mode 100644 index f69427433c..0000000000 --- a/packages/neuron-wallet/src/models/keys/keystore.ts +++ /dev/null @@ -1,176 +0,0 @@ -import crypto from 'crypto' -import { Keccak } from 'sha3' -import { v4 as uuid } from 'uuid' - -import { UnsupportedCipher, IncorrectPassword, InvalidKeystore } from '../../exceptions' -import { ExtendedPrivateKey } from './key' - -const CIPHER = 'aes-128-ctr' -const CKB_CLI_ORIGIN = 'ckb-cli' - -interface CipherParams { - iv: string -} - -interface KdfParams { - dklen: number - n: number - r: number - p: number - salt: string -} - -interface Crypto { - cipher: string - cipherparams: CipherParams - ciphertext: string - kdf: string - kdfparams: KdfParams - mac: string -} - -// Encrypt and save master extended private key. -export default class Keystore { - crypto: Crypto - id: string - version: number = 3 - - constructor(theCrypto: Crypto, id: string) { - this.crypto = theCrypto - this.id = id - } - - static fromJson = (json: string) => { - try { - const object = JSON.parse(json) - if (object.origin === CKB_CLI_ORIGIN) { - throw 'Keystore from CKB CLI is not supported' - } - if (!object.crypto || !object.id) { - throw new InvalidKeystore() - } - return new Keystore(object.crypto, object.id) - } catch { - throw new InvalidKeystore() - } - } - - // Create an empty keystore object that contains empty private key - static createEmpty = () => { - const salt = crypto.randomBytes(32) - const iv = crypto.randomBytes(16) - const kdfparams: KdfParams = { - dklen: 32, - salt: salt.toString('hex'), - n: 2 ** 18, - r: 8, - p: 1, - } - return new Keystore( - { - ciphertext: '', - cipherparams: { - iv: iv.toString('hex'), - }, - cipher: CIPHER, - kdf: 'scrypt', - kdfparams, - mac: '', - }, - uuid() - ) - } - - static create = ( - extendedPrivateKey: ExtendedPrivateKey, - password: string, - options: { salt?: Buffer; iv?: Buffer } = {} - ) => { - const salt = options.salt || crypto.randomBytes(32) - const iv = options.iv || crypto.randomBytes(16) - const kdfparams: KdfParams = { - dklen: 32, - salt: salt.toString('hex'), - n: 2 ** 18, - r: 8, - p: 1, - } - const derivedKey = crypto.scryptSync(password, salt, kdfparams.dklen, Keystore.scryptOptions(kdfparams)) - - const cipher = crypto.createCipheriv(CIPHER, derivedKey.slice(0, 16), iv) - if (!cipher) { - throw new UnsupportedCipher() - } - const ciphertext = Buffer.concat([ - cipher.update(Buffer.from(extendedPrivateKey.serialize(), 'hex')), - cipher.final(), - ]) - - return new Keystore( - { - ciphertext: ciphertext.toString('hex'), - cipherparams: { - iv: iv.toString('hex'), - }, - cipher: CIPHER, - kdf: 'scrypt', - kdfparams, - mac: Keystore.mac(derivedKey, ciphertext), - }, - uuid() - ) - } - - // Imported from xpub with empty private key. - isEmpty(): boolean { - return this.crypto.ciphertext === '' && this.crypto.mac === '' - } - - // Decrypt and return serialized extended private key. - decrypt(password: string): string { - const derivedKey = this.derivedKey(password) - const ciphertext = Buffer.from(this.crypto.ciphertext, 'hex') - if (Keystore.mac(derivedKey, ciphertext) !== this.crypto.mac) { - throw new IncorrectPassword() - } - const decipher = crypto.createDecipheriv( - this.crypto.cipher, - derivedKey.slice(0, 16), - Buffer.from(this.crypto.cipherparams.iv, 'hex') - ) - return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('hex') - } - - extendedPrivateKey = (password: string): ExtendedPrivateKey => { - return ExtendedPrivateKey.parse(this.decrypt(password)) - } - - checkPassword = (password: string) => { - const derivedKey = this.derivedKey(password) - const ciphertext = Buffer.from(this.crypto.ciphertext, 'hex') - return Keystore.mac(derivedKey, ciphertext) === this.crypto.mac - } - - derivedKey = (password: string) => { - const { kdfparams } = this.crypto - return crypto.scryptSync( - password, - Buffer.from(kdfparams.salt, 'hex'), - kdfparams.dklen, - Keystore.scryptOptions(kdfparams) - ) - } - - static mac = (derivedKey: Buffer, ciphertext: Buffer) => { - return new Keccak(256).update(Buffer.concat([derivedKey.slice(16, 32), ciphertext])).digest('hex') - } - - static scryptOptions = (kdfparams: KdfParams) => { - return { - N: kdfparams.n, - r: kdfparams.r, - p: kdfparams.p, - maxmem: 128 * (kdfparams.n + kdfparams.p + 2) * kdfparams.r, - } - } -} diff --git a/packages/neuron-wallet/src/models/keys/mnemonic/index.ts b/packages/neuron-wallet/src/models/keys/mnemonic/index.ts deleted file mode 100644 index a84ed544c2..0000000000 --- a/packages/neuron-wallet/src/models/keys/mnemonic/index.ts +++ /dev/null @@ -1,150 +0,0 @@ -import crypto from 'crypto' -import wordList from './word-list' - -const RADIX = 2048 -const PBKDF2_ROUNDS = 2048 -const KEY_LEN = 64 -const MIN_ENTROPY_SIZE = 16 -const MAX_ENTROPY_SIZE = 32 -const MIN_WORDS_SIZE = 12 -const MAX_WORDS_SIZE = 24 - -const INVALID_MNEMONIC = `Invalid mnemonic` -const INVALID_CHECKSUM = `Invalid checksum` -const ENTROPY_NOT_DIVISIBLE = `Entropy should be divisible by 4` -const ENTROPY_TOO_LONG = `Entropy should be shorter than ${MAX_ENTROPY_SIZE + 1}` -const ENTROPY_TOO_SHORT = `Entropy should be longer than ${MIN_ENTROPY_SIZE - 1}` -const WORDS_TOO_LONG = `Words should be shorter than ${MAX_WORDS_SIZE + 1}` -const WORDS_TOO_SHORT = `Words should be longer than ${MIN_WORDS_SIZE - 1}` - -if (wordList.length !== RADIX) { - throw new Error(`Word list should have ${RADIX} words, but ${wordList.length} received in fact`) -} - -const bytesToBinary = (bytes: Buffer): string => { - return bytes.reduce((binary, byte) => { - return binary + byte.toString(2).padStart(8, '0') - }, '') -} - -const deriveChecksumBits = (entropyBuffer: Buffer): string => { - const ENT = entropyBuffer.length * 8 - const CS = ENT / 32 - const hash = crypto.createHash('sha256').update(entropyBuffer).digest() - return bytesToBinary(hash).slice(0, CS) -} - -const salt = (password = '') => { - return `mnemonic${password}` -} - -export const mnemonicToSeedSync = (mnemonic: string = '', password: string = '') => { - const mnemonicBuffer = Buffer.from(mnemonic.normalize('NFKD'), 'utf8') - const saltBuffer = Buffer.from(salt(password.normalize('NFKD')), 'utf8') - return crypto.pbkdf2Sync(mnemonicBuffer, saltBuffer, PBKDF2_ROUNDS, KEY_LEN, 'sha512') -} - -export function mnemonicToSeed(mnemonic: string = '', password: string = ''): Promise { - return new Promise((resolve, reject) => { - try { - const mnemonicBuffer = Buffer.from(mnemonic.normalize('NFKD'), 'utf8') - const saltBuffer = Buffer.from(salt(password.normalize('NFKD')), 'utf8') - crypto.pbkdf2(mnemonicBuffer, saltBuffer, PBKDF2_ROUNDS, KEY_LEN, 'sha512', (err, data) => { - if (err) { - reject(err) - } - resolve(data) - }) - } catch (error) { - reject(error) - } - }) -} - -export function mnemonicToEntropy(mnemonic: string = '') { - const words = mnemonic.normalize('NFKD').split(' ') - if (words.length < MIN_WORDS_SIZE) { - throw new Error(WORDS_TOO_SHORT) - } - if (words.length > MAX_WORDS_SIZE) { - throw new Error(WORDS_TOO_LONG) - } - if (words.length % 3 !== 0) { - throw new Error(INVALID_MNEMONIC) - } - const bits = words - .map(word => { - const index = wordList!.indexOf(word) - if (index === -1) { - throw new Error(INVALID_MNEMONIC) - } - return index.toString(2).padStart(11, '0') - }) - .join('') - - const dividerIndex = Math.floor(bits.length / 33) * 32 - const entropyBits = bits.slice(0, dividerIndex) - const checksumBits = bits.slice(dividerIndex) - - const entropyBytes = entropyBits.match(/(.{1,8})/g)!.map(byte => parseInt(byte, 2)) - if (entropyBytes.length < MIN_ENTROPY_SIZE) { - throw new Error(ENTROPY_TOO_SHORT) - } - if (entropyBytes.length > MAX_ENTROPY_SIZE) { - throw new Error(ENTROPY_TOO_LONG) - } - if (entropyBytes.length % 4 !== 0) { - throw new Error(ENTROPY_NOT_DIVISIBLE) - } - - const entropy = Buffer.from(entropyBytes) - const newChecksum = deriveChecksumBits(entropy) - if (newChecksum !== checksumBits) { - throw new Error(INVALID_CHECKSUM) - } - - return entropy.toString('hex') -} - -export function entropyToMnemonic(entropyStr: string) { - const entropy = Buffer.from(entropyStr, 'hex') - - if (entropy.length < MIN_ENTROPY_SIZE) { - throw new TypeError(ENTROPY_TOO_SHORT) - } - if (entropy.length > MAX_ENTROPY_SIZE) { - throw new TypeError(ENTROPY_TOO_LONG) - } - if (entropy.length % 4 !== 0) { - throw new TypeError(ENTROPY_NOT_DIVISIBLE) - } - - const entropyBytes = bytesToBinary(entropy) - const checksumBytes = deriveChecksumBits(entropy) - - const bytes = entropyBytes + checksumBytes - const chunks = bytes.match(/(.{1,11})/g)! - const words = chunks.map(binary => { - const index = parseInt(binary, 2) - return wordList[index] - }) - - return words.join(' ') -} - -export function validateMnemonic(mnemonic: string) { - try { - mnemonicToEntropy(mnemonic) - } catch (e) { - return false - } - return true -} - -export default { - entropyToMnemonic, - mnemonicToEntropy, - mnemonicToSeed, - mnemonicToSeedSync, - validateMnemonic, -} diff --git a/packages/neuron-wallet/src/models/keys/mnemonic/word-list.ts b/packages/neuron-wallet/src/models/keys/mnemonic/word-list.ts deleted file mode 100644 index f5d81d3377..0000000000 --- a/packages/neuron-wallet/src/models/keys/mnemonic/word-list.ts +++ /dev/null @@ -1,2050 +0,0 @@ -export default [ - 'abandon', - 'ability', - 'able', - 'about', - 'above', - 'absent', - 'absorb', - 'abstract', - 'absurd', - 'abuse', - 'access', - 'accident', - 'account', - 'accuse', - 'achieve', - 'acid', - 'acoustic', - 'acquire', - 'across', - 'act', - 'action', - 'actor', - 'actress', - 'actual', - 'adapt', - 'add', - 'addict', - 'address', - 'adjust', - 'admit', - 'adult', - 'advance', - 'advice', - 'aerobic', - 'affair', - 'afford', - 'afraid', - 'again', - 'age', - 'agent', - 'agree', - 'ahead', - 'aim', - 'air', - 'airport', - 'aisle', - 'alarm', - 'album', - 'alcohol', - 'alert', - 'alien', - 'all', - 'alley', - 'allow', - 'almost', - 'alone', - 'alpha', - 'already', - 'also', - 'alter', - 'always', - 'amateur', - 'amazing', - 'among', - 'amount', - 'amused', - 'analyst', - 'anchor', - 'ancient', - 'anger', - 'angle', - 'angry', - 'animal', - 'ankle', - 'announce', - 'annual', - 'another', - 'answer', - 'antenna', - 'antique', - 'anxiety', - 'any', - 'apart', - 'apology', - 'appear', - 'apple', - 'approve', - 'april', - 'arch', - 'arctic', - 'area', - 'arena', - 'argue', - 'arm', - 'armed', - 'armor', - 'army', - 'around', - 'arrange', - 'arrest', - 'arrive', - 'arrow', - 'art', - 'artefact', - 'artist', - 'artwork', - 'ask', - 'aspect', - 'assault', - 'asset', - 'assist', - 'assume', - 'asthma', - 'athlete', - 'atom', - 'attack', - 'attend', - 'attitude', - 'attract', - 'auction', - 'audit', - 'august', - 'aunt', - 'author', - 'auto', - 'autumn', - 'average', - 'avocado', - 'avoid', - 'awake', - 'aware', - 'away', - 'awesome', - 'awful', - 'awkward', - 'axis', - 'baby', - 'bachelor', - 'bacon', - 'badge', - 'bag', - 'balance', - 'balcony', - 'ball', - 'bamboo', - 'banana', - 'banner', - 'bar', - 'barely', - 'bargain', - 'barrel', - 'base', - 'basic', - 'basket', - 'battle', - 'beach', - 'bean', - 'beauty', - 'because', - 'become', - 'beef', - 'before', - 'begin', - 'behave', - 'behind', - 'believe', - 'below', - 'belt', - 'bench', - 'benefit', - 'best', - 'betray', - 'better', - 'between', - 'beyond', - 'bicycle', - 'bid', - 'bike', - 'bind', - 'biology', - 'bird', - 'birth', - 'bitter', - 'black', - 'blade', - 'blame', - 'blanket', - 'blast', - 'bleak', - 'bless', - 'blind', - 'blood', - 'blossom', - 'blouse', - 'blue', - 'blur', - 'blush', - 'board', - 'boat', - 'body', - 'boil', - 'bomb', - 'bone', - 'bonus', - 'book', - 'boost', - 'border', - 'boring', - 'borrow', - 'boss', - 'bottom', - 'bounce', - 'box', - 'boy', - 'bracket', - 'brain', - 'brand', - 'brass', - 'brave', - 'bread', - 'breeze', - 'brick', - 'bridge', - 'brief', - 'bright', - 'bring', - 'brisk', - 'broccoli', - 'broken', - 'bronze', - 'broom', - 'brother', - 'brown', - 'brush', - 'bubble', - 'buddy', - 'budget', - 'buffalo', - 'build', - 'bulb', - 'bulk', - 'bullet', - 'bundle', - 'bunker', - 'burden', - 'burger', - 'burst', - 'bus', - 'business', - 'busy', - 'butter', - 'buyer', - 'buzz', - 'cabbage', - 'cabin', - 'cable', - 'cactus', - 'cage', - 'cake', - 'call', - 'calm', - 'camera', - 'camp', - 'can', - 'canal', - 'cancel', - 'candy', - 'cannon', - 'canoe', - 'canvas', - 'canyon', - 'capable', - 'capital', - 'captain', - 'car', - 'carbon', - 'card', - 'cargo', - 'carpet', - 'carry', - 'cart', - 'case', - 'cash', - 'casino', - 'castle', - 'casual', - 'cat', - 'catalog', - 'catch', - 'category', - 'cattle', - 'caught', - 'cause', - 'caution', - 'cave', - 'ceiling', - 'celery', - 'cement', - 'census', - 'century', - 'cereal', - 'certain', - 'chair', - 'chalk', - 'champion', - 'change', - 'chaos', - 'chapter', - 'charge', - 'chase', - 'chat', - 'cheap', - 'check', - 'cheese', - 'chef', - 'cherry', - 'chest', - 'chicken', - 'chief', - 'child', - 'chimney', - 'choice', - 'choose', - 'chronic', - 'chuckle', - 'chunk', - 'churn', - 'cigar', - 'cinnamon', - 'circle', - 'citizen', - 'city', - 'civil', - 'claim', - 'clap', - 'clarify', - 'claw', - 'clay', - 'clean', - 'clerk', - 'clever', - 'click', - 'client', - 'cliff', - 'climb', - 'clinic', - 'clip', - 'clock', - 'clog', - 'close', - 'cloth', - 'cloud', - 'clown', - 'club', - 'clump', - 'cluster', - 'clutch', - 'coach', - 'coast', - 'coconut', - 'code', - 'coffee', - 'coil', - 'coin', - 'collect', - 'color', - 'column', - 'combine', - 'come', - 'comfort', - 'comic', - 'common', - 'company', - 'concert', - 'conduct', - 'confirm', - 'congress', - 'connect', - 'consider', - 'control', - 'convince', - 'cook', - 'cool', - 'copper', - 'copy', - 'coral', - 'core', - 'corn', - 'correct', - 'cost', - 'cotton', - 'couch', - 'country', - 'couple', - 'course', - 'cousin', - 'cover', - 'coyote', - 'crack', - 'cradle', - 'craft', - 'cram', - 'crane', - 'crash', - 'crater', - 'crawl', - 'crazy', - 'cream', - 'credit', - 'creek', - 'crew', - 'cricket', - 'crime', - 'crisp', - 'critic', - 'crop', - 'cross', - 'crouch', - 'crowd', - 'crucial', - 'cruel', - 'cruise', - 'crumble', - 'crunch', - 'crush', - 'cry', - 'crystal', - 'cube', - 'culture', - 'cup', - 'cupboard', - 'curious', - 'current', - 'curtain', - 'curve', - 'cushion', - 'custom', - 'cute', - 'cycle', - 'dad', - 'damage', - 'damp', - 'dance', - 'danger', - 'daring', - 'dash', - 'daughter', - 'dawn', - 'day', - 'deal', - 'debate', - 'debris', - 'decade', - 'december', - 'decide', - 'decline', - 'decorate', - 'decrease', - 'deer', - 'defense', - 'define', - 'defy', - 'degree', - 'delay', - 'deliver', - 'demand', - 'demise', - 'denial', - 'dentist', - 'deny', - 'depart', - 'depend', - 'deposit', - 'depth', - 'deputy', - 'derive', - 'describe', - 'desert', - 'design', - 'desk', - 'despair', - 'destroy', - 'detail', - 'detect', - 'develop', - 'device', - 'devote', - 'diagram', - 'dial', - 'diamond', - 'diary', - 'dice', - 'diesel', - 'diet', - 'differ', - 'digital', - 'dignity', - 'dilemma', - 'dinner', - 'dinosaur', - 'direct', - 'dirt', - 'disagree', - 'discover', - 'disease', - 'dish', - 'dismiss', - 'disorder', - 'display', - 'distance', - 'divert', - 'divide', - 'divorce', - 'dizzy', - 'doctor', - 'document', - 'dog', - 'doll', - 'dolphin', - 'domain', - 'donate', - 'donkey', - 'donor', - 'door', - 'dose', - 'double', - 'dove', - 'draft', - 'dragon', - 'drama', - 'drastic', - 'draw', - 'dream', - 'dress', - 'drift', - 'drill', - 'drink', - 'drip', - 'drive', - 'drop', - 'drum', - 'dry', - 'duck', - 'dumb', - 'dune', - 'during', - 'dust', - 'dutch', - 'duty', - 'dwarf', - 'dynamic', - 'eager', - 'eagle', - 'early', - 'earn', - 'earth', - 'easily', - 'east', - 'easy', - 'echo', - 'ecology', - 'economy', - 'edge', - 'edit', - 'educate', - 'effort', - 'egg', - 'eight', - 'either', - 'elbow', - 'elder', - 'electric', - 'elegant', - 'element', - 'elephant', - 'elevator', - 'elite', - 'else', - 'embark', - 'embody', - 'embrace', - 'emerge', - 'emotion', - 'employ', - 'empower', - 'empty', - 'enable', - 'enact', - 'end', - 'endless', - 'endorse', - 'enemy', - 'energy', - 'enforce', - 'engage', - 'engine', - 'enhance', - 'enjoy', - 'enlist', - 'enough', - 'enrich', - 'enroll', - 'ensure', - 'enter', - 'entire', - 'entry', - 'envelope', - 'episode', - 'equal', - 'equip', - 'era', - 'erase', - 'erode', - 'erosion', - 'error', - 'erupt', - 'escape', - 'essay', - 'essence', - 'estate', - 'eternal', - 'ethics', - 'evidence', - 'evil', - 'evoke', - 'evolve', - 'exact', - 'example', - 'excess', - 'exchange', - 'excite', - 'exclude', - 'excuse', - 'execute', - 'exercise', - 'exhaust', - 'exhibit', - 'exile', - 'exist', - 'exit', - 'exotic', - 'expand', - 'expect', - 'expire', - 'explain', - 'expose', - 'express', - 'extend', - 'extra', - 'eye', - 'eyebrow', - 'fabric', - 'face', - 'faculty', - 'fade', - 'faint', - 'faith', - 'fall', - 'false', - 'fame', - 'family', - 'famous', - 'fan', - 'fancy', - 'fantasy', - 'farm', - 'fashion', - 'fat', - 'fatal', - 'father', - 'fatigue', - 'fault', - 'favorite', - 'feature', - 'february', - 'federal', - 'fee', - 'feed', - 'feel', - 'female', - 'fence', - 'festival', - 'fetch', - 'fever', - 'few', - 'fiber', - 'fiction', - 'field', - 'figure', - 'file', - 'film', - 'filter', - 'final', - 'find', - 'fine', - 'finger', - 'finish', - 'fire', - 'firm', - 'first', - 'fiscal', - 'fish', - 'fit', - 'fitness', - 'fix', - 'flag', - 'flame', - 'flash', - 'flat', - 'flavor', - 'flee', - 'flight', - 'flip', - 'float', - 'flock', - 'floor', - 'flower', - 'fluid', - 'flush', - 'fly', - 'foam', - 'focus', - 'fog', - 'foil', - 'fold', - 'follow', - 'food', - 'foot', - 'force', - 'forest', - 'forget', - 'fork', - 'fortune', - 'forum', - 'forward', - 'fossil', - 'foster', - 'found', - 'fox', - 'fragile', - 'frame', - 'frequent', - 'fresh', - 'friend', - 'fringe', - 'frog', - 'front', - 'frost', - 'frown', - 'frozen', - 'fruit', - 'fuel', - 'fun', - 'funny', - 'furnace', - 'fury', - 'future', - 'gadget', - 'gain', - 'galaxy', - 'gallery', - 'game', - 'gap', - 'garage', - 'garbage', - 'garden', - 'garlic', - 'garment', - 'gas', - 'gasp', - 'gate', - 'gather', - 'gauge', - 'gaze', - 'general', - 'genius', - 'genre', - 'gentle', - 'genuine', - 'gesture', - 'ghost', - 'giant', - 'gift', - 'giggle', - 'ginger', - 'giraffe', - 'girl', - 'give', - 'glad', - 'glance', - 'glare', - 'glass', - 'glide', - 'glimpse', - 'globe', - 'gloom', - 'glory', - 'glove', - 'glow', - 'glue', - 'goat', - 'goddess', - 'gold', - 'good', - 'goose', - 'gorilla', - 'gospel', - 'gossip', - 'govern', - 'gown', - 'grab', - 'grace', - 'grain', - 'grant', - 'grape', - 'grass', - 'gravity', - 'great', - 'green', - 'grid', - 'grief', - 'grit', - 'grocery', - 'group', - 'grow', - 'grunt', - 'guard', - 'guess', - 'guide', - 'guilt', - 'guitar', - 'gun', - 'gym', - 'habit', - 'hair', - 'half', - 'hammer', - 'hamster', - 'hand', - 'happy', - 'harbor', - 'hard', - 'harsh', - 'harvest', - 'hat', - 'have', - 'hawk', - 'hazard', - 'head', - 'health', - 'heart', - 'heavy', - 'hedgehog', - 'height', - 'hello', - 'helmet', - 'help', - 'hen', - 'hero', - 'hidden', - 'high', - 'hill', - 'hint', - 'hip', - 'hire', - 'history', - 'hobby', - 'hockey', - 'hold', - 'hole', - 'holiday', - 'hollow', - 'home', - 'honey', - 'hood', - 'hope', - 'horn', - 'horror', - 'horse', - 'hospital', - 'host', - 'hotel', - 'hour', - 'hover', - 'hub', - 'huge', - 'human', - 'humble', - 'humor', - 'hundred', - 'hungry', - 'hunt', - 'hurdle', - 'hurry', - 'hurt', - 'husband', - 'hybrid', - 'ice', - 'icon', - 'idea', - 'identify', - 'idle', - 'ignore', - 'ill', - 'illegal', - 'illness', - 'image', - 'imitate', - 'immense', - 'immune', - 'impact', - 'impose', - 'improve', - 'impulse', - 'inch', - 'include', - 'income', - 'increase', - 'index', - 'indicate', - 'indoor', - 'industry', - 'infant', - 'inflict', - 'inform', - 'inhale', - 'inherit', - 'initial', - 'inject', - 'injury', - 'inmate', - 'inner', - 'innocent', - 'input', - 'inquiry', - 'insane', - 'insect', - 'inside', - 'inspire', - 'install', - 'intact', - 'interest', - 'into', - 'invest', - 'invite', - 'involve', - 'iron', - 'island', - 'isolate', - 'issue', - 'item', - 'ivory', - 'jacket', - 'jaguar', - 'jar', - 'jazz', - 'jealous', - 'jeans', - 'jelly', - 'jewel', - 'job', - 'join', - 'joke', - 'journey', - 'joy', - 'judge', - 'juice', - 'jump', - 'jungle', - 'junior', - 'junk', - 'just', - 'kangaroo', - 'keen', - 'keep', - 'ketchup', - 'key', - 'kick', - 'kid', - 'kidney', - 'kind', - 'kingdom', - 'kiss', - 'kit', - 'kitchen', - 'kite', - 'kitten', - 'kiwi', - 'knee', - 'knife', - 'knock', - 'know', - 'lab', - 'label', - 'labor', - 'ladder', - 'lady', - 'lake', - 'lamp', - 'language', - 'laptop', - 'large', - 'later', - 'latin', - 'laugh', - 'laundry', - 'lava', - 'law', - 'lawn', - 'lawsuit', - 'layer', - 'lazy', - 'leader', - 'leaf', - 'learn', - 'leave', - 'lecture', - 'left', - 'leg', - 'legal', - 'legend', - 'leisure', - 'lemon', - 'lend', - 'length', - 'lens', - 'leopard', - 'lesson', - 'letter', - 'level', - 'liar', - 'liberty', - 'library', - 'license', - 'life', - 'lift', - 'light', - 'like', - 'limb', - 'limit', - 'link', - 'lion', - 'liquid', - 'list', - 'little', - 'live', - 'lizard', - 'load', - 'loan', - 'lobster', - 'local', - 'lock', - 'logic', - 'lonely', - 'long', - 'loop', - 'lottery', - 'loud', - 'lounge', - 'love', - 'loyal', - 'lucky', - 'luggage', - 'lumber', - 'lunar', - 'lunch', - 'luxury', - 'lyrics', - 'machine', - 'mad', - 'magic', - 'magnet', - 'maid', - 'mail', - 'main', - 'major', - 'make', - 'mammal', - 'man', - 'manage', - 'mandate', - 'mango', - 'mansion', - 'manual', - 'maple', - 'marble', - 'march', - 'margin', - 'marine', - 'market', - 'marriage', - 'mask', - 'mass', - 'master', - 'match', - 'material', - 'math', - 'matrix', - 'matter', - 'maximum', - 'maze', - 'meadow', - 'mean', - 'measure', - 'meat', - 'mechanic', - 'medal', - 'media', - 'melody', - 'melt', - 'member', - 'memory', - 'mention', - 'menu', - 'mercy', - 'merge', - 'merit', - 'merry', - 'mesh', - 'message', - 'metal', - 'method', - 'middle', - 'midnight', - 'milk', - 'million', - 'mimic', - 'mind', - 'minimum', - 'minor', - 'minute', - 'miracle', - 'mirror', - 'misery', - 'miss', - 'mistake', - 'mix', - 'mixed', - 'mixture', - 'mobile', - 'model', - 'modify', - 'mom', - 'moment', - 'monitor', - 'monkey', - 'monster', - 'month', - 'moon', - 'moral', - 'more', - 'morning', - 'mosquito', - 'mother', - 'motion', - 'motor', - 'mountain', - 'mouse', - 'move', - 'movie', - 'much', - 'muffin', - 'mule', - 'multiply', - 'muscle', - 'museum', - 'mushroom', - 'music', - 'must', - 'mutual', - 'myself', - 'mystery', - 'myth', - 'naive', - 'name', - 'napkin', - 'narrow', - 'nasty', - 'nation', - 'nature', - 'near', - 'neck', - 'need', - 'negative', - 'neglect', - 'neither', - 'nephew', - 'nerve', - 'nest', - 'net', - 'network', - 'neutral', - 'never', - 'news', - 'next', - 'nice', - 'night', - 'noble', - 'noise', - 'nominee', - 'noodle', - 'normal', - 'north', - 'nose', - 'notable', - 'note', - 'nothing', - 'notice', - 'novel', - 'now', - 'nuclear', - 'number', - 'nurse', - 'nut', - 'oak', - 'obey', - 'object', - 'oblige', - 'obscure', - 'observe', - 'obtain', - 'obvious', - 'occur', - 'ocean', - 'october', - 'odor', - 'off', - 'offer', - 'office', - 'often', - 'oil', - 'okay', - 'old', - 'olive', - 'olympic', - 'omit', - 'once', - 'one', - 'onion', - 'online', - 'only', - 'open', - 'opera', - 'opinion', - 'oppose', - 'option', - 'orange', - 'orbit', - 'orchard', - 'order', - 'ordinary', - 'organ', - 'orient', - 'original', - 'orphan', - 'ostrich', - 'other', - 'outdoor', - 'outer', - 'output', - 'outside', - 'oval', - 'oven', - 'over', - 'own', - 'owner', - 'oxygen', - 'oyster', - 'ozone', - 'pact', - 'paddle', - 'page', - 'pair', - 'palace', - 'palm', - 'panda', - 'panel', - 'panic', - 'panther', - 'paper', - 'parade', - 'parent', - 'park', - 'parrot', - 'party', - 'pass', - 'patch', - 'path', - 'patient', - 'patrol', - 'pattern', - 'pause', - 'pave', - 'payment', - 'peace', - 'peanut', - 'pear', - 'peasant', - 'pelican', - 'pen', - 'penalty', - 'pencil', - 'people', - 'pepper', - 'perfect', - 'permit', - 'person', - 'pet', - 'phone', - 'photo', - 'phrase', - 'physical', - 'piano', - 'picnic', - 'picture', - 'piece', - 'pig', - 'pigeon', - 'pill', - 'pilot', - 'pink', - 'pioneer', - 'pipe', - 'pistol', - 'pitch', - 'pizza', - 'place', - 'planet', - 'plastic', - 'plate', - 'play', - 'please', - 'pledge', - 'pluck', - 'plug', - 'plunge', - 'poem', - 'poet', - 'point', - 'polar', - 'pole', - 'police', - 'pond', - 'pony', - 'pool', - 'popular', - 'portion', - 'position', - 'possible', - 'post', - 'potato', - 'pottery', - 'poverty', - 'powder', - 'power', - 'practice', - 'praise', - 'predict', - 'prefer', - 'prepare', - 'present', - 'pretty', - 'prevent', - 'price', - 'pride', - 'primary', - 'print', - 'priority', - 'prison', - 'private', - 'prize', - 'problem', - 'process', - 'produce', - 'profit', - 'program', - 'project', - 'promote', - 'proof', - 'property', - 'prosper', - 'protect', - 'proud', - 'provide', - 'public', - 'pudding', - 'pull', - 'pulp', - 'pulse', - 'pumpkin', - 'punch', - 'pupil', - 'puppy', - 'purchase', - 'purity', - 'purpose', - 'purse', - 'push', - 'put', - 'puzzle', - 'pyramid', - 'quality', - 'quantum', - 'quarter', - 'question', - 'quick', - 'quit', - 'quiz', - 'quote', - 'rabbit', - 'raccoon', - 'race', - 'rack', - 'radar', - 'radio', - 'rail', - 'rain', - 'raise', - 'rally', - 'ramp', - 'ranch', - 'random', - 'range', - 'rapid', - 'rare', - 'rate', - 'rather', - 'raven', - 'raw', - 'razor', - 'ready', - 'real', - 'reason', - 'rebel', - 'rebuild', - 'recall', - 'receive', - 'recipe', - 'record', - 'recycle', - 'reduce', - 'reflect', - 'reform', - 'refuse', - 'region', - 'regret', - 'regular', - 'reject', - 'relax', - 'release', - 'relief', - 'rely', - 'remain', - 'remember', - 'remind', - 'remove', - 'render', - 'renew', - 'rent', - 'reopen', - 'repair', - 'repeat', - 'replace', - 'report', - 'require', - 'rescue', - 'resemble', - 'resist', - 'resource', - 'response', - 'result', - 'retire', - 'retreat', - 'return', - 'reunion', - 'reveal', - 'review', - 'reward', - 'rhythm', - 'rib', - 'ribbon', - 'rice', - 'rich', - 'ride', - 'ridge', - 'rifle', - 'right', - 'rigid', - 'ring', - 'riot', - 'ripple', - 'risk', - 'ritual', - 'rival', - 'river', - 'road', - 'roast', - 'robot', - 'robust', - 'rocket', - 'romance', - 'roof', - 'rookie', - 'room', - 'rose', - 'rotate', - 'rough', - 'round', - 'route', - 'royal', - 'rubber', - 'rude', - 'rug', - 'rule', - 'run', - 'runway', - 'rural', - 'sad', - 'saddle', - 'sadness', - 'safe', - 'sail', - 'salad', - 'salmon', - 'salon', - 'salt', - 'salute', - 'same', - 'sample', - 'sand', - 'satisfy', - 'satoshi', - 'sauce', - 'sausage', - 'save', - 'say', - 'scale', - 'scan', - 'scare', - 'scatter', - 'scene', - 'scheme', - 'school', - 'science', - 'scissors', - 'scorpion', - 'scout', - 'scrap', - 'screen', - 'script', - 'scrub', - 'sea', - 'search', - 'season', - 'seat', - 'second', - 'secret', - 'section', - 'security', - 'seed', - 'seek', - 'segment', - 'select', - 'sell', - 'seminar', - 'senior', - 'sense', - 'sentence', - 'series', - 'service', - 'session', - 'settle', - 'setup', - 'seven', - 'shadow', - 'shaft', - 'shallow', - 'share', - 'shed', - 'shell', - 'sheriff', - 'shield', - 'shift', - 'shine', - 'ship', - 'shiver', - 'shock', - 'shoe', - 'shoot', - 'shop', - 'short', - 'shoulder', - 'shove', - 'shrimp', - 'shrug', - 'shuffle', - 'shy', - 'sibling', - 'sick', - 'side', - 'siege', - 'sight', - 'sign', - 'silent', - 'silk', - 'silly', - 'silver', - 'similar', - 'simple', - 'since', - 'sing', - 'siren', - 'sister', - 'situate', - 'six', - 'size', - 'skate', - 'sketch', - 'ski', - 'skill', - 'skin', - 'skirt', - 'skull', - 'slab', - 'slam', - 'sleep', - 'slender', - 'slice', - 'slide', - 'slight', - 'slim', - 'slogan', - 'slot', - 'slow', - 'slush', - 'small', - 'smart', - 'smile', - 'smoke', - 'smooth', - 'snack', - 'snake', - 'snap', - 'sniff', - 'snow', - 'soap', - 'soccer', - 'social', - 'sock', - 'soda', - 'soft', - 'solar', - 'soldier', - 'solid', - 'solution', - 'solve', - 'someone', - 'song', - 'soon', - 'sorry', - 'sort', - 'soul', - 'sound', - 'soup', - 'source', - 'south', - 'space', - 'spare', - 'spatial', - 'spawn', - 'speak', - 'special', - 'speed', - 'spell', - 'spend', - 'sphere', - 'spice', - 'spider', - 'spike', - 'spin', - 'spirit', - 'split', - 'spoil', - 'sponsor', - 'spoon', - 'sport', - 'spot', - 'spray', - 'spread', - 'spring', - 'spy', - 'square', - 'squeeze', - 'squirrel', - 'stable', - 'stadium', - 'staff', - 'stage', - 'stairs', - 'stamp', - 'stand', - 'start', - 'state', - 'stay', - 'steak', - 'steel', - 'stem', - 'step', - 'stereo', - 'stick', - 'still', - 'sting', - 'stock', - 'stomach', - 'stone', - 'stool', - 'story', - 'stove', - 'strategy', - 'street', - 'strike', - 'strong', - 'struggle', - 'student', - 'stuff', - 'stumble', - 'style', - 'subject', - 'submit', - 'subway', - 'success', - 'such', - 'sudden', - 'suffer', - 'sugar', - 'suggest', - 'suit', - 'summer', - 'sun', - 'sunny', - 'sunset', - 'super', - 'supply', - 'supreme', - 'sure', - 'surface', - 'surge', - 'surprise', - 'surround', - 'survey', - 'suspect', - 'sustain', - 'swallow', - 'swamp', - 'swap', - 'swarm', - 'swear', - 'sweet', - 'swift', - 'swim', - 'swing', - 'switch', - 'sword', - 'symbol', - 'symptom', - 'syrup', - 'system', - 'table', - 'tackle', - 'tag', - 'tail', - 'talent', - 'talk', - 'tank', - 'tape', - 'target', - 'task', - 'taste', - 'tattoo', - 'taxi', - 'teach', - 'team', - 'tell', - 'ten', - 'tenant', - 'tennis', - 'tent', - 'term', - 'test', - 'text', - 'thank', - 'that', - 'theme', - 'then', - 'theory', - 'there', - 'they', - 'thing', - 'this', - 'thought', - 'three', - 'thrive', - 'throw', - 'thumb', - 'thunder', - 'ticket', - 'tide', - 'tiger', - 'tilt', - 'timber', - 'time', - 'tiny', - 'tip', - 'tired', - 'tissue', - 'title', - 'toast', - 'tobacco', - 'today', - 'toddler', - 'toe', - 'together', - 'toilet', - 'token', - 'tomato', - 'tomorrow', - 'tone', - 'tongue', - 'tonight', - 'tool', - 'tooth', - 'top', - 'topic', - 'topple', - 'torch', - 'tornado', - 'tortoise', - 'toss', - 'total', - 'tourist', - 'toward', - 'tower', - 'town', - 'toy', - 'track', - 'trade', - 'traffic', - 'tragic', - 'train', - 'transfer', - 'trap', - 'trash', - 'travel', - 'tray', - 'treat', - 'tree', - 'trend', - 'trial', - 'tribe', - 'trick', - 'trigger', - 'trim', - 'trip', - 'trophy', - 'trouble', - 'truck', - 'true', - 'truly', - 'trumpet', - 'trust', - 'truth', - 'try', - 'tube', - 'tuition', - 'tumble', - 'tuna', - 'tunnel', - 'turkey', - 'turn', - 'turtle', - 'twelve', - 'twenty', - 'twice', - 'twin', - 'twist', - 'two', - 'type', - 'typical', - 'ugly', - 'umbrella', - 'unable', - 'unaware', - 'uncle', - 'uncover', - 'under', - 'undo', - 'unfair', - 'unfold', - 'unhappy', - 'uniform', - 'unique', - 'unit', - 'universe', - 'unknown', - 'unlock', - 'until', - 'unusual', - 'unveil', - 'update', - 'upgrade', - 'uphold', - 'upon', - 'upper', - 'upset', - 'urban', - 'urge', - 'usage', - 'use', - 'used', - 'useful', - 'useless', - 'usual', - 'utility', - 'vacant', - 'vacuum', - 'vague', - 'valid', - 'valley', - 'valve', - 'van', - 'vanish', - 'vapor', - 'various', - 'vast', - 'vault', - 'vehicle', - 'velvet', - 'vendor', - 'venture', - 'venue', - 'verb', - 'verify', - 'version', - 'very', - 'vessel', - 'veteran', - 'viable', - 'vibrant', - 'vicious', - 'victory', - 'video', - 'view', - 'village', - 'vintage', - 'violin', - 'virtual', - 'virus', - 'visa', - 'visit', - 'visual', - 'vital', - 'vivid', - 'vocal', - 'voice', - 'void', - 'volcano', - 'volume', - 'vote', - 'voyage', - 'wage', - 'wagon', - 'wait', - 'walk', - 'wall', - 'walnut', - 'want', - 'warfare', - 'warm', - 'warrior', - 'wash', - 'wasp', - 'waste', - 'water', - 'wave', - 'way', - 'wealth', - 'weapon', - 'wear', - 'weasel', - 'weather', - 'web', - 'wedding', - 'weekend', - 'weird', - 'welcome', - 'west', - 'wet', - 'whale', - 'what', - 'wheat', - 'wheel', - 'when', - 'where', - 'whip', - 'whisper', - 'wide', - 'width', - 'wife', - 'wild', - 'will', - 'win', - 'window', - 'wine', - 'wing', - 'wink', - 'winner', - 'winter', - 'wire', - 'wisdom', - 'wise', - 'wish', - 'witness', - 'wolf', - 'woman', - 'wonder', - 'wood', - 'wool', - 'word', - 'work', - 'world', - 'worry', - 'worth', - 'wrap', - 'wreck', - 'wrestle', - 'wrist', - 'write', - 'wrong', - 'yard', - 'year', - 'yellow', - 'you', - 'young', - 'youth', - 'zebra', - 'zero', - 'zone', - 'zoo', -] diff --git a/packages/neuron-wallet/src/models/subjects/command.ts b/packages/neuron-wallet/src/models/subjects/command.ts index f840618928..75f1a52fd6 100644 --- a/packages/neuron-wallet/src/models/subjects/command.ts +++ b/packages/neuron-wallet/src/models/subjects/command.ts @@ -10,9 +10,9 @@ const CommandSubject = new Subject<{ | 'import-xpubkey' | 'import-hardware' | 'load-transaction-json' - | 'migrate-acp' | 'sign-verify' | 'multisig-address' + | 'lock-window' payload: string | null dispatchToUI: boolean }>() diff --git a/packages/neuron-wallet/src/models/synced-block-number.ts b/packages/neuron-wallet/src/models/synced-block-number.ts index 35f98e9e0a..b76e9b92b0 100644 --- a/packages/neuron-wallet/src/models/synced-block-number.ts +++ b/packages/neuron-wallet/src/models/synced-block-number.ts @@ -28,7 +28,7 @@ export default class SyncedBlockNumber { if (!this.#blockNumberEntity) { let blockNumber = await getConnection() .getRepository(SyncInfoEntity) - .findOne({ name: SyncInfoEntity.CURRENT_BLOCK_NUMBER }) + .findOneBy({ name: SyncInfoEntity.CURRENT_BLOCK_NUMBER }) if (!blockNumber) { blockNumber = new SyncInfoEntity() diff --git a/packages/neuron-wallet/src/services/addresses.ts b/packages/neuron-wallet/src/services/addresses.ts index 91647a04e7..6f7f75b98c 100644 --- a/packages/neuron-wallet/src/services/addresses.ts +++ b/packages/neuron-wallet/src/services/addresses.ts @@ -1,5 +1,5 @@ -import { AccountExtendedPublicKey, DefaultAddressNumber } from '../models/keys/key' -import Address, { AddressType, publicKeyToAddress } from '../models/keys/address' +import { AddressType, AccountExtendedPublicKey } from '@ckb-lumos/hd' +import { publicKeyToAddress, DefaultAddressNumber } from '../utils/scriptAndAddress' import { Address as AddressInterface } from '../models/address' import AddressCreatedSubject from '../models/subjects/address-created-subject' import NetworksService from '../services/networks' @@ -95,20 +95,17 @@ export default class AddressService { receivingAddressCount: number = DefaultAddressNumber.Receiving, changeAddressCount: number = DefaultAddressNumber.Change ): Promise { - const [unusedReceivingAddresses, unusedChangeAddresses] = await this.getGroupedUnusedAddressesByWalletId(walletId) - const unusedReceivingCount = unusedReceivingAddresses.length - const unusedChangeCount = unusedChangeAddresses.length - if (unusedReceivingCount > this.minUnusedAddressCount && unusedChangeCount > this.minUnusedAddressCount) { - return undefined - } + const [receivingCount, changeCount] = await this.getAddressCountsToFillGapLimit( + walletId, + receivingAddressCount, + changeAddressCount + ) + if (!receivingCount && !changeCount) return undefined const maxReceivingAddressIndex = await this.maxAddressIndex(walletId, AddressType.Receiving) const maxChangeAddressIndex = await this.maxAddressIndex(walletId, AddressType.Change) const nextReceivingIndex = maxReceivingAddressIndex === undefined ? 0 : maxReceivingAddressIndex + 1 const nextChangeIndex = maxChangeAddressIndex === undefined ? 0 : maxChangeAddressIndex + 1 - const receivingCount: number = unusedReceivingCount > this.minUnusedAddressCount ? 0 : receivingAddressCount - const changeCount: number = unusedChangeCount > this.minUnusedAddressCount ? 0 : changeAddressCount - const currentGeneratedAddresses = await this.generateAndSave( walletId, extendedKey, @@ -140,6 +137,20 @@ export default class AddressService { return allGeneratedAddresses } + public static async getAddressCountsToFillGapLimit( + walletId: string, + receivingAddressCount: number = DefaultAddressNumber.Receiving, + changeAddressCount: number = DefaultAddressNumber.Change + ) { + const [unusedReceivingAddresses, unusedChangeAddresses] = await this.getGroupedUnusedAddressesByWalletId(walletId) + const unusedReceivingCount = unusedReceivingAddresses.length + const unusedChangeCount = unusedChangeAddresses.length + return [ + unusedReceivingCount > this.minUnusedAddressCount ? 0 : receivingAddressCount, + unusedChangeCount > this.minUnusedAddressCount ? 0 : changeAddressCount, + ] + } + public static async generateAndSaveForExtendedKey({ walletId, extendedKey, @@ -249,25 +260,21 @@ export default class AddressService { } private static toAddress = (addressMetaInfo: AddressMetaInfo): AddressInterface => { - const path: string = Address.pathFor(addressMetaInfo.addressType, addressMetaInfo.addressIndex) - const address: string = addressMetaInfo.accountExtendedPublicKey.address( + const info = addressMetaInfo.accountExtendedPublicKey.publicKeyInfo( addressMetaInfo.addressType, - addressMetaInfo.addressIndex, - NetworksService.getInstance().isMainnet() - ).address - - const blake160: string = AddressParser.toBlake160(address) + addressMetaInfo.addressIndex + ) - const addressInfo: AddressInterface = { + const address: AddressInterface = { walletId: addressMetaInfo.walletId, - address, - path, + address: publicKeyToAddress(info.publicKey, NetworksService.getInstance().isMainnet()), + path: info.path, addressType: addressMetaInfo.addressType, addressIndex: addressMetaInfo.addressIndex, - blake160, + blake160: info.blake160, } - return addressInfo + return address } private static async maxAddressIndex(walletId: string, addressType: AddressType): Promise { diff --git a/packages/neuron-wallet/src/services/amend-transaction.ts b/packages/neuron-wallet/src/services/amend-transaction.ts index 9933787483..b3eb96f545 100644 --- a/packages/neuron-wallet/src/services/amend-transaction.ts +++ b/packages/neuron-wallet/src/services/amend-transaction.ts @@ -3,7 +3,7 @@ import AmendTransactionEntity from '../database/chain/entities/amend-transaction export default class AmendTransactionService { static async save(hash: string, amendHash: string) { - const exist = await getConnection().getRepository(AmendTransactionEntity).findOne({ + const exist = await getConnection().getRepository(AmendTransactionEntity).findOneBy({ hash, amendHash, }) diff --git a/packages/neuron-wallet/src/services/asset-account-service.ts b/packages/neuron-wallet/src/services/asset-account-service.ts index 7dc8f4689c..991338a744 100644 --- a/packages/neuron-wallet/src/services/asset-account-service.ts +++ b/packages/neuron-wallet/src/services/asset-account-service.ts @@ -1,4 +1,4 @@ -import { In } from 'typeorm' +import { In, IsNull } from 'typeorm' import { getConnection } from '../database/chain/connection' import BufferUtils from '../utils/buffer' import OutputEntity from '../database/chain/entities/output' @@ -22,19 +22,13 @@ export default class AssetAccountService { private static async getACPCells(publicKeyHash: string, tokenId: string = 'CKBytes') { const assetAccountInfo = new AssetAccountInfo() const anyoneCanPayLockHash = assetAccountInfo.generateAnyoneCanPayScript(publicKeyHash).computeHash() - let typeHash = null - if (tokenId !== 'CKBytes') { - typeHash = assetAccountInfo.generateSudtScript(tokenId).computeHash() - } const outputs = await getConnection() .getRepository(OutputEntity) - .createQueryBuilder('output') - .where({ + .findBy({ status: In([OutputStatus.Live, OutputStatus.Sent]), lockHash: anyoneCanPayLockHash, - typeHash, + typeHash: tokenId !== 'CKBytes' ? assetAccountInfo.generateSudtScript(tokenId).computeHash() : IsNull(), }) - .getMany() return outputs } diff --git a/packages/neuron-wallet/src/services/cells.ts b/packages/neuron-wallet/src/services/cells.ts index 82972a2d65..03e8cc0a1f 100644 --- a/packages/neuron-wallet/src/services/cells.ts +++ b/packages/neuron-wallet/src/services/cells.ts @@ -506,7 +506,7 @@ export default class CellsService { } public static getLiveCell = async (outPoint: OutPoint): Promise => { - const cellEntity: OutputEntity | undefined = await CellsService.getLiveCellEntity(outPoint) + const cellEntity = await CellsService.getLiveCellEntity(outPoint) if (!cellEntity) { return undefined @@ -515,8 +515,8 @@ export default class CellsService { return cellEntity.toModel() } - private static getLiveCellEntity = async (outPoint: OutPoint): Promise => { - const cellEntity: OutputEntity | undefined = await getConnection().getRepository(OutputEntity).findOne({ + private static getLiveCellEntity = async (outPoint: OutPoint): Promise => { + const cellEntity = await getConnection().getRepository(OutputEntity).findOneBy({ outPointTxHash: outPoint.txHash, outPointIndex: outPoint.index, status: 'live', diff --git a/packages/neuron-wallet/src/services/ckb-runner.ts b/packages/neuron-wallet/src/services/ckb-runner.ts index aeb1128d4c..003800312d 100644 --- a/packages/neuron-wallet/src/services/ckb-runner.ts +++ b/packages/neuron-wallet/src/services/ckb-runner.ts @@ -26,6 +26,11 @@ const platform = (): string => { } } +enum NeedMigrateMsg { + Wants = 'CKB wants to migrate the data into new format', + Recommends = 'CKB recommends migrating your data into a new format', +} + const { app } = env let ckb: ChildProcess | null = null @@ -124,7 +129,7 @@ export const startCkbNode = async () => { currentProcess.stderr?.on('data', data => { const dataString: string = data.toString() logger.error('CKB:\trun fail:', dataString) - if (dataString.includes('CKB wants to migrate the data into new format')) { + if (dataString.includes(NeedMigrateMsg.Wants) || dataString.includes(NeedMigrateMsg.Recommends)) { MigrateSubject.next({ type: 'need-migrate' }) } }) diff --git a/packages/neuron-wallet/src/services/hardware/common.ts b/packages/neuron-wallet/src/services/hardware/common.ts index 2cf52ae82c..67bde2b3d6 100644 --- a/packages/neuron-wallet/src/services/hardware/common.ts +++ b/packages/neuron-wallet/src/services/hardware/common.ts @@ -1,4 +1,4 @@ -import { AddressType } from '../../models/keys/address' +import { AddressType } from '@ckb-lumos/hd' export enum Manufacturer { Ledger = 'Ledger', diff --git a/packages/neuron-wallet/src/services/hardware/hardware.ts b/packages/neuron-wallet/src/services/hardware/hardware.ts index 20af74e74c..9d28825752 100644 --- a/packages/neuron-wallet/src/services/hardware/hardware.ts +++ b/packages/neuron-wallet/src/services/hardware/hardware.ts @@ -7,7 +7,7 @@ import Multisig from '../../models/multisig' import WalletService from '../../services/wallets' import DeviceSignIndexSubject from '../../models/subjects/device-sign-index-subject' import type { DeviceInfo, ExtendedPublicKey, PublicKey } from './common' -import { AccountExtendedPublicKey } from '../../models/keys/key' +import { AccountExtendedPublicKey } from '@ckb-lumos/hd' import AssetAccountInfo from '../../models/asset-account-info' export abstract class Hardware { diff --git a/packages/neuron-wallet/src/services/hardware/ledger.ts b/packages/neuron-wallet/src/services/hardware/ledger.ts index 1a41162df1..a3f6fca5eb 100644 --- a/packages/neuron-wallet/src/services/hardware/ledger.ts +++ b/packages/neuron-wallet/src/services/hardware/ledger.ts @@ -7,7 +7,7 @@ import type Transport from '@ledgerhq/hw-transport' import { Observable, timer } from 'rxjs' import { takeUntil, filter, scan } from 'rxjs/operators' import Transaction from '../../models/chain/transaction' -import Address, { AddressType } from '../../models/keys/address' +import { AddressType, AccountExtendedPublicKey } from '@ckb-lumos/hd' import logger from '../../utils/logger' import NetworksService from '../../services/networks' import { generateRPC } from '../../utils/ckb-rpc' @@ -64,7 +64,7 @@ export default class Ledger extends Hardware { } const signature = await this.ledgerCKB!.signTransaction( - path === Address.pathForReceiving(0) ? this.defaultPath : path, + path === AccountExtendedPublicKey.pathForReceiving(0) ? this.defaultPath : path, rawTx, witnesses, context, @@ -77,7 +77,7 @@ export default class Ledger extends Hardware { async signMessage(path: string, messageHex: string) { const message = this.removePrefix(messageHex) const signed = await this.ledgerCKB!.signMessage( - path === Address.pathForReceiving(0) ? this.defaultPath : path, + path === AccountExtendedPublicKey.pathForReceiving(0) ? this.defaultPath : path, message, false ) @@ -103,7 +103,7 @@ export default class Ledger extends Hardware { const networkService = NetworksService.getInstance() const isTestnet = !networkService.isMainnet() const result = await this.ledgerCKB!.getWalletPublicKey( - path === Address.pathForReceiving(0) ? this.defaultPath : path, + path === AccountExtendedPublicKey.pathForReceiving(0) ? this.defaultPath : path, isTestnet ) return result diff --git a/packages/neuron-wallet/src/services/settings.ts b/packages/neuron-wallet/src/services/settings.ts index 55dd2a3e16..f836ba5e0e 100644 --- a/packages/neuron-wallet/src/services/settings.ts +++ b/packages/neuron-wallet/src/services/settings.ts @@ -1,5 +1,6 @@ -import { BrowserWindow, nativeTheme } from 'electron' +import { BrowserWindow, nativeTheme, safeStorage } from 'electron' import fs from 'node:fs' +import crypto from 'node:crypto' import env from '../env' import Store from '../models/store' import { changeLanguage } from '../locales/i18n' @@ -15,6 +16,7 @@ export type Locale = (typeof locales)[number] const settingKeys = { ckbDataPath: 'ckbDataPath', nodeDataPath: 'nodeDataPath', + lockWindow: 'lockWindow', } export default class SettingsService extends Store { @@ -80,6 +82,39 @@ export default class SettingsService extends Store { this.writeSync('themeSource', theme) } + get lockWindowInfo(): { locked: boolean; encryptedPassword?: string } { + return this.readSync(settingKeys.lockWindow) ?? { locked: false } + } + + private generateEncryptString(str: string) { + if (safeStorage.isEncryptionAvailable()) { + return safeStorage.encryptString(str).toString('hex') + } + const hash = crypto.createHash('sha256') + hash.update(str) + return hash.digest('hex') + } + + updateLockWindowInfo(params: { locked?: boolean; password?: string }) { + const oldValue = SettingsService.getInstance().lockWindowInfo + const updatedValue = { + locked: params.locked ?? oldValue.locked, + encryptedPassword: oldValue.encryptedPassword, + } + if (params.password) { + updatedValue.encryptedPassword = this.generateEncryptString(params.password) + } + this.writeSync(settingKeys.lockWindow, updatedValue) + } + + verifyLockWindowPassword(password: string) { + if (safeStorage.isEncryptionAvailable()) { + return safeStorage.decryptString(Buffer.from(this.lockWindowInfo.encryptedPassword!, 'hex')) === password + } + const encryptedPassword = this.generateEncryptString(password) + return SettingsService.getInstance().lockWindowInfo.encryptedPassword === encryptedPassword + } + constructor() { super( '', @@ -88,6 +123,9 @@ export default class SettingsService extends Store { locale: app.getLocale(), ckbDataPath: path.resolve(app.getPath('userData'), 'chains/mainnet'), isFirstSync: true, + [settingKeys.lockWindow]: { + locked: false, + }, }) ) if (!this.getNodeDataPath(LIGHT_CLIENT_MAINNET) || !this.getNodeDataPath('ckb')) { @@ -100,7 +138,7 @@ export default class SettingsService extends Store { private onLocaleChanged = (lng: Locale) => { BrowserWindow.getAllWindows().forEach(bw => bw.webContents.send('set-locale', lng)) - updateApplicationMenu(null) + updateApplicationMenu(BrowserWindow.getFocusedWindow()) } migrateDataPath() { diff --git a/packages/neuron-wallet/src/services/sign-message.ts b/packages/neuron-wallet/src/services/sign-message.ts index 96787d0ef2..aeaf32dca7 100644 --- a/packages/neuron-wallet/src/services/sign-message.ts +++ b/packages/neuron-wallet/src/services/sign-message.ts @@ -1,12 +1,12 @@ import AddressService from './addresses' import WalletService, { Wallet } from './wallets' -import Keychain from '../models/keys/keychain' import Blake2b from '../models/blake2b' -import hd from '@ckb-lumos/hd' +import { key, Keychain } from '@ckb-lumos/hd' import { ec as EC } from 'elliptic' import { AddressNotFound } from '../exceptions' import HardwareWalletService from './hardware' import AddressParser from '../models/address-parser' +import { bytes } from '@ckb-lumos/codec' export default class SignMessage { static GENERATE_COUNT = 100 @@ -40,7 +40,7 @@ export default class SignMessage { private static signByPrivateKey(privateKey: string, message: string): string { const digest = SignMessage.signatureHash(message) - const signature = hd.key.signRecoverable(digest, privateKey) + const signature = key.signRecoverable(digest, privateKey) return signature } @@ -92,8 +92,8 @@ export default class SignMessage { private static getPrivateKey(wallet: Wallet, path: string, password: string): string { const masterPrivateKey = wallet.loadKeystore().extendedPrivateKey(password) const masterKeychain = new Keychain( - Buffer.from(masterPrivateKey.privateKey, 'hex'), - Buffer.from(masterPrivateKey.chainCode, 'hex') + Buffer.from(bytes.bytify(masterPrivateKey.privateKey)), + Buffer.from(bytes.bytify(masterPrivateKey.chainCode)) ) return `0x${masterKeychain.derivePath(path).privateKey.toString('hex')}` diff --git a/packages/neuron-wallet/src/services/sudt-token-info.ts b/packages/neuron-wallet/src/services/sudt-token-info.ts index c24f06ad79..7195f2cdb2 100644 --- a/packages/neuron-wallet/src/services/sudt-token-info.ts +++ b/packages/neuron-wallet/src/services/sudt-token-info.ts @@ -41,7 +41,7 @@ export default class SudtTokenInfoService { .execute() } - static getSudtTokenInfo(typeArgs: string): Promise { + static getSudtTokenInfo(typeArgs: string): Promise { return getConnection() .getRepository(SudtTokenInfoEntity) .createQueryBuilder('info') diff --git a/packages/neuron-wallet/src/services/sync-progress.ts b/packages/neuron-wallet/src/services/sync-progress.ts index 47362710a4..54839896f2 100644 --- a/packages/neuron-wallet/src/services/sync-progress.ts +++ b/packages/neuron-wallet/src/services/sync-progress.ts @@ -68,6 +68,11 @@ export default class SyncProgressService { return res } + static async getExistingSyncArgses() { + const syncProgresses = await getConnection().getRepository(SyncProgress).createQueryBuilder().getMany() + return new Set(syncProgresses.map(v => v.args)) + } + static async getAllSyncStatusToMap() { const result: Map = new Map() const syncProgresses = await getConnection() @@ -111,7 +116,7 @@ export default class SyncProgressService { } static async getOtherTypeSyncBlockNumber() { - const items = await getConnection().getRepository(SyncProgress).find({ + const items = await getConnection().getRepository(SyncProgress).findBy({ addressType: SyncAddressType.Multisig, }) return items.reduce>((pre, cur) => ({ ...pre, [cur.hash]: cur.localSavedBlockNumber }), {}) @@ -132,4 +137,11 @@ export default class SyncProgressService { .set({ localSavedBlockNumber: 0, syncedBlockNumber: 0 }) .execute() } + + static async deleteWalletSyncProgress(walletId: string) { + await getConnection().getRepository(SyncProgress).delete({ + walletId, + addressType: SyncAddressType.Default, + }) + } } diff --git a/packages/neuron-wallet/src/services/transaction-sender.ts b/packages/neuron-wallet/src/services/transaction-sender.ts index 139e6f8179..a0681e4673 100644 --- a/packages/neuron-wallet/src/services/transaction-sender.ts +++ b/packages/neuron-wallet/src/services/transaction-sender.ts @@ -4,12 +4,10 @@ import { TargetOutput, TransactionGenerator, TransactionPersistor } from './tx' import AddressService from './addresses' import WalletService, { Wallet } from '../services/wallets' import RpcService from '../services/rpc-service' -import { PathAndPrivateKey } from '../models/keys/key' import { Address } from '../models/address' import FeeMode from '../models/fee-mode' import TransactionSize from '../models/transaction-size' import TransactionFee from '../models/transaction-fee' -import Keychain from '../models/keys/keychain' import Input from '../models/chain/input' import OutPoint from '../models/chain/out-point' import Output from '../models/chain/output' @@ -20,7 +18,7 @@ import Multisig from '../models/multisig' import Blake2b from '../models/blake2b' import logger from '../utils/logger' import { signWitnesses } from '../utils/signWitnesses' -import { bytes as byteUtils, bytes, number } from '@ckb-lumos/codec' +import { bytes, number } from '@ckb-lumos/codec' import SystemScriptInfo from '../models/system-script-info' import AddressParser from '../models/address-parser' import HardwareWalletService from './hardware' @@ -43,7 +41,7 @@ import { SignStatus } from '../models/offline-sign' import NetworksService from './networks' import { generateRPC } from '../utils/ckb-rpc' import CellsService from './cells' -import hd from '@ckb-lumos/hd' +import { key, Keychain } from '@ckb-lumos/hd' import { getClusterByOutPoint } from '@spore-sdk/core' import CellDep, { DepType } from '../models/chain/cell-dep' import { dao } from '@ckb-lumos/common-scripts' @@ -55,6 +53,11 @@ interface SignInfo { lockArgs: string } +interface PathAndPrivateKey { + path: string + privateKey: string +} + export default class TransactionSender { static MULTI_SIGN_ARGS_LENGTH = 58 @@ -411,7 +414,7 @@ export default class TransactionSender { lock: `0x` + serializedMultiSign.slice(2) + '0'.repeat(130 * m), }) const serializedEmptyWitness = serializeWitnessArgs(emptyWitness.toSDK()) - const serializedEmptyWitnessSize = byteUtils.bytify(serializedEmptyWitness).byteLength + const serializedEmptyWitnessSize = bytes.bytify(serializedEmptyWitness).byteLength const blake2b = new Blake2b() blake2b.update(txHash) blake2b.update(bytes.hexify(number.Uint64LE.pack(`0x${serializedEmptyWitnessSize.toString(16)}`))) @@ -419,7 +422,7 @@ export default class TransactionSender { restWitnesses.forEach(w => { const wit: string = typeof w === 'string' ? w : serializeWitnessArgs(w.toSDK()) - const byteLength = byteUtils.bytify(wit).byteLength + const byteLength = bytes.bytify(wit).byteLength blake2b.update(bytes.hexify(number.Uint64LE.pack(`0x${byteLength.toString(16)}`))) blake2b.update(wit) }) @@ -429,7 +432,7 @@ export default class TransactionSender { if (!wallet.isHardware()) { // `privateKeyOrPath` variable here is a private key because wallet is not a hardware one. Otherwise, it will be a private key path. const privateKey = privateKeyOrPath - emptyWitness.lock = hd.key.signRecoverable(message, privateKey) + emptyWitness.lock = key.signRecoverable(message, privateKey) } return [emptyWitness, ...restWitnesses] @@ -928,14 +931,14 @@ export default class TransactionSender { public getPrivateKeys = (wallet: Wallet, paths: string[], password: string): PathAndPrivateKey[] => { const masterPrivateKey = wallet.loadKeystore().extendedPrivateKey(password) const masterKeychain = new Keychain( - Buffer.from(masterPrivateKey.privateKey, 'hex'), - Buffer.from(masterPrivateKey.chainCode, 'hex') + Buffer.from(bytes.bytify(masterPrivateKey.privateKey)), + Buffer.from(bytes.bytify(masterPrivateKey.chainCode)) ) const uniquePaths = paths.filter((value, idx, a) => a.indexOf(value) === idx) return uniquePaths.map(path => ({ path, - privateKey: `0x${masterKeychain.derivePath(path).privateKey.toString('hex')}`, + privateKey: bytes.hexify(masterKeychain.derivePath(path).privateKey), })) } } diff --git a/packages/neuron-wallet/src/services/tx/transaction-generator.ts b/packages/neuron-wallet/src/services/tx/transaction-generator.ts index 53dccc2047..3164d353cc 100644 --- a/packages/neuron-wallet/src/services/tx/transaction-generator.ts +++ b/packages/neuron-wallet/src/services/tx/transaction-generator.ts @@ -378,6 +378,7 @@ export class TransactionGenerator { tx.outputs[outputs.length - 1].setCapacity((totalCapacity - capacitiesExceptLast - finalFee).toString()) tx.fee = finalFee.toString() tx.size = txSize + tx.hash = tx.computeHash() // check if ( @@ -1456,9 +1457,9 @@ export class TransactionGenerator { const secpCellDep = await SystemScriptInfo.getInstance().getSecpCellDep() const sudtCellDep = assetAccountInfo.sudtCellDep - const anyoneCanPayDep = assetAccountInfo.anyoneCanPayCellDep let outputs: Output[] = [] let acpInputCell: Input | null = null + const acpCodeHashes = new Set([sudtCell.lock.codeHash]) if (acpAddress) { if (!inputSudtCell.type) { throw new MigrateSudtCellNoTypeError() @@ -1473,6 +1474,7 @@ export class TransactionGenerator { const receiverAcpInputAmount = BufferUtils.readBigUInt128LE(receiverAcpCell.data) const sudtCellAmount = BufferUtils.readBigUInt128LE(inputSudtCell.data) const receiverAcpOutputAmount = receiverAcpInputAmount + sudtCellAmount + inputSudtCell.setLock(SystemScriptInfo.generateSecpScript(inputSudtCell.lock.args)) inputSudtCell.setData('0x') inputSudtCell.setType(null) outputs = [ @@ -1494,6 +1496,7 @@ export class TransactionGenerator { since: '0', }) sudtMigrateAcpInputs.push(acpInputCell) + acpCodeHashes.add(receiverAcpCell.lock().codeHash) } else { const addresses = await currentWallet.getNextReceivingAddresses() const usedBlake160s = new Set( @@ -1504,10 +1507,11 @@ export class TransactionGenerator { outputs = [inputSudtCell] } + const acpCellDeps = [...acpCodeHashes].map(v => assetAccountInfo.getAcpCellDep(v)) const tx = Transaction.fromObject({ version: '0', headerDeps: [], - cellDeps: [secpCellDep, sudtCellDep, anyoneCanPayDep], + cellDeps: [secpCellDep, sudtCellDep, ...acpCellDeps.filter((v): v is CellDep => !!v)], inputs: sudtMigrateAcpInputs, outputs: outputs, outputsData: outputs.map(v => v.data || '0x'), diff --git a/packages/neuron-wallet/src/services/tx/transaction-persistor.ts b/packages/neuron-wallet/src/services/tx/transaction-persistor.ts index 7298ded15f..3eeecd3a61 100644 --- a/packages/neuron-wallet/src/services/tx/transaction-persistor.ts +++ b/packages/neuron-wallet/src/services/tx/transaction-persistor.ts @@ -24,9 +24,7 @@ export class TransactionPersistor { // 1. If the tx is not persisted before sending, output = sent, input = pending // 2. If the tx is already persisted before sending, do nothing private static saveWithSent = async (transaction: Transaction): Promise => { - const txEntity: TransactionEntity | undefined = await getConnection() - .getRepository(TransactionEntity) - .findOne(transaction.hash) + const txEntity = await getConnection().getRepository(TransactionEntity).findOneBy({ hash: transaction.hash }) if (txEntity && txEntity.status === TransactionStatus.Failed) { // delete and create a new one (OR just update all status) @@ -51,9 +49,9 @@ export class TransactionPersistor { lockArgsSetNeedsDetail?: Set ): Promise => { const connection = getConnection() - const txEntity: TransactionEntity | undefined = await connection + const txEntity: TransactionEntity | null = await connection .getRepository(TransactionEntity) - .findOne(transaction.hash) + .findOneBy({ hash: transaction.hash }) // update multiSignBlake160 / input.type / input.data / output.data if (txEntity) { @@ -151,7 +149,7 @@ export class TransactionPersistor { const outPoint: OutPoint | null = input.previousOutput() if (outPoint) { - const outputEntity: OutputEntity | undefined = await connection.getRepository(OutputEntity).findOne({ + const outputEntity = await connection.getRepository(OutputEntity).findOneBy({ outPointTxHash: outPoint.txHash, outPointIndex: outPoint.index, }) @@ -257,7 +255,7 @@ export class TransactionPersistor { inputs.push(input) if (outPoint) { - const previousOutput: OutputEntity | undefined = await connection.getRepository(OutputEntity).findOne({ + const previousOutput = await connection.getRepository(OutputEntity).findOneBy({ outPointTxHash: outPoint.txHash, outPointIndex: outPoint.index, }) @@ -271,7 +269,7 @@ export class TransactionPersistor { } const outputsData = transaction.outputsData! - const useTxInputs = await connection.getRepository(InputEntity).find({ outPointTxHash: tx.hash }) + const useTxInputs = await connection.getRepository(InputEntity).findBy({ outPointTxHash: tx.hash }) const useTxIndices = new Set(useTxInputs.map(v => v.outPointIndex)) const outputs: OutputEntity[] = transaction.outputs.map((o, index) => { const output = new OutputEntity() diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts index 6d7306c8d4..025bedd07f 100644 --- a/packages/neuron-wallet/src/services/wallets.ts +++ b/packages/neuron-wallet/src/services/wallets.ts @@ -1,10 +1,10 @@ import { v4 as uuid } from 'uuid' import { WalletNotFound, IsRequired, UsedName, WalletFunctionNotSupported, DuplicateImportWallet } from '../exceptions' import Store from '../models/store' -import Keystore from '../models/keys/keystore' +import { Keystore, AccountExtendedPublicKey } from '@ckb-lumos/hd' import WalletDeletedSubject from '../models/subjects/wallet-deleted-subject' import { WalletListSubject, CurrentWalletSubject } from '../models/subjects/wallets' -import { AccountExtendedPublicKey, DefaultAddressNumber } from '../models/keys/key' +import { DefaultAddressNumber } from '../utils/scriptAndAddress' import { Address as AddressInterface } from '../models/address' import FileService from './file' @@ -16,6 +16,8 @@ import { getConnection } from '../database/chain/connection' import NetworksService from './networks' import { NetworkType } from '../models/network' import { resetSyncTaskQueue } from '../block-sync-renderer' +import SyncProgressService from './sync-progress' +import { prefixWith0x } from '../utils/scriptAndAddress' const fileService = FileService.getInstance() @@ -55,7 +57,7 @@ export abstract class Wallet { this.id = id this.name = name - this.extendedKey = extendedKey + this.extendedKey = prefixWith0x(extendedKey) this.device = device this.isHD = isHDWallet ?? true this.startBlockNumber = startBlockNumber @@ -110,6 +112,10 @@ export abstract class Wallet { } } + public async needsGenerateAddress() { + return false + } + public abstract checkAndGenerateAddresses( isImporting?: boolean, receivingAddressCount?: number, @@ -144,7 +150,7 @@ export class FileKeystoreWallet extends Wallet { } accountExtendedPublicKey = (): AccountExtendedPublicKey => { - return AccountExtendedPublicKey.parse(this.extendedKey) as AccountExtendedPublicKey + return AccountExtendedPublicKey.parse(this.extendedKey) } public toJSON = () => { @@ -179,6 +185,11 @@ export class FileKeystoreWallet extends Wallet { return `${this.id}.json` } + public async needsGenerateAddress() { + const [receiveCount, changeCount] = await AddressService.getAddressCountsToFillGapLimit(this.id) + return receiveCount !== 0 || changeCount !== 0 + } + public checkAndGenerateAddresses = async ( isImporting: boolean = false, receivingAddressCount: number = DefaultAddressNumber.Receiving, @@ -225,7 +236,7 @@ export class HardwareWallet extends Wallet { } accountExtendedPublicKey = (): AccountExtendedPublicKey => { - return AccountExtendedPublicKey.parse(this.extendedKey) as AccountExtendedPublicKey + return AccountExtendedPublicKey.parse(this.extendedKey) } static fromJSON = (json: WalletProperties) => { @@ -365,11 +376,21 @@ export default class WalletService { } } + public async checkNeedGenerateAddress(walletIds: string[]) { + for (const walletId of new Set(walletIds)) { + const wallet = this.get(walletId) + if (await wallet.needsGenerateAddress()) { + return true + } + } + return false + } + public create = (props: WalletProperties) => { if (!props) { throw new IsRequired('wallet property') } - + props = { ...props, extendedKey: prefixWith0x(props.extendedKey) } const index = this.getAll().findIndex(wallet => wallet.name === props.name) if (index !== -1) { @@ -416,6 +437,7 @@ export default class WalletService { this.setCurrent(newWallet.id) await AddressService.deleteByWalletId(existingWalletId) + await SyncProgressService.deleteWalletSyncProgress(existingWalletId) const newWallets = wallets.filter(w => w.id !== existingWalletId) this.listStore.writeSync(this.walletsKey, [...newWallets, newWallet]) @@ -470,6 +492,7 @@ export default class WalletService { } await AddressService.deleteByWalletId(id) + await SyncProgressService.deleteWalletSyncProgress(id) this.listStore.writeSync(this.walletsKey, newWallets) diff --git a/packages/neuron-wallet/src/utils/scriptAndAddress.ts b/packages/neuron-wallet/src/utils/scriptAndAddress.ts index a744ebe29f..a20fb27596 100644 --- a/packages/neuron-wallet/src/utils/scriptAndAddress.ts +++ b/packages/neuron-wallet/src/utils/scriptAndAddress.ts @@ -1,6 +1,26 @@ -import { Script } from '@ckb-lumos/base' +import { key } from '@ckb-lumos/hd' +import { type Script } from '@ckb-lumos/base' import { predefined } from '@ckb-lumos/config-manager' import { encodeToAddress, parseAddress } from '@ckb-lumos/helpers' +import { systemScripts } from './systemScripts' + +export enum DefaultAddressNumber { + Change = 10, + Receiving = 20, +} + +export const prefixWith0x = (hex: string) => (hex.startsWith('0x') ? hex : `0x${hex}`) + +export const publicKeyToAddress = (publicKey: string, isMainnet = false) => { + return scriptToAddress( + { + codeHash: systemScripts.SECP256K1_BLAKE160.CODE_HASH, + hashType: systemScripts.SECP256K1_BLAKE160.HASH_TYPE, + args: key.publicKeyToBlake160(prefixWith0x(publicKey)), + }, + isMainnet + ) +} export const scriptToAddress = (script: CKBComponents.Script, isMainnet = true): string => { const lumosConfig = !isMainnet ? predefined.AGGRON4 : predefined.LINA diff --git a/packages/neuron-wallet/tests/block-sync-renderer/full-synchronizer.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/full-synchronizer.test.ts index 19a6ef6f18..b4f2cd19fd 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/full-synchronizer.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/full-synchronizer.test.ts @@ -1,6 +1,6 @@ import { scriptToAddress } from '../../src/utils/scriptAndAddress' import { when } from 'jest-when' -import { AddressType } from '../../src/models/keys/address' +import { AddressType } from '@ckb-lumos/hd' import { Address, AddressVersion } from '../../src/models/address' import SystemScriptInfo from '../../src/models/system-script-info' import FullSynchronizer from '../../src/block-sync-renderer/sync/full-synchronizer' diff --git a/packages/neuron-wallet/tests/block-sync-renderer/index/resetSyncTask.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/index/resetSyncTask.test.ts index 8cf8258bab..848f155c9a 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/index/resetSyncTask.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/index/resetSyncTask.test.ts @@ -5,7 +5,11 @@ describe(`Reset sync task`, () => { jest.doMock('services/wallets', () => ({ getInstance: () => ({ maintainAddressesIfNecessary: stubbedmaintainAddressesIfNecessary }), })) - jest.doMock('utils/common', () => ({ sleep: stubbedSleep, timeout: stubbedTimeout })) + jest.doMock('utils/common', () => ({ + sleep: stubbedSleep, + timeout: stubbedTimeout, + retry: (_: number, __: number, fn: () => void) => fn(), + })) jest.doMock('services/tx', () => ({ TransactionPersistor: { checkTxLock: jest.fn() } })) const blockSyncRenderer = require('block-sync-renderer') diff --git a/packages/neuron-wallet/tests/block-sync-renderer/indexer-cache-service.intg.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/indexer-cache-service.intg.test.ts index 507934f31e..2ba8f41a08 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/indexer-cache-service.intg.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/indexer-cache-service.intg.test.ts @@ -1,6 +1,6 @@ import { when } from 'jest-when' import AddressMeta from '../../src/database/address/meta' -import { AddressType } from '../../src/models/keys/address' +import { AddressType } from '@ckb-lumos/hd' import { AddressVersion } from '../../src/models/address' import IndexerTxHashCache from '../../src/database/chain/entities/indexer-tx-hash-cache' import RpcService from '../../src/services/rpc-service' diff --git a/packages/neuron-wallet/tests/block-sync-renderer/light-synchronizer.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/light-synchronizer.test.ts index 8e3b83be0c..ccd7ee549e 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/light-synchronizer.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/light-synchronizer.test.ts @@ -12,6 +12,7 @@ const getWalletMinLocalSavedBlockNumberMock = jest.fn() const removeByHashesAndAddressType = jest.fn() const getOtherTypeSyncProgressMock = jest.fn() const getOtherTypeSyncBlockNumberMock = jest.fn() +const getExistingSyncArgsesMock = jest.fn() const setScriptsMock = jest.fn() const getScriptsMock = jest.fn() @@ -61,6 +62,7 @@ jest.mock('../../src/services/sync-progress', () => { static getOtherTypeSyncProgress: any = () => getOtherTypeSyncProgressMock() static getOtherTypeSyncBlockNumber: any = () => getOtherTypeSyncBlockNumberMock() + static getExistingSyncArgses: any = () => getExistingSyncArgsesMock() } }) @@ -134,8 +136,9 @@ describe('test light synchronizer', () => { 'partial' ) }) - it('there is not exist sync scripts with light client', async () => { + it('when syncing script in the local DB', async () => { getScriptsMock.mockResolvedValue([{ script, blockNumber: '0xaa' }]) + getExistingSyncArgsesMock.mockResolvedValue(new Set([script.args])) const addressMeta = AddressMeta.fromObject({ walletId: 'walletId', address, @@ -182,6 +185,67 @@ describe('test light synchronizer', () => { ]) expect(updateSyncProgressFlagMock).toBeCalledWith(['walletId']) }) + it('when syncing script not in the local DB', async () => { + getScriptsMock.mockResolvedValue([{ script, blockNumber: '0xaa' }]) + getExistingSyncArgsesMock.mockResolvedValue(new Set()) + const addressMeta = AddressMeta.fromObject({ + walletId: 'walletId', + address, + path: '', + addressIndex: 10, + addressType: 0, + blake160: script.args, + }) + const connect = new LightSynchronizer([addressMeta], '') + //@ts-ignore + await connect.initSyncProgress() + expect(setScriptsMock).toHaveBeenNthCalledWith( + 1, + [ + { + script: addressMeta.generateDefaultLockScript().toSDK(), + scriptType: 'lock', + walletId: 'walletId', + blockNumber: '0x0', + }, + { + script: addressMeta.generateACPLockScript().toSDK(), + scriptType: 'lock', + walletId: 'walletId', + blockNumber: '0x0', + }, + { + script: addressMeta.generateLegacyACPLockScript().toSDK(), + scriptType: 'lock', + walletId: 'walletId', + blockNumber: '0x0', + }, + ], + 'partial' + ) + expect(setScriptsMock).toHaveBeenLastCalledWith([], 'delete') + expect(initSyncProgressMock).toBeCalledWith([ + { + script: addressMeta.generateDefaultLockScript().toSDK(), + scriptType: 'lock', + walletId: 'walletId', + blockNumber: '0x0', + }, + { + script: addressMeta.generateACPLockScript().toSDK(), + scriptType: 'lock', + walletId: 'walletId', + blockNumber: '0x0', + }, + { + script: addressMeta.generateLegacyACPLockScript().toSDK(), + scriptType: 'lock', + walletId: 'walletId', + blockNumber: '0x0', + }, + ]) + expect(updateSyncProgressFlagMock).toBeCalledWith(['walletId']) + }) it('set new script with the synced min block number', async () => { getScriptsMock.mockResolvedValue([]) const addressMeta = AddressMeta.fromObject({ @@ -262,6 +326,47 @@ describe('test light synchronizer', () => { ) expect(setScriptsMock).toHaveBeenLastCalledWith([], 'delete') }) + it('when set the wallet start block number is bigger than current synced block number', async () => { + getScriptsMock.mockResolvedValue([{ script, blockNumber: '0xaa' }]) + const addressMeta = AddressMeta.fromObject({ + walletId: 'walletId', + address, + path: '', + addressIndex: 10, + addressType: 0, + blake160: script.args, + }) + getWalletMinLocalSavedBlockNumberMock.mockResolvedValue({}) + walletGetAllMock.mockReturnValue([{ id: 'walletId', startBlockNumber: '0xffff' }]) + const connect = new LightSynchronizer([addressMeta], '') + //@ts-ignore + await connect.initSyncProgress() + expect(setScriptsMock).toHaveBeenNthCalledWith( + 1, + [ + { + script: addressMeta.generateDefaultLockScript().toSDK(), + scriptType: 'lock', + walletId: 'walletId', + blockNumber: '0xffff', + }, + { + script: addressMeta.generateACPLockScript().toSDK(), + scriptType: 'lock', + walletId: 'walletId', + blockNumber: '0xffff', + }, + { + script: addressMeta.generateLegacyACPLockScript().toSDK(), + scriptType: 'lock', + walletId: 'walletId', + blockNumber: '0xffff', + }, + ], + 'partial' + ) + expect(setScriptsMock).toHaveBeenLastCalledWith([], 'delete') + }) }) describe('test initSync', () => { diff --git a/packages/neuron-wallet/tests/block-sync-renderer/queue.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/queue.test.ts index d181a4ae5c..52051e88e8 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/queue.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/queue.test.ts @@ -2,7 +2,7 @@ import '../../src/types/ckbComponents.d.ts' import { Subject } from 'rxjs' import { Tip } from '@ckb-lumos/base' import { scriptToAddress } from '../../src/utils/scriptAndAddress' -import { AddressType } from '../../src/models/keys/address' +import { AddressType } from '@ckb-lumos/hd' import SystemScriptInfo from '../../src/models/system-script-info' import { Address, AddressVersion } from '../../src/models/address' import Queue from '../../src/block-sync-renderer/sync/queue' @@ -183,6 +183,15 @@ describe('queue', () => { }, } }) + jest.doMock('../../src/services/wallets', () => { + return { + getInstance() { + return { + checkNeedGenerateAddress: jest.fn(), + } + }, + } + }) const Queue = require('../../src/block-sync-renderer/sync/queue').default queue = new Queue(fakeNodeUrl, addresses) }) diff --git a/packages/neuron-wallet/tests/block-sync-renderer/synchronizer.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/synchronizer.test.ts index 95df115186..c3c1e7e325 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/synchronizer.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/synchronizer.test.ts @@ -1,5 +1,5 @@ import { scriptToAddress } from '../../src/utils/scriptAndAddress' -import { AddressType } from '../../src/models/keys/address' +import { AddressType } from '@ckb-lumos/hd' import { Address, AddressVersion } from '../../src/models/address' import SystemScriptInfo from '../../src/models/system-script-info' import { Synchronizer, type LumosCell, type LumosCellQuery } from '../../src/block-sync-renderer/sync/synchronizer' diff --git a/packages/neuron-wallet/tests/controllers/asset-account.test.ts b/packages/neuron-wallet/tests/controllers/asset-account.test.ts index ce4cb37b25..2bba32b61e 100644 --- a/packages/neuron-wallet/tests/controllers/asset-account.test.ts +++ b/packages/neuron-wallet/tests/controllers/asset-account.test.ts @@ -84,78 +84,6 @@ describe('AssetAccountController', () => { AssetAccountController = require('../../src/controllers/asset-account').default assetAccountController = new AssetAccountController() }) - describe('#showACPMigrationDialog', () => { - const mockStates = (syncStatus: any, windowsCount: any, hasFocusedWindow: any, hasTx: any) => { - stubbedGetSyncStatus.mockResolvedValue(syncStatus) - stubbedGetAllWindows.mockReturnValue(Array(windowsCount)) - stubbedGetFocusedWindow.mockReturnValue(hasFocusedWindow ? { id: '1' } : undefined) - stubbedGenerateMigrateLegacyACPTx.mockResolvedValue(hasTx ? {} : null) - } - beforeEach(() => { - stubbedShowMessageBox.mockResolvedValue({ response: 1 }) - }) - describe('when all conditions met to display dialog', () => { - beforeEach(async () => { - mockStates(SyncStatus.SyncCompleted, 1, true, true) - await assetAccountController.showACPMigrationDialog() - }) - it('broadcast migrate-acp command', () => { - expect(stubbedCommandSubjectNext).toHaveBeenCalledWith({ - dispatchToUI: true, - payload: 'w1', - type: 'migrate-acp', - winID: '1', - }) - }) - describe('attempts to open dialog again', () => { - beforeEach(async () => { - stubbedCommandSubjectNext.mockReset() - mockStates(SyncStatus.SyncCompleted, 1, true, true) - await assetAccountController.showACPMigrationDialog() - }) - it('should not broadcast migrate-acp command', () => { - expect(stubbedCommandSubjectNext).not.toHaveBeenCalled() - }) - }) - describe('force to open dialog again', () => { - beforeEach(async () => { - stubbedCommandSubjectNext.mockReset() - mockStates(SyncStatus.SyncCompleted, 1, true, true) - await assetAccountController.showACPMigrationDialog(true) - }) - it('broadcast migrate-acp command', () => { - expect(stubbedCommandSubjectNext).toHaveBeenCalledWith({ - dispatchToUI: true, - payload: 'w1', - type: 'migrate-acp', - winID: '1', - }) - }) - }) - }) - describe('when one of the conditions not met', () => { - ;[ - [SyncStatus.SyncNotStart, 1, true, true], - [SyncStatus.SyncPending, 1, true, true], - [SyncStatus.Syncing, 1, true, true], - [SyncStatus.SyncCompleted, 0, true, true], - [SyncStatus.SyncCompleted, 2, true, true], - [SyncStatus.SyncCompleted, 1, false, true], - [SyncStatus.SyncCompleted, 1, true, false], - ].forEach(([syncStatus, winCount, hasFocusedWindow, hasTx]) => { - describe(`when SyncStatus: ${syncStatus}, winCount: ${winCount}, hasFocusedWindow: ${hasFocusedWindow}, hasTx: ${hasTx}`, () => { - beforeEach(async () => { - assetAccountController = new AssetAccountController() - mockStates(syncStatus, winCount, hasFocusedWindow, hasTx) - await assetAccountController.showACPMigrationDialog() - }) - it('should not display again in the application session', () => { - expect(stubbedCommandSubjectNext).not.toHaveBeenCalled() - }) - }) - }) - }) - }) describe('destroyAssetAccount', () => { const params = { walletID: 'walletID', diff --git a/packages/neuron-wallet/tests/controllers/export-debug.test.ts b/packages/neuron-wallet/tests/controllers/export-debug.test.ts index 5fbe40e45a..9e9f4b797e 100644 --- a/packages/neuron-wallet/tests/controllers/export-debug.test.ts +++ b/packages/neuron-wallet/tests/controllers/export-debug.test.ts @@ -36,7 +36,7 @@ jest.mock('fs', () => { createWriteStream: () => null, readFileSync: () => JSON.stringify({}), writeFileSync: () => jest.fn(), - existsSync: () => jest.fn(), + existsSync: () => jest.fn()(), } }) @@ -88,6 +88,18 @@ jest.mock('../../src/services/light-runner', () => { } }) +jest.mock('../../src/services/wallets', () => { + return { + getInstance() { + return { + getAll() { + return [] + }, + } + }, + } +}) + import { dialog } from 'electron' import logger from '../../src/utils/logger' import ExportDebugController from '../../src/controllers/export-debug' @@ -148,7 +160,7 @@ describe('Test ExportDebugController', () => { expect(showErrorBoxMock).not.toHaveBeenCalled() expect(logger.error).not.toHaveBeenCalled() - const csv = ['walletId,addressType,addressIndex,publicKeyInBlake160\n', '0,0,0,hash1\n', '1,1,1,hash2\n'].join('') + const csv = ['index,addressType,addressIndex,publicKeyInBlake160\n', '0,0,0,hash1\n', '1,1,1,hash2\n'].join('') expect(archiveAppendMock).toHaveBeenCalledWith(csv, expect.objectContaining({ name: 'hd_public_key_info.csv' })) }) }) diff --git a/packages/neuron-wallet/tests/controllers/multisig.test.ts b/packages/neuron-wallet/tests/controllers/multisig.test.ts index 564f1e8fd7..fff2030f95 100644 --- a/packages/neuron-wallet/tests/controllers/multisig.test.ts +++ b/packages/neuron-wallet/tests/controllers/multisig.test.ts @@ -141,6 +141,7 @@ describe('test for multisig controller', () => { blake160s: [], alias: 'string', changed: expect.any(Function), + lastestBlockNumber: '', } await multisigController.saveConfig(params) expect(MultiSigServiceMock.prototype.saveMultisigConfig).toHaveBeenCalledWith(params) diff --git a/packages/neuron-wallet/tests/database/address/meta.test.ts b/packages/neuron-wallet/tests/database/address/meta.test.ts index eb37c459e8..cdc9f5605e 100644 --- a/packages/neuron-wallet/tests/database/address/meta.test.ts +++ b/packages/neuron-wallet/tests/database/address/meta.test.ts @@ -1,5 +1,5 @@ import { Address, AddressVersion } from '../../../src/models/address' -import { AddressType } from '../../../src/models/keys/address' +import { AddressType } from '@ckb-lumos/hd' import AddressMeta from '../../../src/database/address/meta' import Multisig from '../../../src/models/multisig' import AssetAccountInfo from '../../../src/models/asset-account-info' diff --git a/packages/neuron-wallet/tests/mock/hardware.ts b/packages/neuron-wallet/tests/mock/hardware.ts index 67e9c500eb..bc8c04f3b3 100644 --- a/packages/neuron-wallet/tests/mock/hardware.ts +++ b/packages/neuron-wallet/tests/mock/hardware.ts @@ -1,5 +1,5 @@ import { DeviceInfo } from '../../src/services/hardware/common' -import { AddressType } from '../../src/models/keys/address' +import { AddressType } from '@ckb-lumos/hd' import type { Subscriber } from 'rxjs' enum Manufacturer { diff --git a/packages/neuron-wallet/tests/models/asset-account-info.test.ts b/packages/neuron-wallet/tests/models/asset-account-info.test.ts index 9a35c45a2b..cb7d34a570 100644 --- a/packages/neuron-wallet/tests/models/asset-account-info.test.ts +++ b/packages/neuron-wallet/tests/models/asset-account-info.test.ts @@ -2,7 +2,7 @@ import AssetAccountInfo from '../../src/models/asset-account-info' import CellDep, { DepType } from '../../src/models/chain/cell-dep' import OutPoint from '../../src/models/chain/out-point' import { ScriptHashType } from '../../src/models/chain/script' -import { AddressType } from '../../src/models/keys/address' +import { AddressType } from '@ckb-lumos/hd' import AddressMeta from '../../src/database/address/meta' describe('AssetAccountInfo', () => { diff --git a/packages/neuron-wallet/tests/models/keys/address.test.ts b/packages/neuron-wallet/tests/models/keys/address.test.ts deleted file mode 100644 index c0ec834827..0000000000 --- a/packages/neuron-wallet/tests/models/keys/address.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import Address, { AddressType, publicKeyToAddress } from '../../../src/models/keys/address' - -describe('address', () => { - it('path from index', () => { - expect(Address.pathFor(AddressType.Receiving, 0)).toEqual(`m/44'/309'/0'/0/0`) - expect(Address.pathFor(AddressType.Receiving, 1)).toEqual(`m/44'/309'/0'/0/1`) - expect(Address.pathFor(AddressType.Change, 0)).toEqual(`m/44'/309'/0'/1/0`) - expect(Address.pathFor(AddressType.Change, 1)).toEqual(`m/44'/309'/0'/1/1`) - - expect(Address.pathForReceiving(0)).toEqual(`m/44'/309'/0'/0/0`) - expect(Address.pathForReceiving(1)).toEqual(`m/44'/309'/0'/0/1`) - expect(Address.pathForChange(0)).toEqual(`m/44'/309'/0'/1/0`) - expect(Address.pathForChange(1)).toEqual(`m/44'/309'/0'/1/1`) - }) - - it('from public key', () => { - const publicKey = '0x024a501efd328e062c8675f2365970728c859c592beeefd6be8ead3d901330bc01' - const path = `m/44'/309'/0'/0/0` - const address = Address.fromPublicKey(publicKey, `m/44'/309'/0'/0/0`) - expect(address.address).toEqual( - 'ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqfkcv576ccddnn4quf2ga65xee2m26h7nq4sds0r' - ) - expect(address.path).toEqual(path) - }) - - it('Generate testnet address from public key', () => { - const publicKey = '0x024a501efd328e062c8675f2365970728c859c592beeefd6be8ead3d901330bc01' - const address = publicKeyToAddress(publicKey) - expect(address).toEqual( - 'ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqfkcv576ccddnn4quf2ga65xee2m26h7nq4sds0r' - ) - }) - - it('Generate mainnet address from public key', () => { - const publicKey = '0x024a501efd328e062c8675f2365970728c859c592beeefd6be8ead3d901330bc01' - const address = publicKeyToAddress(publicKey, true) - expect(address).toEqual( - 'ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqfkcv576ccddnn4quf2ga65xee2m26h7nqmzxl9m' - ) - }) -}) diff --git a/packages/neuron-wallet/tests/models/keys/hd-public-key-info.test.ts b/packages/neuron-wallet/tests/models/keys/hd-public-key-info.test.ts index 68c397e1bd..a9aee99971 100644 --- a/packages/neuron-wallet/tests/models/keys/hd-public-key-info.test.ts +++ b/packages/neuron-wallet/tests/models/keys/hd-public-key-info.test.ts @@ -1,5 +1,5 @@ import { scriptToAddress } from '../../../src/utils/scriptAndAddress' -import { AddressType } from '../../../src/models/keys/address' +import { AddressType } from '@ckb-lumos/hd' import KeyInfos from '../../setupAndTeardown/public-key-info.fixture' import { systemScripts } from '../../../src/utils/systemScripts' import { NetworkType } from '../../../src/models/network' diff --git a/packages/neuron-wallet/tests/models/keys/key.test.ts b/packages/neuron-wallet/tests/models/keys/key.test.ts deleted file mode 100644 index 5f014a7f2c..0000000000 --- a/packages/neuron-wallet/tests/models/keys/key.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - ExtendedPublicKey, - AccountExtendedPublicKey, - ExtendedPrivateKey, - generateMnemonic, -} from '../../../src/models/keys/key' -import { AddressType } from '../../../src/models/keys/address' - -const fixture = { - privateKey: 'e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35', - publicKey: '0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2', - chainCode: '873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508', -} - -describe('extended public key', () => { - it('serialize and parse', () => { - const extendedKey = new ExtendedPublicKey(fixture.publicKey, fixture.chainCode) - const serialized = extendedKey.serialize() - const parsed = ExtendedPublicKey.parse(serialized) - expect(parsed.publicKey).toEqual(fixture.publicKey) - expect(parsed.chainCode).toEqual(fixture.chainCode) - }) -}) - -describe('account extended public key', () => { - const extendedKey = new AccountExtendedPublicKey( - '03e5b310636a0f6e7dcdfffa98f28d7ed70df858bb47acf13db830bfde3510b3f3', - '37e85a19f54f0a242a35599abac64a71aacc21e3a5860dd024377ffc7e6827d8' - ) - - it('key from extended public key', () => { - // @ts-ignore: Private method - expect(extendedKey.addressPublicKey(AddressType.Receiving, 0)).toEqual( - '0331b3c0225388c5010e3507beb28ecf409c022ef6f358f02b139cbae082f5a2a3' - ) - // @ts-ignore: Private method - expect(extendedKey.addressPublicKey(AddressType.Change, 1)).toEqual( - '0360bf05c11e7b4ac8de58077554e3d777acd64bf4abb9cd947002eb98a4827bba' - ) - }) - - it('serialize and parse', () => { - const serialized = extendedKey.serialize() - const parsed = AccountExtendedPublicKey.parse(serialized) - expect(parsed.publicKey).toEqual(extendedKey.publicKey) - expect(parsed.chainCode).toEqual(extendedKey.chainCode) - }) - - it('derive address', () => { - const receivingAddress = extendedKey.address(AddressType.Receiving, 0) - expect(receivingAddress.path).toEqual(`m/44'/309'/0'/0/0`) - - const changeAddress = extendedKey.address(AddressType.Change, 1) - expect(changeAddress.path).toEqual(`m/44'/309'/0'/1/1`) - }) -}) - -describe('extended private key', () => { - it('serialize and parse', () => { - const extendedKey = new ExtendedPrivateKey(fixture.privateKey, fixture.chainCode) - const serialized = extendedKey.serialize() - const parsed = ExtendedPrivateKey.parse(serialized) - expect(parsed.privateKey).toEqual(fixture.privateKey) - expect(parsed.chainCode).toEqual(fixture.chainCode) - }) - - it('derivative extended public key', () => { - const extendedKey = new ExtendedPrivateKey(fixture.privateKey, fixture.chainCode).toExtendedPublicKey() - expect(extendedKey.publicKey).toEqual(fixture.publicKey) - expect(extendedKey.chainCode).toEqual(fixture.chainCode) - }) -}) - -describe('generate mnemonic', () => { - it('generate 12 words code', () => { - const mnemonic = generateMnemonic() - expect(mnemonic.split(' ').length).toBe(12) - }) -}) diff --git a/packages/neuron-wallet/tests/models/keys/keychain.test.ts b/packages/neuron-wallet/tests/models/keys/keychain.test.ts deleted file mode 100644 index d7b88e3831..0000000000 --- a/packages/neuron-wallet/tests/models/keys/keychain.test.ts +++ /dev/null @@ -1,249 +0,0 @@ -import Keychain, { privateToPublic } from '../../../src/models/keys/keychain' - -// https://en.bitcoin.it/wiki/BIP_0032_TestVectors -describe('BIP32 Keychain tests', () => { - const shortSeed = Buffer.from('000102030405060708090a0b0c0d0e0f', 'hex') - const longSeed = Buffer.from( - 'fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542', - 'hex' - ) - - it('create master keychain from seed', () => { - const master = Keychain.fromSeed(shortSeed) - expect(master.privateKey.toString('hex')).toEqual( - 'e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35' - ) - expect(master.identifier.toString('hex')).toEqual('3442193e1bb70916e914552172cd4e2dbc9df811') - expect(master.fingerprint).toEqual(876747070) - expect(master.chainCode.toString('hex')).toEqual('873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508') - expect(master.index).toEqual(0) - expect(master.depth).toEqual(0) - expect(master.parentFingerprint).toEqual(0) - }) - - it('derive children hardened', () => { - const master = Keychain.fromSeed(shortSeed) - const child = master.deriveChild(0, true) - expect(child.privateKey.toString('hex')).toEqual('edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea') - expect(child.identifier.toString('hex')).toEqual('5c1bd648ed23aa5fd50ba52b2457c11e9e80a6a7') - expect(child.fingerprint).toEqual(1545328200) - expect(child.chainCode.toString('hex')).toEqual('47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141') - expect(child.index).toEqual(0) - expect(child.depth).toEqual(1) - }) - - it('derive path', () => { - const master = Keychain.fromSeed(shortSeed) - expect(master.derivePath(`m/0'`).privateKey.toString('hex')).toEqual( - 'edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea' - ) - - const child = master.derivePath(`m/0'/1/2'`) - expect(child.privateKey.toString('hex')).toEqual('cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca') - expect(child.identifier.toString('hex')).toEqual('ee7ab90cde56a8c0e2bb086ac49748b8db9dce72') - expect(child.fingerprint).toEqual(4001020172) - expect(child.chainCode.toString('hex')).toEqual('04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f') - expect(child.index).toEqual(2) - expect(child.depth).toEqual(3) - }) - - it('create master keychain from long seed', () => { - const master = Keychain.fromSeed(longSeed) - expect(master.privateKey.toString('hex')).toEqual( - '4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e' - ) - expect(master.identifier.toString('hex')).toEqual('bd16bee53961a47d6ad888e29545434a89bdfe95') - expect(master.fingerprint).toEqual(3172384485) - expect(master.chainCode.toString('hex')).toEqual('60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689') - expect(master.index).toEqual(0) - expect(master.depth).toEqual(0) - expect(master.parentFingerprint).toEqual(0) - }) - - it('derive path large index', () => { - const master = Keychain.fromSeed(longSeed) - expect(master.derivePath(`m`).privateKey.toString('hex')).toEqual( - '4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e' - ) - - let child = master.derivePath(`0/2147483647'`) - expect(child.privateKey.toString('hex')).toEqual('877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93') - expect(child.identifier.toString('hex')).toEqual('d8ab493736da02f11ed682f88339e720fb0379d1') - expect(child.fingerprint).toEqual(3635104055) - expect(child.chainCode.toString('hex')).toEqual('be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9') - expect(child.index).toEqual(2147483647) - expect(child.depth).toEqual(2) - - child = child.deriveChild(1, false) - expect(child.privateKey.toString('hex')).toEqual('704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7') - expect(child.identifier.toString('hex')).toEqual('78412e3a2296a40de124307b6485bd19833e2e34') - expect(child.fingerprint).toEqual(2017537594) - expect(child.chainCode.toString('hex')).toEqual('f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb') - expect(child.index).toEqual(1) - expect(child.depth).toEqual(3) - - child = child.deriveChild(2147483646, true) - expect(child.privateKey.toString('hex')).toEqual('f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d') - expect(child.identifier.toString('hex')).toEqual('31a507b815593dfc51ffc7245ae7e5aee304246e') - expect(child.fingerprint).toEqual(832899000) - expect(child.chainCode.toString('hex')).toEqual('637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29') - expect(child.index).toEqual(2147483646) - expect(child.depth).toEqual(4) - }) - - it('derive children no hardened', () => { - const master = Keychain.fromSeed(longSeed) - const child = master.deriveChild(0, false) - expect(child.privateKey.toString('hex')).toEqual('abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e') - expect(child.identifier.toString('hex')).toEqual('5a61ff8eb7aaca3010db97ebda76121610b78096') - expect(child.fingerprint).toEqual(1516371854) - expect(child.chainCode.toString('hex')).toEqual('f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c') - expect(child.index).toEqual(0) - expect(child.depth).toEqual(1) - }) - - it('create child keychain from public key', () => { - const child = Keychain.fromPublicKey( - Buffer.from('0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2', 'hex'), - Buffer.from('04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f', 'hex'), - `m/0'/1/2'` - ) - expect(child.identifier.toString('hex')).toEqual('ee7ab90cde56a8c0e2bb086ac49748b8db9dce72') - expect(child.fingerprint).toEqual(4001020172) - expect(child.index).toEqual(2) - expect(child.depth).toEqual(3) - - const grandchild = child.deriveChild(2, false) - expect(grandchild.publicKey.toString('hex')).toEqual( - '02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29' - ) - expect(grandchild.chainCode.toString('hex')).toEqual( - 'cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd' - ) - expect(grandchild.identifier.toString('hex')).toEqual('d880d7d893848509a62d8fb74e32148dac68412f') - expect(grandchild.fingerprint).toEqual(3632322520) - expect(grandchild.index).toEqual(2) - expect(grandchild.depth).toEqual(4) - }) - - it('derive ckb keys', () => { - const master = Keychain.fromSeed(shortSeed) - const extendedKey = master.derivePath(`m/44'/309'/0'`) - expect(extendedKey.privateKey.toString('hex')).toEqual( - 'bb39d218506b30ca69b0f3112427877d983dd3cd2cabc742ab723e2964d98016' - ) - expect(extendedKey.publicKey.toString('hex')).toEqual( - '03e5b310636a0f6e7dcdfffa98f28d7ed70df858bb47acf13db830bfde3510b3f3' - ) - expect(extendedKey.chainCode.toString('hex')).toEqual( - '37e85a19f54f0a242a35599abac64a71aacc21e3a5860dd024377ffc7e6827d8' - ) - - const addressKey = extendedKey.deriveChild(0, false).deriveChild(0, false) - expect(addressKey.privateKey.toString('hex')).toEqual( - 'fcba4708f1f07ddc00fc77422d7a70c72b3456f5fef3b2f68368cdee4e6fb498' - ) - expect(addressKey.publicKey.toString('hex')).toEqual( - '0331b3c0225388c5010e3507beb28ecf409c022ef6f358f02b139cbae082f5a2a3' - ) - expect(addressKey.chainCode.toString('hex')).toEqual( - 'c4b7aef857b625bbb0497267ed51151d090f81737f4f22a0ac3673483b927090' - ) - }) - - it('derive ckb keys another seed', () => { - const master = Keychain.fromSeed( - // From mnemonic `tank planet champion pottery together intact quick police asset flower sudden question` - Buffer.from( - '1371018cfad5990f5e451bf586d59c3820a8671162d8700533549b0df61a63330e5cd5099a5d3938f833d51e4572104868bfac7cfe5b4063b1509a995652bc08', - 'hex' - ) - ) - expect(master.privateKey.toString('hex')).toEqual( - '37d25afe073a6ba17badc2df8e91fc0de59ed88bcad6b9a0c2210f325fafca61' - ) - - expect(master.derivePath(`m/44'/309'/0'`).privateKey.toString('hex')).toEqual( - '2925f5dfcbee3b6ad29100a37ed36cbe92d51069779cc96164182c779c5dc20e' - ) - - expect(master.derivePath(`m/44'/309'/0'`).deriveChild(0, false).privateKey.toString('hex')).toEqual( - '047fae4f38b3204f93a6b39d6dbcfbf5901f2b09f6afec21cbef6033d01801f1' - ) - - expect(master.derivePath(`m/44'/309'/0'/0`).privateKey.toString('hex')).toEqual( - '047fae4f38b3204f93a6b39d6dbcfbf5901f2b09f6afec21cbef6033d01801f1' - ) - - expect( - master.derivePath(`m/44'/309'/0'`).deriveChild(0, false).deriveChild(0, false).privateKey.toString('hex') - ).toEqual('848422863825f69e66dc7f48a3302459ec845395370c23578817456ad6b04b14') - - expect(master.derivePath(`m/44'/309'/0'/0/0`).privateKey.toString('hex')).toEqual( - '848422863825f69e66dc7f48a3302459ec845395370c23578817456ad6b04b14' - ) - }) - - it('derive ckb keys from master extended key', () => { - const privateKey = Buffer.from('37d25afe073a6ba17badc2df8e91fc0de59ed88bcad6b9a0c2210f325fafca61', 'hex') - const chainCode = Buffer.from('5f772d1e3cfee5821911aefa5e8f79d20d4cf6678378d744efd08b66b2633b80', 'hex') - const master = new Keychain(privateKey, chainCode) - expect(master.publicKey.toString('hex')).toEqual( - '020720a7a11a9ac4f0330e2b9537f594388ea4f1cd660301f40b5a70e0bc231065' - ) - - expect(master.derivePath(`m/44'/309'/0'`).privateKey.toString('hex')).toEqual( - '2925f5dfcbee3b6ad29100a37ed36cbe92d51069779cc96164182c779c5dc20e' - ) - - expect(master.derivePath(`m/44'/309'/0'`).deriveChild(0, false).privateKey.toString('hex')).toEqual( - '047fae4f38b3204f93a6b39d6dbcfbf5901f2b09f6afec21cbef6033d01801f1' - ) - - expect(master.derivePath(`m/44'/309'/0'/0`).privateKey.toString('hex')).toEqual( - '047fae4f38b3204f93a6b39d6dbcfbf5901f2b09f6afec21cbef6033d01801f1' - ) - - expect( - master.derivePath(`m/44'/309'/0'`).deriveChild(0, false).deriveChild(0, false).privateKey.toString('hex') - ).toEqual('848422863825f69e66dc7f48a3302459ec845395370c23578817456ad6b04b14') - - expect(master.derivePath(`m/44'/309'/0'/0/0`).privateKey.toString('hex')).toEqual( - '848422863825f69e66dc7f48a3302459ec845395370c23578817456ad6b04b14' - ) - }) - - it('private key add', () => { - const privateKey = Buffer.from('9e919c96ac5a4caea7ba0ea1f7dd7bca5dca8a11e66ed633690c71e483a6e3c9', 'hex') - const toAdd = Buffer.from('36e92e33659808bf06c3e4302b657f39ca285f6bb5393019bb4e2f7b96e3f914', 'hex') - // @ts-ignore: Private method - const sum = Keychain.privateKeyAdd(privateKey, toAdd) - expect(sum.toString('hex')).toEqual('d57acaca11f2556dae7df2d22342fb0427f2e97d9ba8064d245aa1601a8adcdd') - }) - - it('public key add', () => { - const publicKey = Buffer.from('03556b2c7e03b12845a973a6555b49fe44b0836fbf3587709fa73bb040ba181b21', 'hex') - const toAdd = Buffer.from('953fd6b91b51605d32a28ab478f39ab53c90103b93bd688330b118c460e9c667', 'hex') - // @ts-ignore: Private method - const sum = Keychain.publicKeyAdd(publicKey, toAdd) - expect(sum.toString('hex')).toEqual('03db6eab66f918e434bae0e24fd73de1a2b293a2af9bd3ad53123996fa94494f37') - }) -}) - -describe('private to public', () => { - it('derive public key from private key', () => { - const privateKey = Buffer.from('bb39d218506b30ca69b0f3112427877d983dd3cd2cabc742ab723e2964d98016', 'hex') - const publicKey = Buffer.from('03e5b310636a0f6e7dcdfffa98f28d7ed70df858bb47acf13db830bfde3510b3f3', 'hex') - expect(privateToPublic(privateKey)).toEqual(publicKey) - }) - - it('derive public key from private key wrong length', () => { - expect(() => privateToPublic(Buffer.from(''))).toThrowError() - expect(() => - privateToPublic(Buffer.from('39d218506b30ca69b0f3112427877d983dd3cd2cabc742ab723e2964d98016', 'hex')) - ).toThrowError() - expect(() => - privateToPublic(Buffer.from('0xbb39d218506b30ca69b0f3112427877d983dd3cd2cabc742ab723e2964d98016', 'hex')) - ).toThrowError() - }) -}) diff --git a/packages/neuron-wallet/tests/models/keys/keystore.test.ts b/packages/neuron-wallet/tests/models/keys/keystore.test.ts deleted file mode 100644 index 39b7e0b041..0000000000 --- a/packages/neuron-wallet/tests/models/keys/keystore.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { ExtendedPrivateKey } from '../../../src/models/keys/key' -import Keystore from '../../../src/models/keys/keystore' -import { IncorrectPassword } from '../../../src/exceptions/wallet' - -const fixture = { - privateKey: 'e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35', - publicKey: '0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2', - chainCode: '873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508', -} - -describe('load and check password', () => { - const password = 'hello~!23' - const keystore = Keystore.create(new ExtendedPrivateKey(fixture.privateKey, fixture.chainCode), password) - - it('checks wrong password', () => { - expect(keystore.checkPassword(`oops${password}`)).toBe(false) - }) - - it('checks correct password', () => { - expect(keystore.checkPassword(password)).toBe(true) - }) - - it('decrypts', () => { - expect(keystore.decrypt(password)).toEqual( - new ExtendedPrivateKey(fixture.privateKey, fixture.chainCode).serialize() - ) - }) - - it('loads private key', () => { - const extendedPrivateKey = keystore.extendedPrivateKey(password) - expect(extendedPrivateKey.privateKey).toEqual(fixture.privateKey) - expect(extendedPrivateKey.chainCode).toEqual(fixture.chainCode) - }) -}) - -describe('load ckb cli light keystore', () => { - const password = '123' - const keystoreString = - '{"crypto":{"cipher": "aes-128-ctr", "ciphertext": "253397209cae86474e368720f9baa30f448767047d2cc5a7672ef121861974ed", "cipherparams": {"iv": "8bd8523e0048db3a4ae2534aec6d303a"}, "kdf": "scrypt", "kdfparams": {"dklen": 32, "n": 4096, "p": 6, "r": 8, "salt": "be3d86c99f4895f99d1a0048afb61a34153fa83d5edd033fc914de2c502f57e7"}, "mac": "4453cf5d4f6ec43d0664c3895c4ab9b1c9bcd2d02c7abb190c84375a42739099" },"id": "id", "version": 3}' - const keystore = Keystore.fromJson(keystoreString) - - it('checks correct password', () => { - expect(keystore.checkPassword(password)).toBe(true) - }) -}) - -describe('load ckb cli standard keystore', () => { - const password = '123' - const keystoreString = - '{"address":"ea22142fa5be326e834681144ca30326f99a6d5a","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"29304e5bcbb1885ef5cdcb40b5312b58"},"ciphertext":"93054530a8fbe5b11995acda856585d7362ac7d2b1e4f268c633d997be2d6532c4962501d0835bf52a4693ae7a091ac9bac9297793f4116ef7c123edb00dbc85","kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"724327e67ca321ccf15035bb78a0a05c816bebbe218a0840abdc26da8453c1f4"},"mac":"1d0e5660ffbfc1f9ff4da97aefcfc2153c0ec1b411e35ffee26ee92815cc06f9"},"id":"43c1116e-efd5-4c9e-a86a-3ec0ab163122","version":3}' - const keystore = Keystore.fromJson(keystoreString) - - it('checks correct password', () => { - expect(keystore.checkPassword(password)).toBe(true) - }) - - it('loads private key', () => { - const extendedPrivateKey = keystore.extendedPrivateKey(password) - expect(extendedPrivateKey.privateKey).toEqual('8af124598932440269a81771ad662642e83a38b323b2f70223b8ae0b6c5e0779') - expect(extendedPrivateKey.chainCode).toEqual('615302e2c93151a55c29121dd02ad554e47908a6df6d7374f357092cec11675b') - }) -}) - -describe('load ckb cli origin keystore', () => { - const keystoreString = - '{"origin":"ckb-cli", "address":"ea22142fa5be326e834681144ca30326f99a6d5a","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"29304e5bcbb1885ef5cdcb40b5312b58"},"ciphertext":"93054530a8fbe5b11995acda856585d7362ac7d2b1e4f268c633d997be2d6532c4962501d0835bf52a4693ae7a091ac9bac9297793f4116ef7c123edb00dbc85","kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"724327e67ca321ccf15035bb78a0a05c816bebbe218a0840abdc26da8453c1f4"},"mac":"1d0e5660ffbfc1f9ff4da97aefcfc2153c0ec1b411e35ffee26ee92815cc06f9"},"id":"43c1116e-efd5-4c9e-a86a-3ec0ab163122","version":3}' - - it('does not load', () => { - expect(() => Keystore.fromJson(keystoreString)).toThrowError() - }) -}) - -describe('create empty keystore', () => { - const keystore = Keystore.createEmpty() - - it('has empty ciphertext and mac', () => { - expect(keystore.crypto.ciphertext).toEqual('') - expect(keystore.crypto.mac).toEqual('') - }) - - it("won't verify password", () => { - expect(keystore.checkPassword('')).toBeFalsy() - expect(keystore.checkPassword('anypassword')).toBeFalsy() - }) - - it('cannot decrypt', () => { - expect(() => keystore.decrypt('')).toThrowError(new IncorrectPassword()) - }) -}) diff --git a/packages/neuron-wallet/tests/models/keys/mnemonic/fixtures.json b/packages/neuron-wallet/tests/models/keys/mnemonic/fixtures.json deleted file mode 100644 index 5aec22150c..0000000000 --- a/packages/neuron-wallet/tests/models/keys/mnemonic/fixtures.json +++ /dev/null @@ -1,147 +0,0 @@ -{ - "vectors": [ - { - "entropy": "00000000000000000000000000000000", - "mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", - "seed": "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4" - }, - - { - "entropy": "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", - "mnemonic": "legal winner thank year wave sausage worth useful legal winner thank yellow", - "seed": "878386efb78845b3355bd15ea4d39ef97d179cb712b77d5c12b6be415fffeffe5f377ba02bf3f8544ab800b955e51fbff09828f682052a20faa6addbbddfb096" - }, - - { - "entropy": "80808080808080808080808080808080", - "mnemonic": "letter advice cage absurd amount doctor acoustic avoid letter advice cage above", - "seed": "77d6be9708c8218738934f84bbbb78a2e048ca007746cb764f0673e4b1812d176bbb173e1a291f31cf633f1d0bad7d3cf071c30e98cd0688b5bcce65ecaceb36" - }, - - { - "entropy": "ffffffffffffffffffffffffffffffff", - "mnemonic": "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", - "seed": "b6a6d8921942dd9806607ebc2750416b289adea669198769f2e15ed926c3aa92bf88ece232317b4ea463e84b0fcd3b53577812ee449ccc448eb45e6f544e25b6" - }, - - { - "entropy": "000000000000000000000000000000000000000000000000", - "mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent", - "seed": "4975bb3d1faf5308c86a30893ee903a976296609db223fd717e227da5a813a34dc1428b71c84a787fc51f3b9f9dc28e9459f48c08bd9578e9d1b170f2d7ea506" - }, - - { - "entropy": "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", - "mnemonic": "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will", - "seed": "b059400ce0f55498a5527667e77048bb482ff6daa16c37b4b9e8af70c85b3f4df588004f19812a1a027c9a51e5e94259a560268e91cd10e206451a129826e740" - }, - - { - "entropy": "808080808080808080808080808080808080808080808080", - "mnemonic": "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always", - "seed": "04d5f77103510c41d610f7f5fb3f0badc77c377090815cee808ea5d2f264fdfabf7c7ded4be6d4c6d7cdb021ba4c777b0b7e57ca8aa6de15aeb9905dba674d66" - }, - - { - "entropy": "ffffffffffffffffffffffffffffffffffffffffffffffff", - "mnemonic": "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when", - "seed": "d2911131a6dda23ac4441d1b66e2113ec6324354523acfa20899a2dcb3087849264e91f8ec5d75355f0f617be15369ffa13c3d18c8156b97cd2618ac693f759f" - }, - - { - "entropy": "0000000000000000000000000000000000000000000000000000000000000000", - "mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art", - "seed": "408b285c123836004f4b8842c89324c1f01382450c0d439af345ba7fc49acf705489c6fc77dbd4e3dc1dd8cc6bc9f043db8ada1e243c4a0eafb290d399480840" - }, - - { - "entropy": "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", - "mnemonic": "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title", - "seed": "761914478ebf6fe16185749372e91549361af22b386de46322cf8b1ba7e92e80c4af05196f742be1e63aab603899842ddadf4e7248d8e43870a4b6ff9bf16324" - }, - - { - "entropy": "8080808080808080808080808080808080808080808080808080808080808080", - "mnemonic": "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", - "seed": "848bbe19cad445e46f35fd3d1a89463583ac2b60b5eb4cfcf955731775a5d9e17a81a71613fed83f1ae27b408478fdec2bbc75b5161d1937aa7cdf4ad686ef5f" - }, - - { - "entropy": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "mnemonic": "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote", - "seed": "e28a37058c7f5112ec9e16a3437cf363a2572d70b6ceb3b6965447623d620f14d06bb321a26b33ec15fcd84a3b5ddfd5520e230c924c87aaa0d559749e044fef" - }, - - { - "entropy": "77c2b00716cec7213839159e404db50d", - "mnemonic": "jelly better achieve collect unaware mountain thought cargo oxygen act hood bridge", - "seed": "c7b8fbb38c1abe38dfc0fea9797804558dfac244cd7737ae3a1b619991e0ad520155d982f906629639dc39e440520f98f820bea4f886a63a45923a63441f25ef" - }, - - { - "entropy": "b63a9c59a6e641f288ebc103017f1da9f8290b3da6bdef7b", - "mnemonic": "renew stay biology evidence goat welcome casual join adapt armor shuffle fault little machine walk stumble urge swap", - "seed": "b1a1f06f175feccc998684667474b3d83efa57a0f39bb3a6cf3a3350ee7a6638ae6d15c4622c8252efe5aa319b026db1d4c91a80661ed34da1f2fb7d381224c8" - }, - - { - "entropy": "3e141609b97933b66a060dcddc71fad1d91677db872031e85f4c015c5e7e8982", - "mnemonic": "dignity pass list indicate nasty swamp pool script soccer toe leaf photo multiply desk host tomato cradle drill spread actor shine dismiss champion exotic", - "seed": "ecf9632e864630c00be4ca3d752d4f19a852cd628d9bbc3309a4c1a2f39801461a6816ca52793ddd3dacb242e207ad48e8bfde3afd0e8f978ad0e8cc4dd276c1" - }, - - { - "entropy": "0460ef47585604c5660618db2e6a7e7f", - "mnemonic": "afford alter spike radar gate glance object seek swamp infant panel yellow", - "seed": "3ddfd060236156416f8915ed6ced01c3316292aec7250434f7e32cda2338e76399874787257acad15618c81bcddd88714f8c0d316140dad809f0ca8b1a971679" - }, - - { - "entropy": "72f60ebac5dd8add8d2a25a797102c3ce21bc029c200076f", - "mnemonic": "indicate race push merry suffer human cruise dwarf pole review arch keep canvas theme poem divorce alter left", - "seed": "fe34200c8c3781f81f48d19f628a7370eb25c94c75077c9a6d4a1ef30fd9cc2f29f8ea7ef52bb765c5278413c19b7b2854b62cb3591ce4d749cd7f497da436a6" - }, - - { - "entropy": "2c85efc7f24ee4573d2b81a6ec66cee209b2dcbd09d8eddc51e0215b0b68e416", - "mnemonic": "clutch control vehicle tonight unusual clog visa ice plunge glimpse recipe series open hour vintage deposit universe tip job dress radar refuse motion taste", - "seed": "fa9ca5ef1ebfcb5e945091d413843bf7ce748d27b8b99bb5373d34b9a6b1450d2a2d7f04480904b29c78a41a6ea949288f687f72b5b8e322193a7eae8151f109" - }, - - { - "entropy": "eaebabb2383351fd31d703840b32e9e2", - "mnemonic": "turtle front uncle idea crush write shrug there lottery flower risk shell", - "seed": "4ef6e8484a846392f996b15283906b73be4ec100859ce68689d5a0fad7f761745b86d70ea5f5c43e4cc93ce4b82b3d9aeed7f85d503fac00b10ebbc150399100" - }, - - { - "entropy": "7ac45cfe7722ee6c7ba84fbc2d5bd61b45cb2fe5eb65aa78", - "mnemonic": "kiss carry display unusual confirm curtain upgrade antique rotate hello void custom frequent obey nut hole price segment", - "seed": "f0b24a453174e3c4f27634f3e2be07c069328f7cbaa24f695cbeb79a39e79f05154bddbabec57b832a46813d2e49e7b33f438e79cc566f78a3179dbce86cdd84" - }, - - { - "entropy": "4fa1a8bc3e6d80ee1316050e862c1812031493212b7ec3f3bb1b08f168cabeef", - "mnemonic": "exile ask congress lamp submit jacket era scheme attend cousin alcohol catch course end lucky hurt sentence oven short ball bird grab wing top", - "seed": "8a91a843ad4fede95f23937099a94f117115a369903603761ecabae734b5d501ddba04b1a3c9f2256437ef2d230f295d8f08676e5de93ad5190da6645ded8160" - }, - - { - "entropy": "18ab19a9f54a9274f03e5209a2ac8a91", - "mnemonic": "board flee heavy tunnel powder denial science ski answer betray cargo cat", - "seed": "22087755f76d6fb93ddd19e71106d4d4146f48424a241c0eda88787227827166223f61860d53652b635f360b5a37dd26c8aed3fa10b6f8e95be18f1913f4ca88" - }, - - { - "entropy": "18a2e1d81b8ecfb2a333adcb0c17a5b9eb76cc5d05db91a4", - "mnemonic": "board blade invite damage undo sun mimic interest slam gaze truly inherit resist great inject rocket museum chief", - "seed": "99539dbb0a15a76cdadd9cc066bae337a006823fa3439b42656fd0fca3d48afe6a0ca6f7a1d10412df611c32e18669a29bc0494de61b4c36730a5c31045464e2" - }, - - { - "entropy": "15da872c95a13dd738fbf50e427583ad61f18fd99f628c417a61cf8343c90419", - "mnemonic": "beyond stage sleep clip because twist token leaf atom beauty genius food business side grid unable middle armed observe pair crouch tonight away coconut", - "seed": "898c7388d88e3a5b3b2922a0f03f95c8e61aeadba9fa8a7b0b5629d7c98e1e0aec53f0b10fcbd4a913b4b8c985028b0026ec6fdb0a4442ee18344ca3fac4d692" - } - ] -} diff --git a/packages/neuron-wallet/tests/models/keys/mnemonic/index.test.ts b/packages/neuron-wallet/tests/models/keys/mnemonic/index.test.ts deleted file mode 100644 index 616796cc36..0000000000 --- a/packages/neuron-wallet/tests/models/keys/mnemonic/index.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { - entropyToMnemonic, - mnemonicToEntropy, - mnemonicToSeed, - mnemonicToSeedSync, - validateMnemonic, -} from '../../../../src/models/keys/mnemonic' - -const fixtures = require('./fixtures.json') - -describe('mnemonic', () => { - it('generate, validate mnemonic', () => { - fixtures.vectors.map(async ({ entropy, mnemonic }: { entropy: string; mnemonic: string; seed: string }) => { - expect(validateMnemonic(mnemonic)).toBe(true) - expect(entropyToMnemonic(entropy)).toBe(mnemonic) - expect(mnemonicToEntropy(mnemonic)).toBe(entropy) - }) - }) - - it('generate seed', () => { - fixtures.vectors.map(async ({ mnemonic, seed }: { entropy: string; mnemonic: string; seed: string }) => { - expect(await mnemonicToSeed(mnemonic).then(s => s.toString('hex'))).toBe(seed) - expect(mnemonicToSeedSync(mnemonic).toString('hex')).toBe(seed) - }) - }) -}) diff --git a/packages/neuron-wallet/tests/models/synced-block-number.intg.test.ts b/packages/neuron-wallet/tests/models/synced-block-number.intg.test.ts index c4a7855960..2a8fc4f8ff 100644 --- a/packages/neuron-wallet/tests/models/synced-block-number.intg.test.ts +++ b/packages/neuron-wallet/tests/models/synced-block-number.intg.test.ts @@ -1,5 +1,3 @@ -import { closeConnection, getConnection, initConnection } from '../setupAndTeardown' - const stubbedSyncedBlockNumberSubjectNext = jest.fn() const stubbedLoggerInfo = jest.fn() @@ -14,6 +12,8 @@ jest.mock('utils/logger', () => { } }) +import { closeConnection, getConnection, initConnection } from '../setupAndTeardown' + describe('SyncedBlockNumber model', () => { let SyncedBlockNumber: any beforeAll(async () => { diff --git a/packages/neuron-wallet/tests/services/address.test.ts b/packages/neuron-wallet/tests/services/address.test.ts index 98bbf90d12..c2b09c39e7 100644 --- a/packages/neuron-wallet/tests/services/address.test.ts +++ b/packages/neuron-wallet/tests/services/address.test.ts @@ -1,12 +1,10 @@ -import { AccountExtendedPublicKey } from '../../src/models/keys/key' import SystemScriptInfo from '../../src/models/system-script-info' import { OutputStatus } from '../../src/models/chain/output' import OutputEntity from '../../src/database/chain/entities/output' -import { AddressType } from '../../src/models/keys/address' +import { AddressType, AccountExtendedPublicKey } from '@ckb-lumos/hd' import { Address } from '../../src/models/address' import Transaction from '../../src/database/chain/entities/transaction' import { TransactionStatus } from '../../src/models/chain/transaction' -import AddressParser from '../../src/models/address-parser' import { when } from 'jest-when' import HdPublicKeyInfo from '../../src/database/chain/entities/hd-public-key-info' import { closeConnection, getConnection, initConnection } from '../setupAndTeardown' @@ -14,8 +12,8 @@ import { NetworkType } from '../../src/models/network' const walletId = '1' const extendedKey = new AccountExtendedPublicKey( - '03e5b310636a0f6e7dcdfffa98f28d7ed70df858bb47acf13db830bfde3510b3f3', - '37e85a19f54f0a242a35599abac64a71aacc21e3a5860dd024377ffc7e6827d8' + '0x03e5b310636a0f6e7dcdfffa98f28d7ed70df858bb47acf13db830bfde3510b3f3', + '0x37e85a19f54f0a242a35599abac64a71aacc21e3a5860dd024377ffc7e6827d8' ) const preloadedPublicKeys: any = [] @@ -95,21 +93,21 @@ describe('integration tests for AddressService', () => { beforeAll(() => { for (let addressType = 0; addressType <= 1; addressType++) { for (let addressIndex = 0; addressIndex <= 7; addressIndex++) { - const address = extendedKey.address(addressType, addressIndex, true) + const publicKeyInfo = extendedKey.publicKeyInfo(addressType, addressIndex) preloadedPublicKeys.push({ - address, addressType, addressIndex, - publicKeyHash: AddressParser.toBlake160(address.address), + publicKeyInfo, + publicKeyHash: publicKeyInfo.blake160, }) } } - const stubbedExtendedKeyAddressFn = when(jest.spyOn(extendedKey, 'address')) + const stubbedExtendedPublicKeyInfoFn = when(jest.spyOn(extendedKey, 'publicKeyInfo')) for (const addressToMock of preloadedPublicKeys) { - stubbedExtendedKeyAddressFn - .calledWith(addressToMock.addressType, addressToMock.addressIndex, true) - .mockReturnValue(addressToMock.address) + stubbedExtendedPublicKeyInfoFn + .calledWith(addressToMock.addressType, addressToMock.addressIndex) + .mockReturnValue(addressToMock.publicKeyInfo) } }) @@ -123,7 +121,7 @@ describe('integration tests for AddressService', () => { let generatedAddresses: Address[] beforeAll(async () => { - await initConnection('') + await initConnection() }) afterAll(async () => { diff --git a/packages/neuron-wallet/tests/services/asset-account-service.test.ts b/packages/neuron-wallet/tests/services/asset-account-service.test.ts index 39842abbbe..0e980b8a08 100644 --- a/packages/neuron-wallet/tests/services/asset-account-service.test.ts +++ b/packages/neuron-wallet/tests/services/asset-account-service.test.ts @@ -11,7 +11,7 @@ import { TransactionStatus } from '../../src/models/chain/transaction' import { closeConnection, createAccounts, getConnection, initConnection } from '../setupAndTeardown' import accounts from '../setupAndTeardown/accounts.fixture' import HdPublicKeyInfo from '../../src/database/chain/entities/hd-public-key-info' -import { AddressType } from '../../src/models/keys/address' +import { AddressType } from '@ckb-lumos/hd' import OutPoint from '../../src/models/chain/out-point' import { when } from 'jest-when' import SystemScriptInfo from '../../src/models/system-script-info' @@ -132,7 +132,7 @@ describe('AssetAccountService', () => { const AssetAccountService = require('../../src/services/asset-account-service').default beforeAll(async () => { - await initConnection('0x1234') + await initConnection() }) afterAll(async () => { diff --git a/packages/neuron-wallet/tests/services/cell-local-info.test.ts b/packages/neuron-wallet/tests/services/cell-local-info.test.ts index 188d4f6ac7..066f906415 100644 --- a/packages/neuron-wallet/tests/services/cell-local-info.test.ts +++ b/packages/neuron-wallet/tests/services/cell-local-info.test.ts @@ -106,13 +106,13 @@ describe('CellLocalInfoService', () => { describe('updateLiveCellLockStatus', () => { it('create new entity', async () => { await CellLocalInfoService.updateLiveCellLockStatus(outPoints, true) - await expect(getConnection().getRepository(CellLocalInfo).find({ locked: true })).resolves.toHaveLength(2) + await expect(getConnection().getRepository(CellLocalInfo).findBy({ locked: true })).resolves.toHaveLength(2) }) it('update entity locked', async () => { await CellLocalInfoService.updateLiveCellLockStatus(outPoints, true) - await expect(getConnection().getRepository(CellLocalInfo).find({ locked: true })).resolves.toHaveLength(2) + await expect(getConnection().getRepository(CellLocalInfo).findBy({ locked: true })).resolves.toHaveLength(2) await CellLocalInfoService.updateLiveCellLockStatus(outPoints, false) - await expect(getConnection().getRepository(CellLocalInfo).find({ locked: false })).resolves.toHaveLength(2) + await expect(getConnection().getRepository(CellLocalInfo).findBy({ locked: false })).resolves.toHaveLength(2) }) }) diff --git a/packages/neuron-wallet/tests/services/multisig.test.ts b/packages/neuron-wallet/tests/services/multisig.test.ts index 18933d0d7d..4f0e72fe7e 100644 --- a/packages/neuron-wallet/tests/services/multisig.test.ts +++ b/packages/neuron-wallet/tests/services/multisig.test.ts @@ -70,7 +70,7 @@ describe('multisig service', () => { const multisigOutput = MultisigOutput.fromIndexer(defaultOutput) beforeAll(async () => { - await initConnection('0x1234') + await initConnection() }) afterAll(async () => { diff --git a/packages/neuron-wallet/tests/services/setting.test.ts b/packages/neuron-wallet/tests/services/setting.test.ts index aec2419474..78f2bf4530 100644 --- a/packages/neuron-wallet/tests/services/setting.test.ts +++ b/packages/neuron-wallet/tests/services/setting.test.ts @@ -42,6 +42,7 @@ jest.mock('../../src/services/networks', () => { jest.mock('electron', () => ({ BrowserWindow: { getAllWindows: jest.fn().mockReturnValue([]), + getFocusedWindow: jest.fn().mockReturnValue(null), }, })) diff --git a/packages/neuron-wallet/tests/services/sudt-token-info.test.ts b/packages/neuron-wallet/tests/services/sudt-token-info.test.ts index 88e6d96d66..f89fd33088 100644 --- a/packages/neuron-wallet/tests/services/sudt-token-info.test.ts +++ b/packages/neuron-wallet/tests/services/sudt-token-info.test.ts @@ -162,13 +162,13 @@ describe('sudt token info service', () => { }) it('no token info', async () => { - await expect(SudtTokenInfoService.getSudtTokenInfo('0x')).resolves.toBeUndefined() + await expect(SudtTokenInfoService.getSudtTokenInfo('0x')).resolves.toBeNull() }) it('token info not match', async () => { const entity = AssetAccountEntity.fromModel(assetAccount) await getConnection().manager.save([entity.sudtTokenInfo, entity]) - await expect(SudtTokenInfoService.getSudtTokenInfo(`0x${'00'.repeat(20)}`)).resolves.toBeUndefined() + await expect(SudtTokenInfoService.getSudtTokenInfo(`0x${'00'.repeat(20)}`)).resolves.toBeNull() }) it('match token info', async () => { diff --git a/packages/neuron-wallet/tests/services/transactions.test.ts b/packages/neuron-wallet/tests/services/transactions.test.ts index 8abca504b8..f8882620b1 100644 --- a/packages/neuron-wallet/tests/services/transactions.test.ts +++ b/packages/neuron-wallet/tests/services/transactions.test.ts @@ -64,7 +64,7 @@ describe('transactions service', () => { let hashes: string[] beforeAll(async () => { - await initConnection('0x1234') + await initConnection() }) afterAll(async () => { diff --git a/packages/neuron-wallet/tests/services/tx-wallet.test.ts b/packages/neuron-wallet/tests/services/tx-wallet.test.ts index 95ebaaa9ad..66cde86d55 100644 --- a/packages/neuron-wallet/tests/services/tx-wallet.test.ts +++ b/packages/neuron-wallet/tests/services/tx-wallet.test.ts @@ -1,8 +1,7 @@ import WalletService from '../../src/services/wallets' -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 { bytes } from '@ckb-lumos/codec' +import { Keychain, Keystore, ExtendedPrivateKey, AccountExtendedPublicKey } from '@ckb-lumos/hd' +import { mnemonicToSeedSync } from '@ckb-lumos/hd/lib/mnemonic' import TransactionSender from '../../src/services/transaction-sender' import { signWitnesses } from '../../src/utils/signWitnesses' @@ -45,17 +44,17 @@ describe('get keys with paths', () => { const seed = mnemonicToSeedSync(mnemonic) const masterKeychain = Keychain.fromSeed(seed) const extendedKey = new ExtendedPrivateKey( - masterKeychain.privateKey.toString('hex'), - masterKeychain.chainCode.toString('hex') + bytes.hexify(masterKeychain.privateKey), + bytes.hexify(masterKeychain.chainCode) ) - const p = masterKeychain.derivePath(receivingPath).privateKey.toString('hex') - expect(`0x${p}`).toEqual(receivingPrivateKey) + const privateKey = bytes.hexify(masterKeychain.derivePath(receivingPath).privateKey) + expect(privateKey).toEqual(receivingPrivateKey) const keystore = Keystore.create(extendedKey, password) const accountKeychain = masterKeychain.derivePath(AccountExtendedPublicKey.ckbAccountPath) const accountExtendedPublicKey = new AccountExtendedPublicKey( - accountKeychain.publicKey.toString('hex'), - accountKeychain.chainCode.toString('hex') + bytes.hexify(accountKeychain.publicKey), + bytes.hexify(accountKeychain.chainCode) ) const wallet = walletService.create({ @@ -66,7 +65,7 @@ describe('get keys with paths', () => { }) const masterPrivateKey = wallet.loadKeystore().extendedPrivateKey(password) - expect(masterKeychain.privateKey.toString('hex')).toEqual(masterPrivateKey.privateKey) + expect(bytes.hexify(masterKeychain.privateKey)).toEqual(masterPrivateKey.privateKey) const pathsAndKeys = new TransactionSender().getPrivateKeys(wallet, [receivingPath, changePath], password) expect(pathsAndKeys[0]).toEqual({ diff --git a/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts b/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts index 12db6c9f6f..3644000e9d 100644 --- a/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts +++ b/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts @@ -90,7 +90,7 @@ import { closeConnection, getConnection, initConnection } from '../../setupAndTe describe('TransactionGenerator', () => { beforeAll(async () => { - await initConnection('0x1234') + await initConnection() // @ts-ignore: Private method SystemScriptInfo.getInstance().secpOutPointInfo = new Map([ @@ -2543,6 +2543,21 @@ describe('TransactionGenerator', () => { expect(res.outputs).toHaveLength(2) expect(res.outputs[1].data).toEqual(BufferUtils.writeBigUInt128LE(BigInt(200))) }) + it('sudt capacitity is enough with legacy acp address', async () => { + const bobLegacyAnyoneCanPayLockScript = assetAccountInfo.generateLegacyAnyoneCanPayScript( + '0x36c329ed630d6ce750712a477543672adab57f4c' + ) + const sudtCell = Output.fromObject(sudtCellObject) + sudtCell.setLock(bobLegacyAnyoneCanPayLockScript) + sudtCell.setCapacity(toShannon('144')) + getCurrentMock.mockReturnValueOnce({}) + const bobAddress = scriptToAddress(bobAnyoneCanPayLockScript) + const res = (await TransactionGenerator.generateSudtMigrateAcpTx(sudtCell, bobAddress)) as Transaction + const legacyACPCellDep = assetAccountInfo.getLegacyAnyoneCanPayInfo().cellDep + expect(res.cellDeps.find(v => v.outPoint.txHash === legacyACPCellDep.outPoint.txHash)).not.toBe(-1) + expect(res.outputs).toHaveLength(2) + expect(res.outputs[1].data).toEqual(BufferUtils.writeBigUInt128LE(BigInt(200))) + }) it('sudt capacitity is not enough and last address should be acp input cell', async () => { const sudtCell = Output.fromObject(sudtCellObject) getCurrentMock.mockReturnValueOnce({ diff --git a/packages/neuron-wallet/tests/services/tx/transaction-persistor.test.ts b/packages/neuron-wallet/tests/services/tx/transaction-persistor.test.ts index 78c3ac4f91..e39e3d482f 100644 --- a/packages/neuron-wallet/tests/services/tx/transaction-persistor.test.ts +++ b/packages/neuron-wallet/tests/services/tx/transaction-persistor.test.ts @@ -16,7 +16,7 @@ const [tx, tx2] = transactions describe('TransactionPersistor', () => { beforeAll(async () => { - await initConnection('') + await initConnection() }) afterAll(async () => { @@ -91,7 +91,7 @@ describe('TransactionPersistor', () => { expect(loadedTx?.inputs.length).toBe(0) expect(loadedTx?.outputs.length).toBe(1) expect(loadedTx?.outputs[0].lockArgs).toBe(tx.outputs[0].lock.args) - const txLocks = await getConnection().getRepository(TxLockEntity).find({ transactionHash: tx.hash }) + const txLocks = await getConnection().getRepository(TxLockEntity).findBy({ transactionHash: tx.hash }) expect(txLocks.length).toBe(1) expect(txLocks[0].lockHash).toBe(tx.inputs[0].lock?.computeHash()) }) @@ -109,7 +109,7 @@ describe('TransactionPersistor', () => { .getOne() expect(loadedTx?.inputs.length).toBe(1) expect(loadedTx?.outputs.length).toBe(2) - const txLocks = await getConnection().getRepository(TxLockEntity).find({ transactionHash: tx.hash }) + const txLocks = await getConnection().getRepository(TxLockEntity).findBy({ transactionHash: tx.hash }) expect(txLocks.length).toBe(0) }) it('filter with receive cheque and send cheque', async () => { @@ -139,7 +139,7 @@ describe('TransactionPersistor', () => { .getOne() expect(loadedTx?.inputs.length).toBe(1) expect(loadedTx?.outputs.length).toBe(2) - const txLocks = await getConnection().getRepository(TxLockEntity).find({ transactionHash: tx.hash }) + const txLocks = await getConnection().getRepository(TxLockEntity).findBy({ transactionHash: tx.hash }) expect(txLocks.length).toBe(0) }) it('filter with multi lock time', async () => { @@ -162,7 +162,7 @@ describe('TransactionPersistor', () => { .getOne() expect(loadedTx?.inputs.length).toBe(1) expect(loadedTx?.outputs.length).toBe(2) - const txLocks = await getConnection().getRepository(TxLockEntity).find({ transactionHash: tx.hash }) + const txLocks = await getConnection().getRepository(TxLockEntity).findBy({ transactionHash: tx.hash }) expect(txLocks.length).toBe(0) }) }) @@ -238,7 +238,7 @@ describe('TransactionPersistor', () => { //@ts-ignore private method await TransactionPersistor.saveWithFetch(tx) expect(createMock).toBeCalledTimes(0) - const output = await getConnection().getRepository(OutputEntity).findOne({ outPointTxHash: entity.hash }) + const output = await getConnection().getRepository(OutputEntity).findOneBy({ outPointTxHash: entity.hash }) expect(output?.status).toBe(OutputStatus.Live) }) it('update live output to dead because refer to input', async () => { @@ -247,7 +247,7 @@ describe('TransactionPersistor', () => { await originalCreate(tx2, OutputStatus.Sent, OutputStatus.Pending) //@ts-ignore private method await TransactionPersistor.saveWithFetch(tx2) - const output = await getConnection().getRepository(OutputEntity).findOne({ + const output = await getConnection().getRepository(OutputEntity).findOneBy({ outPointTxHash: tx2.inputs[0].previousOutput?.txHash, outPointIndex: tx2.inputs[0].previousOutput?.index, }) @@ -262,7 +262,7 @@ describe('TransactionPersistor', () => { it('set output to dead if it is in input', async () => { await TransactionPersistor.convertTransactionAndSave(tx2, TxSaveType.Sent) await TransactionPersistor.convertTransactionAndSave(tx, TxSaveType.Sent) - const output = await getConnection().getRepository(OutputEntity).findOne({ + const output = await getConnection().getRepository(OutputEntity).findOneBy({ outPointTxHash: tx2.inputs[0].previousOutput?.txHash, outPointIndex: tx2.inputs[0].previousOutput?.index, }) @@ -300,15 +300,15 @@ describe('TransactionPersistor', () => { }) await getConnection().manager.save([txLocks, hdPublicKeyInfo]) await TransactionPersistor.convertTransactionAndSave(tx, TxSaveType.Sent) - let output = await getConnection().getRepository(OutputEntity).findOne({ outPointTxHash: tx.hash }) - let input = await getConnection().getRepository(InputEntity).findOne({ transactionHash: tx.hash }) + let output = await getConnection().getRepository(OutputEntity).findOneBy({ outPointTxHash: tx.hash }) + let input = await getConnection().getRepository(InputEntity).findOneBy({ transactionHash: tx.hash }) expect(output).toBeDefined() expect(input).toBeDefined() await TransactionPersistor.checkTxLock() - output = await getConnection().getRepository(OutputEntity).findOne({ outPointTxHash: tx.hash }) - input = await getConnection().getRepository(InputEntity).findOne({ transactionHash: tx.hash }) - expect(output).toBeUndefined() - expect(input).toBeUndefined() + output = await getConnection().getRepository(OutputEntity).findOneBy({ outPointTxHash: tx.hash }) + input = await getConnection().getRepository(InputEntity).findOneBy({ transactionHash: tx.hash }) + expect(output).toBeNull() + expect(input).toBeNull() }) }) @@ -317,8 +317,8 @@ describe('TransactionPersistor', () => { let outputEntities: OutputEntity[] = [] beforeAll(async () => { await TransactionPersistor.convertTransactionAndSave(tx, TxSaveType.Sent) - inputEntities = await getConnection().getRepository(InputEntity).find({ transactionHash: tx.hash }) - outputEntities = await getConnection().getRepository(OutputEntity).find({ outPointTxHash: tx.hash }) + inputEntities = await getConnection().getRepository(InputEntity).findBy({ transactionHash: tx.hash }) + outputEntities = await getConnection().getRepository(OutputEntity).findBy({ outPointTxHash: tx.hash }) }) it('no filter', () => { const cells = [...inputEntities, ...outputEntities] diff --git a/packages/neuron-wallet/tests/services/tx/transaction-sender.test.ts b/packages/neuron-wallet/tests/services/tx/transaction-sender.test.ts index f4ca99d4e1..919d8f3bdd 100644 --- a/packages/neuron-wallet/tests/services/tx/transaction-sender.test.ts +++ b/packages/neuron-wallet/tests/services/tx/transaction-sender.test.ts @@ -187,8 +187,7 @@ import OutPoint from '../../../src/models/chain/out-point' import Input from '../../../src/models/chain/input' import Script, { ScriptHashType } from '../../../src/models/chain/script' import Output from '../../../src/models/chain/output' -import Keystore from '../../../src/models/keys/keystore' -import { AddressType } from '../../../src/models/keys/address' +import { AddressType, Keystore } from '@ckb-lumos/hd' import WitnessArgs from '../../../src/models/chain/witness-args' import CellWithStatus from '../../../src/models/chain/cell-with-status' import SystemScriptInfo from '../../../src/models/system-script-info' diff --git a/packages/neuron-wallet/tests/services/wallets.test.ts b/packages/neuron-wallet/tests/services/wallets.test.ts index f696f2c270..5940e0c57a 100644 --- a/packages/neuron-wallet/tests/services/wallets.test.ts +++ b/packages/neuron-wallet/tests/services/wallets.test.ts @@ -1,8 +1,8 @@ -import Keystore from '../../src/models/keys/keystore' import { when } from 'jest-when' import { WalletFunctionNotSupported, DuplicateImportWallet } from '../../src/exceptions/wallet' -import { AddressType } from '../../src/models/keys/address' +import { AddressType, Keystore, AccountExtendedPublicKey } from '@ckb-lumos/hd' import { Manufacturer } from '../../src/services/hardware/common' +import { prefixWith0x } from '../../src/utils/scriptAndAddress' const stubbedDeletedByWalletIdFn = jest.fn() const stubbedGenerateAndSaveForExtendedKeyQueue = jest.fn() @@ -32,7 +32,6 @@ jest.doMock('../../src/services/addresses', () => { } }) import WalletService, { WalletProperties, Wallet } from '../../src/services/wallets' -import { AccountExtendedPublicKey } from '../../src/models/keys/key' import HdPublicKeyInfo from '../../src/database/chain/entities/hd-public-key-info' import { closeConnection, getConnection, initConnection } from '../setupAndTeardown' @@ -54,11 +53,11 @@ describe('wallet service', () => { let wallet3: WalletProperties let wallet4: WalletProperties let wallet5: WalletProperties - const fakePublicKey = 'keykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykey' - const fakeChainCode = 'codecodecodecodecodecodecodecodecodecodecodecodecodecodecodecode' + const fakePublicKey = 'abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc' + const fakeChainCode = '1234123412341234123412341234123412341234123412341234123412341234' beforeAll(async () => { - await initConnection('') + await initConnection() }) afterAll(async () => { @@ -99,7 +98,7 @@ describe('wallet service', () => { wallet2 = { name: 'wallet-test2', id: '', - extendedKey: 'a', + extendedKey: 'b'.repeat(66) + '2'.repeat(64), keystore: new Keystore( { cipher: 'wallet2', @@ -122,7 +121,7 @@ describe('wallet service', () => { wallet3 = { name: 'wallet-test3', id: '', - extendedKey: 'b', + extendedKey: 'c'.repeat(66) + '3'.repeat(64), keystore: new Keystore( { cipher: 'wallet3', @@ -145,7 +144,7 @@ describe('wallet service', () => { wallet4 = { name: 'wallet-test4', id: '', - extendedKey: 'a'.repeat(66) + 'b'.repeat(64), + extendedKey: '0x' + 'a'.repeat(66) + 'b'.repeat(64), device: { manufacturer: Manufacturer.Ledger, product: 'Nano S', @@ -160,7 +159,7 @@ describe('wallet service', () => { wallet5 = { name: 'wallet-test5', id: '', - extendedKey: 'a', + extendedKey: 'b'.repeat(66) + '2'.repeat(64), keystore: new Keystore( { cipher: 'wallet5', @@ -209,8 +208,8 @@ describe('wallet service', () => { it('returns xpubkey', () => { const wallet = walletService.get(createdWallet.id) const extendedPublicKey = wallet.accountExtendedPublicKey() - expect(extendedPublicKey.publicKey).toEqual(fakePublicKey) - expect(extendedPublicKey.chainCode).toEqual(fakeChainCode) + expect(extendedPublicKey.publicKey).toEqual(prefixWith0x(fakePublicKey)) + expect(extendedPublicKey.chainCode).toEqual(prefixWith0x(fakeChainCode)) }) }) describe('#getDeviceInfo', () => { @@ -441,14 +440,14 @@ describe('wallet service', () => { it('generates addresses for wallets not having addresses', () => { expect(stubbedGenerateAndSaveForExtendedKeyQueue).toHaveBeenCalledWith({ walletId: createdWallet2.id, - extendedKey: expect.objectContaining({ publicKey: 'a' }), + extendedKey: expect.objectContaining({ publicKey: prefixWith0x('b'.repeat(66)) }), isImporting: false, receivingAddressCount: 20, changeAddressCount: 10, }) expect(stubbedGenerateAndSaveForExtendedKeyQueue).toHaveBeenCalledWith({ walletId: createdWallet3.id, - extendedKey: expect.objectContaining({ publicKey: 'b' }), + extendedKey: expect.objectContaining({ publicKey: prefixWith0x('c'.repeat(66)) }), isImporting: false, receivingAddressCount: 20, changeAddressCount: 10, @@ -528,7 +527,7 @@ describe('wallet service', () => { } catch (error) { const { extendedKey, id } = JSON.parse(error.message) await walletService.replace(createdWallet2.id, id) - expect(extendedKey).toBe('a') + expect(extendedKey).toBe(prefixWith0x('b'.repeat(66) + '2'.repeat(64))) expect(() => walletService.get(createdWallet2.id)).toThrowError() expect(walletService.get(id).name).toBe(wallet5.name) } diff --git a/packages/neuron-wallet/tests/setupAndTeardown/index.ts b/packages/neuron-wallet/tests/setupAndTeardown/index.ts index b2fd2ff5d6..3a725fc654 100644 --- a/packages/neuron-wallet/tests/setupAndTeardown/index.ts +++ b/packages/neuron-wallet/tests/setupAndTeardown/index.ts @@ -1,16 +1,16 @@ import initDB from '../../src/database/chain/ormconfig' -import { getConnection as originGetConnection } from 'typeorm' import { TransactionPersistor } from '../../src/services/tx' import AssetAccount from '../../src/models/asset-account' import OutputEntity from '../../src/database/chain/entities/output' import AssetAccountEntity from '../../src/database/chain/entities/asset-account' +import { getConnection as originGetConnection } from '../../src/database/chain/connection' export const initConnection = (genesisBlockHash?: string) => { return initDB(genesisBlockHash ?? ':memory:') } export const closeConnection = () => { - return originGetConnection('full').close() + return originGetConnection('full').destroy() } export const getConnection = () => { diff --git a/packages/neuron-wallet/tests/setupAndTeardown/public-key-info.fixture.ts b/packages/neuron-wallet/tests/setupAndTeardown/public-key-info.fixture.ts index 6c314371ff..d11da66bfb 100644 --- a/packages/neuron-wallet/tests/setupAndTeardown/public-key-info.fixture.ts +++ b/packages/neuron-wallet/tests/setupAndTeardown/public-key-info.fixture.ts @@ -1,5 +1,5 @@ import { scriptToAddress } from '../../src/utils/scriptAndAddress' -import { AddressType } from '../../src/models/keys/address' +import { AddressType } from '@ckb-lumos/hd' import SystemScriptInfo from '../../src/models/system-script-info' const walletId1 = 'w1' diff --git a/yarn.lock b/yarn.lock index d756cab0ad..a7b79754a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2031,9 +2031,9 @@ regenerator-runtime "^0.14.0" "@babel/runtime@^7.23.9": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e" - integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw== + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" + integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA== dependencies: regenerator-runtime "^0.14.0" @@ -3401,36 +3401,6 @@ pump "^3.0.0" tar-fs "^2.1.1" -"@nervosnetwork/ckb-sdk-core@0.109.0": - version "0.109.0" - resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-sdk-core/-/ckb-sdk-core-0.109.0.tgz#9ac514e119c3fb2e1cde28437351b06e92700a06" - integrity sha512-c0pMKrZxVG0zRaPOMY+sV7CubW+Fr1YGEj/c3iEZadlXQPeeZ7wdhblG/F4oEtGebRv2lO+F8gTL9FwQlHAXRw== - dependencies: - "@nervosnetwork/ckb-sdk-rpc" "0.109.0" - "@nervosnetwork/ckb-sdk-utils" "0.109.0" - "@nervosnetwork/ckb-types" "0.109.0" - tslib "2.3.1" - -"@nervosnetwork/ckb-sdk-rpc@0.109.0": - version "0.109.0" - resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-sdk-rpc/-/ckb-sdk-rpc-0.109.0.tgz#89a46227c22a56820260d5d72e976b6b4420d650" - integrity sha512-qkb2TBMb/gqECNqzVlrx9p6V4TdyYcGlN4sOgjVrrDQw7wVcezZFMjj6usNZZLxzbyEmjksTxbAucUnbftumxA== - dependencies: - "@nervosnetwork/ckb-sdk-utils" "0.109.0" - axios "0.21.4" - tslib "2.3.1" - -"@nervosnetwork/ckb-sdk-utils@0.109.0": - version "0.109.0" - resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-sdk-utils/-/ckb-sdk-utils-0.109.0.tgz#4ac4b70f5649bd641c6cc35c70e0130f043cc98f" - integrity sha512-/lqq9qU2COhQZx6chfTzASbB+v9dFxX5WfdKpvMNvRT6tOHRGTVRgjBBS++GXLKpmn1934EJOlYOc6Peeo7g6g== - dependencies: - "@nervosnetwork/ckb-types" "0.109.0" - bech32 "2.0.0" - elliptic "6.5.4" - jsbi "3.1.3" - tslib "2.3.1" - "@nervosnetwork/ckb-types@0.109.0": version "0.109.0" resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-types/-/ckb-types-0.109.0.tgz#efcd1d96f1d3e21b20bb0c9d38e80b83e90ef827" @@ -4218,7 +4188,7 @@ lodash "^4.17.21" whatwg-mimetype "^3.0.0" -"@sqltools/formatter@^1.2.2": +"@sqltools/formatter@^1.2.5": version "1.2.5" resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12" integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw== @@ -5704,10 +5674,12 @@ dependencies: undici-types "~5.26.4" -"@types/node@^18.11.18": - version "18.16.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.18.tgz#85da09bafb66d4bc14f7c899185336d0c1736390" - integrity sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw== +"@types/node@^20.9.0": + version "20.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.7.tgz#04080362fa3dd6c5822061aa3124f5c152cff384" + integrity sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg== + dependencies: + undici-types "~5.26.4" "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -5965,11 +5937,6 @@ dependencies: "@types/node" "*" -"@types/zen-observable@0.8.3": - version "0.8.3" - resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.3.tgz#781d360c282436494b32fe7d9f7f8e64b3118aa3" - integrity sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw== - "@typescript-eslint/eslint-plugin@6.16.0": version "6.16.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.16.0.tgz#cc29fbd208ea976de3db7feb07755bba0ce8d8bc" @@ -6768,7 +6735,7 @@ app-root-dir@^1.0.2: resolved "https://registry.yarnpkg.com/app-root-dir/-/app-root-dir-1.0.2.tgz#38187ec2dea7577fff033ffcb12172692ff6e118" integrity sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g== -app-root-path@^3.0.0: +app-root-path@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.1.0.tgz#5971a2fc12ba170369a7a1ef018c71e6e47c2e86" integrity sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA== @@ -7159,13 +7126,6 @@ axe-core@^4.6.2: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.2.tgz#040a7342b20765cb18bb50b628394c21bccc17a0" integrity sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g== -axios@0.21.4: - version "0.21.4" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" - integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== - dependencies: - follow-redirects "^1.14.0" - axios@^0.27.2: version "0.27.2" resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" @@ -7454,7 +7414,7 @@ bech32@1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -bech32@2.0.0, bech32@^2.0.0: +bech32@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== @@ -7562,13 +7522,13 @@ bn.js@^5.1.3: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== -body-parser@1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" - integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== +body-parser@1.20.2: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== dependencies: bytes "3.1.2" - content-type "~1.0.4" + content-type "~1.0.5" debug "2.6.9" depd "2.0.0" destroy "1.2.0" @@ -7576,7 +7536,7 @@ body-parser@1.20.1: iconv-lite "0.4.24" on-finished "2.4.1" qs "6.11.0" - raw-body "2.5.1" + raw-body "2.5.2" type-is "~1.6.18" unpipe "1.0.0" @@ -8523,7 +8483,7 @@ content-disposition@0.5.4: dependencies: safe-buffer "5.2.1" -content-type@~1.0.4: +content-type@~1.0.4, content-type@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== @@ -8616,10 +8576,10 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== copy-descriptor@^0.1.0: version "0.1.1" @@ -8971,7 +8931,7 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -date-fns@^2.30.0: +date-fns@^2.29.3, date-fns@^2.30.0: version "2.30.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== @@ -9510,7 +9470,7 @@ dotenv-expand@^5.1.0: resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== -dotenv@8.6.0, dotenv@^8.2.0: +dotenv@8.6.0: version "8.6.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== @@ -9525,6 +9485,11 @@ dotenv@^16.0.0, dotenv@~16.3.1: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== +dotenv@^16.0.3: + version "16.4.4" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.4.tgz#a26e7bb95ebd36272ebb56edb80b826aecf224c1" + integrity sha512-XvPXc8XAQThSjAbY6cQ/9PcBXmFoWuw1sQ3b8HqUCR6ziGXjkTi//kB9SWa2UwqlgdAIuRqAa/9hVljzPehbYg== + dotenv@^9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-9.0.2.tgz#dacc20160935a37dea6364aa1bef819fb9b6ab05" @@ -9556,9 +9521,9 @@ ee-first@1.1.1: integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== ejs@^3.1.6, ejs@^3.1.7, ejs@^3.1.8: - version "3.1.9" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.9.tgz#03c9e8777fe12686a9effcef22303ca3d8eeb361" - integrity sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ== + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== dependencies: jake "^10.8.5" @@ -9639,13 +9604,13 @@ electron-window-state@5.0.3: jsonfile "^4.0.0" mkdirp "^0.5.1" -electron@28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/electron/-/electron-28.1.0.tgz#9de1ecdaafcb0ec5753827f14dfb199e6c84545e" - integrity sha512-82Y7o4PSWPn1o/aVwYPsgmBw6Gyf2lVHpaBu3Ef8LrLWXxytg7ZRZr/RtDqEMOzQp3+mcuy3huH84MyjdmP50Q== +electron@30.0.0: + version "30.0.0" + resolved "https://registry.yarnpkg.com/electron/-/electron-30.0.0.tgz#6b72a27dcc46759fac5f12e147ef64554e596391" + integrity sha512-GRwKphq/TUhSlb44OwSckXKl50f5OR/pm9MvF3rBLyqcxwfu7L11xejrZ0hDea1eKyCkzGd4B+cIqaQiDguPEA== dependencies: "@electron/get" "^2.0.0" - "@types/node" "^18.11.18" + "@types/node" "^20.9.0" extract-zip "^2.0.1" elliptic@6.5.4, elliptic@^6.5.4: @@ -10621,16 +10586,16 @@ exponential-backoff@^3.1.1: integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== express@^4.17.3: - version "4.18.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" - integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== + version "4.19.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" + integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.1" + body-parser "1.20.2" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.5.0" + cookie "0.6.0" cookie-signature "1.0.6" debug "2.6.9" depd "2.0.0" @@ -10948,7 +10913,7 @@ flow-parser@0.*: resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.210.1.tgz#6e04775dc2ebd5bde6a37de38532836678a5ac3e" integrity sha512-M0SdOwD0wZHhk6K/AOaPReBnw2vB7p9KUFUFZHJRsU3ZMl/+WVrMpmb8AfEM6GXZ5mEssCx9vHugxxJg1ieoew== -follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0: +follow-redirects@^1.0.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== @@ -11453,7 +11418,7 @@ glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.0, glob@^8.0.1: +glob@^8.0.0, glob@^8.0.1, glob@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== @@ -13508,7 +13473,7 @@ js-xxhash@^1.0.4: resolved "https://registry.yarnpkg.com/js-xxhash/-/js-xxhash-1.0.4.tgz#ce465d8a5c038167a07aa35a855c0bd792fe8e06" integrity sha512-S/6Oo7ruxx5k8m4qlMnbpwQdJjRsvvfcIhIk1dA9c5y5GNhYHKYKu9krEK3QgBax6CxJuf4gRL2opgLkdzWIKg== -js-yaml@4.1.0, js-yaml@^4.0.0, js-yaml@^4.1.0: +js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -13523,11 +13488,6 @@ js-yaml@^3.10.0, js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -jsbi@3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.1.3.tgz#f024b340032f7c7caaa6ca4b32b55e8d33f6e897" - integrity sha512-nBJqA0C6Qns+ZxurbEoIR56wyjiUszpNy70FHvxO5ervMoCbZVE3z3kxr5nKGhlxr/9MhKTSUBs7cAwwuf3g9w== - jsbi@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-4.3.0.tgz#b54ee074fb6fcbc00619559305c8f7e912b04741" @@ -14745,6 +14705,11 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mkdirp@^2.1.3: + version "2.1.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19" + integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== + modify-values@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" @@ -14947,24 +14912,10 @@ node-fetch-native@^1.0.2: resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.2.0.tgz#13ec6df98f33168958dbfb6945f10aedf42e7ea8" integrity sha512-5IAMBTl9p6PaAjYCnMv5FmqIF6GcZnawAVnzaCG0rX2aYZJ4CxEkZNtVPuTRug7fL7wyM5BQYTlAzcyMPi6oTQ== -node-fetch@2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - -node-fetch@^2.0.0, node-fetch@^2.6.11: - version "2.6.11" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.11.tgz#cde7fc71deef3131ef80a738919f999e6edfff25" - integrity sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w== - dependencies: - whatwg-url "^5.0.0" - -node-fetch@^2.6.7: - version "2.6.9" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6" - integrity sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg== +node-fetch@2.6.13, node-fetch@2.6.7, node-fetch@^2.0.0, node-fetch@^2.6.11, node-fetch@^2.6.7: + version "2.6.13" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.13.tgz#a20acbbec73c2e09f9007de5cda17104122e0010" + integrity sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA== dependencies: whatwg-url "^5.0.0" @@ -16964,10 +16915,10 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== dependencies: bytes "3.1.2" http-errors "2.0.0" @@ -17925,7 +17876,7 @@ sass@1.69.5: immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" -sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4: +sax@^1.2.4, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -19477,11 +19428,6 @@ tsconfig-paths@^4.1.2: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" - integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== - tslib@2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" @@ -19657,28 +19603,26 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typeorm@0.2.45: - version "0.2.45" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.45.tgz#e5bbb3af822dc4646bad96cfa48cd22fa4687cea" - integrity sha512-c0rCO8VMJ3ER7JQ73xfk0zDnVv0WDjpsP6Q1m6CVKul7DB9iVdWLRjPzc8v2eaeBuomsbZ2+gTaYr8k1gm3bYA== +typeorm@0.3.17: + version "0.3.17" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.17.tgz#a73c121a52e4fbe419b596b244777be4e4b57949" + integrity sha512-UDjUEwIQalO9tWw9O2A4GU+sT3oyoUXheHJy4ft+RFdnRdQctdQ34L9SqE2p7LdwzafHx1maxT+bqXON+Qnmig== dependencies: - "@sqltools/formatter" "^1.2.2" - app-root-path "^3.0.0" + "@sqltools/formatter" "^1.2.5" + app-root-path "^3.1.0" buffer "^6.0.3" - chalk "^4.1.0" + chalk "^4.1.2" cli-highlight "^2.1.11" - debug "^4.3.1" - dotenv "^8.2.0" - glob "^7.1.6" - js-yaml "^4.0.0" - mkdirp "^1.0.4" + date-fns "^2.29.3" + debug "^4.3.4" + dotenv "^16.0.3" + glob "^8.1.0" + mkdirp "^2.1.3" reflect-metadata "^0.1.13" sha.js "^2.4.11" - tslib "^2.1.0" - uuid "^8.3.2" - xml2js "^0.4.23" - yargs "^17.0.1" - zen-observable-ts "^1.0.0" + tslib "^2.5.0" + uuid "^9.0.0" + yargs "^17.6.2" typescript@5.3.3: version "5.3.3" @@ -20737,24 +20681,11 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== -xml2js@^0.4.23: - version "0.4.23" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" - integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - xmlbuilder@>=11.0.1, xmlbuilder@^15.1.1: version "15.1.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== -xmlbuilder@~11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" - integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== - xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" @@ -20810,7 +20741,7 @@ yargs-parser@^20.2.2, yargs-parser@^20.2.3: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs@17.7.2, yargs@^17.0.1, yargs@^17.6.2, yargs@^17.7.2: +yargs@17.7.2, yargs@^17.6.2, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== @@ -20849,19 +20780,6 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -zen-observable-ts@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz#2d1aa9d79b87058e9b75698b92791c1838551f83" - integrity sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA== - dependencies: - "@types/zen-observable" "0.8.3" - zen-observable "0.8.15" - -zen-observable@0.8.15: - version "0.8.15" - resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" - integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ== - zip-stream@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-5.0.1.tgz#cf3293bba121cad98be2ec7f05991d81d9f18134"