diff --git a/.ckb-light-version b/.ckb-light-version index d379b578ae..534baa8df5 100644 --- a/.ckb-light-version +++ b/.ckb-light-version @@ -1 +1 @@ -v0.3.6 +v0.3.7 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 6c618b3b53..a35aea95a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +# 0.114.3 (2024-04-16) + +### CKB Node & Light Client + +- [CKB@v0.115.0](https://github.com/nervosnetwork/ckb/releases/tag/v0.115.0) was released on Apr. 1st, 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 `0x9443ad8da9172d484367bc5467988cba7a0c46028398309edfdda7d2d79be897`(at height `12,703,957`) will be skipped in validation.(https://github.com/nervosnetwork/neuron/pull/3123) + +--- + +## New features + +- #3054: Displaying DAO rewards.(@devchenyan) +- #3066: Support keeping screen awake.(@yanguoyu) + +## Bug fixes + +- #3055: Fix sending sudt to a new acp cell with extra 142 CKB by offline sign.(@yanguoyu) +- #3103: Handle MacOS crash properly on quitting.(@devchenyan) + +## New Contributors + +- @twhy made their first contribution in https://github.com/nervosnetwork/neuron/pull/3110 + +**Full Changelog**: https://github.com/nervosnetwork/neuron/compare/v0.114.2...v0.114.3 + # 0.114.2 (2024-03-15) ### CKB Node & Light Client diff --git a/lerna.json b/lerna.json index 85d52c39c0..0e77734cb4 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "0.114.2", + "version": "0.114.3", "npmClient": "yarn", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } diff --git a/package.json b/package.json index aa0b2ab372..0f08c1da14 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "neuron", "productName": "Neuron", "description": "CKB Neuron Wallet", - "version": "0.114.2", + "version": "0.114.3", "private": true, "author": { "name": "Nervos Core Dev", diff --git a/packages/neuron-ui/package.json b/packages/neuron-ui/package.json index ba3c788a3d..dbc6ed8595 100644 --- a/packages/neuron-ui/package.json +++ b/packages/neuron-ui/package.json @@ -1,6 +1,6 @@ { "name": "neuron-ui", - "version": "0.114.2", + "version": "0.114.3", "private": true, "author": { "name": "Nervos Core Dev", @@ -50,6 +50,7 @@ "last 2 chrome versions" ], "dependencies": { + "@ckb-lumos/bi": "0.21.1", "@ckb-lumos/base": "0.21.1", "@ckb-lumos/codec": "0.21.1", "@nervosnetwork/ckb-sdk-core": "0.109.0", @@ -90,7 +91,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/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/hooks.ts b/packages/neuron-ui/src/components/DepositDialog/hooks.ts index 41fa272d06..6465bd25a1 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)) }, diff --git a/packages/neuron-ui/src/components/NervosDAO/index.tsx b/packages/neuron-ui/src/components/NervosDAO/index.tsx index c31269a8f0..5801fc1980 100644 --- a/packages/neuron-ui/src/components/NervosDAO/index.tsx +++ b/packages/neuron-ui/src/components/NervosDAO/index.tsx @@ -288,7 +288,7 @@ const NervosDAO = () => { ) : ( @@ -303,7 +303,7 @@ const NervosDAO = () => {
{onlineAndSynced && !isPrivacyMode ? ( 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/index.tsx b/packages/neuron-ui/src/components/NervosDAORecord/index.tsx index 36256afcdb..76316dbfa6 100644 --- a/packages/neuron-ui/src/components/NervosDAORecord/index.tsx +++ b/packages/neuron-ui/src/components/NervosDAORecord/index.tsx @@ -220,7 +220,7 @@ export const DAORecord = ({ ) : ( {`${shannonToCKBFormatter(capacity)} CKB`} 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/Send/hooks.ts b/packages/neuron-ui/src/components/Send/hooks.ts index b0dd76f996..d75947b0af 100644 --- a/packages/neuron-ui/src/components/Send/hooks.ts +++ b/packages/neuron-ui/src/components/Send/hooks.ts @@ -103,7 +103,7 @@ const updateTransactionWith = if (type === 'all') { const fmtItems = items.map((item, i) => ({ ...item, - amount: shannonToCKBFormatter(res.result.outputs[i].capacity, false, ''), + amount: shannonToCKBFormatter(res.result.outputs[i].capacity, false, false), })) const totalAmount = outputsToTotalAmount(fmtItems) setTotalAmount(totalAmount) 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/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/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/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/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-wallet/.env b/packages/neuron-wallet/.env index ea6ce9081c..f32862920f 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='0x397d7d41167051cab2bf1610e334ad3aa5cf612e2cd442f71b91422e0361141e' -CKB_NODE_DATA_SIZE=51 +CKB_NODE_ASSUME_VALID_TARGET='0x9443ad8da9172d484367bc5467988cba7a0c46028398309edfdda7d2d79be897' +CKB_NODE_DATA_SIZE=53 diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json index 48e38216b0..5d56449c91 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.2", + "version": "0.114.3", "private": true, "author": { "name": "Nervos Core Dev", @@ -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.2", + "neuron-ui": "0.114.3", "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/light-synchronizer.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts index 1adccf187b..6a7d5bc761 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 @@ -262,10 +262,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/services/addresses.ts b/packages/neuron-wallet/src/services/addresses.ts index 91647a04e7..c9530ec1ca 100644 --- a/packages/neuron-wallet/src/services/addresses.ts +++ b/packages/neuron-wallet/src/services/addresses.ts @@ -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, 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/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts index 6d7306c8d4..cc9c15be14 100644 --- a/packages/neuron-wallet/src/services/wallets.ts +++ b/packages/neuron-wallet/src/services/wallets.ts @@ -110,6 +110,10 @@ export abstract class Wallet { } } + public async needsGenerateAddress() { + return false + } + public abstract checkAndGenerateAddresses( isImporting?: boolean, receivingAddressCount?: number, @@ -179,6 +183,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, @@ -365,6 +374,16 @@ 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') diff --git a/packages/neuron-wallet/src/utils/sudt-value-to-amount.ts b/packages/neuron-wallet/src/utils/sudt-value-to-amount.ts index 4791a68544..37c1ed2c03 100644 --- a/packages/neuron-wallet/src/utils/sudt-value-to-amount.ts +++ b/packages/neuron-wallet/src/utils/sudt-value-to-amount.ts @@ -1,32 +1,11 @@ -const sudtValueToAmount = (value: string | null | undefined = '0', decimal: string | null | undefined = '') => { - if (value === null || value === '0') { - return '+0' - } - - if (decimal === null || decimal === undefined || Number.isNaN(+value)) { - return '--' - } - - let sign = '+' - if (value.startsWith('-')) { - 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}${decimalFraction}` - } - return `${sign}${unsignedSUDTValue}` +import { formatUnit } from '@ckb-lumos/bi' + +const sudtValueToAmount = (value: string | null = '0', decimal: string | null = '') => { + return value === null || value === '0' + ? '+0' + : decimal === null || Number.isNaN(+value) || Number.isNaN(+decimal) + ? '--' + : `${+value >= 0 ? '+' : ''}${formatUnit(BigInt(value), +decimal)}` } export default sudtValueToAmount 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/queue.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/queue.test.ts index d181a4ae5c..0e1e8661cd 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/queue.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/queue.test.ts @@ -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/utils/sudt-value-to-amount/fixtures.json b/packages/neuron-wallet/tests/utils/sudt-value-to-amount/fixtures.json index d98624cd1e..f4b1c9c6e5 100644 --- a/packages/neuron-wallet/tests/utils/sudt-value-to-amount/fixtures.json +++ b/packages/neuron-wallet/tests/utils/sudt-value-to-amount/fixtures.json @@ -14,11 +14,21 @@ "decimal": "12", "expected": "+0" }, + "null value and null decimal": { + "value": null, + "decimal": null, + "expected": "+0" + }, "zero value": { "value": "0", "decimal": "1", "expected": "+0" }, + "zero value and null decimal": { + "value": "0", + "decimal": null, + "expected": "+0" + }, "negative value": { "value": "-1", "decimal": "1", diff --git a/yarn.lock b/yarn.lock index d756cab0ad..472e5c43d8 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" @@ -5704,10 +5704,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" @@ -7562,13 +7564,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 +7578,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 +8525,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 +8618,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" @@ -9639,13 +9641,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 +10623,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" @@ -16964,10 +16966,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"