From 6a80a483bb9047af39ac1d3e2d7a9b46d6bf1264 Mon Sep 17 00:00:00 2001 From: devchenyan Date: Fri, 26 Jul 2024 15:57:43 +0800 Subject: [PATCH 01/23] fix: Capacity green progress bar (#3211) Co-authored-by: Chen Yu --- .../src/components/CellInfoDialog/cellInfoDialog.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/neuron-ui/src/components/CellInfoDialog/cellInfoDialog.module.scss b/packages/neuron-ui/src/components/CellInfoDialog/cellInfoDialog.module.scss index 1831f13f93..1b6d32159d 100644 --- a/packages/neuron-ui/src/components/CellInfoDialog/cellInfoDialog.module.scss +++ b/packages/neuron-ui/src/components/CellInfoDialog/cellInfoDialog.module.scss @@ -180,10 +180,10 @@ border: 1px solid var(--divide-line-color); background-color: var(--secondary-background-color); position: relative; + overflow: hidden; & > div { position: absolute; - left: -1px; top: -1px; height: 100%; border-radius: 8px; From c67fe9d0f9ae03ecb0befbc83a3b20b55fb2b302 Mon Sep 17 00:00:00 2001 From: devchenyan Date: Fri, 26 Jul 2024 19:05:37 +0800 Subject: [PATCH 02/23] feat: Remove undici (#3213) --- packages/neuron-wallet/package.json | 1 - packages/neuron-wallet/src/utils/ckb-rpc.ts | 5 +-- .../neuron-wallet/src/utils/rpc-request.ts | 18 ++++----- .../tests/controllers/sync-api.test.ts | 3 -- .../tests/utils/rpc-request.test.ts | 38 ++++++++++--------- yarn.lock | 12 ------ 6 files changed, 31 insertions(+), 46 deletions(-) diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json index 0e762bb2c7..52d4387f3c 100644 --- a/packages/neuron-wallet/package.json +++ b/packages/neuron-wallet/package.json @@ -70,7 +70,6 @@ "subleveldown": "4.1.4", "tslib": "2.6.3", "typeorm": "0.3.17", - "undici": "5.28.4", "uuid": "8.3.2" }, "devDependencies": { diff --git a/packages/neuron-wallet/src/utils/ckb-rpc.ts b/packages/neuron-wallet/src/utils/ckb-rpc.ts index 6a7d3a08d8..3f67d6e8be 100644 --- a/packages/neuron-wallet/src/utils/ckb-rpc.ts +++ b/packages/neuron-wallet/src/utils/ckb-rpc.ts @@ -10,7 +10,6 @@ import { PayloadInBatchException, IdNotMatchedInBatchException, } from '@ckb-lumos/rpc/lib/exceptions' -import { request } from 'undici' import CommonUtils from './common' import { NetworkType } from '../models/network' import type { RPCConfig } from '@ckb-lumos/rpc/lib/types/common' @@ -335,12 +334,12 @@ export class LightRPC extends Base { return [] } - const res = await request(node.url, { + const res = await fetch(node.url, { method: 'POST', body: JSON.stringify(payload), headers: { 'content-type': 'application/json' }, }) - const batchRes = await res.body.json() + const batchRes = await res.json() if (!Array.isArray(batchRes)) { return [] diff --git a/packages/neuron-wallet/src/utils/rpc-request.ts b/packages/neuron-wallet/src/utils/rpc-request.ts index 56bbd7babc..dc0631caaf 100644 --- a/packages/neuron-wallet/src/utils/rpc-request.ts +++ b/packages/neuron-wallet/src/utils/rpc-request.ts @@ -1,5 +1,3 @@ -import { request } from 'undici' - export const rpcRequest = async ( url: string, options: { @@ -7,7 +5,7 @@ export const rpcRequest = async ( params?: any } ): Promise => { - const res = await request(url, { + const res = await fetch(url, { method: 'POST', body: JSON.stringify({ id: 0, @@ -19,10 +17,10 @@ export const rpcRequest = async ( 'content-type': 'application/json', }, }) - if (res.statusCode !== 200) { - throw new Error(`indexer request failed with HTTP code ${res.statusCode}`) + if (res.status !== 200) { + throw new Error(`indexer request failed with HTTP code ${res.status}`) } - const body = await res.body.json() + const body = await res.json() if (body !== null && typeof body === 'object' && 'result' in body) { return body?.result as T } @@ -36,7 +34,7 @@ export const rpcBatchRequest = async ( params?: any }[] ): Promise => { - const res = await request(url, { + const res = await fetch(url, { headers: { 'content-type': 'application/json', }, @@ -50,10 +48,10 @@ export const rpcBatchRequest = async ( })) ), }) - if (res.statusCode !== 200) { - throw new Error(`indexer request failed with HTTP code ${res.statusCode}`) + if (res.status !== 200) { + throw new Error(`indexer request failed with HTTP code ${res.status}`) } - const responseBody = await res.body.json() + const responseBody = await res.json() if (Array.isArray(responseBody) && responseBody.every(i => 'id' in i)) { return responseBody.sort((a, b) => a.id - b.id) } diff --git a/packages/neuron-wallet/tests/controllers/sync-api.test.ts b/packages/neuron-wallet/tests/controllers/sync-api.test.ts index 40dec1ce51..1288f92a1a 100644 --- a/packages/neuron-wallet/tests/controllers/sync-api.test.ts +++ b/packages/neuron-wallet/tests/controllers/sync-api.test.ts @@ -44,9 +44,6 @@ jest.doMock('models/subjects/networks', () => { }, } }) -jest.mock('undici', () => ({ - request: () => jest.fn()(), -})) jest.mock('services/multisig', () => ({ syncMultisigOutput: () => jest.fn(), })) diff --git a/packages/neuron-wallet/tests/utils/rpc-request.test.ts b/packages/neuron-wallet/tests/utils/rpc-request.test.ts index f93ad617b9..fc1f0d207c 100644 --- a/packages/neuron-wallet/tests/utils/rpc-request.test.ts +++ b/packages/neuron-wallet/tests/utils/rpc-request.test.ts @@ -1,10 +1,5 @@ import { rpcBatchRequest, rpcRequest } from '../../src/utils/rpc-request' -const requestMock = jest.fn() -jest.mock('undici', () => ({ - request: () => requestMock(), -})) - describe('rpc-batch-request', () => { const options = [ { @@ -17,15 +12,19 @@ describe('rpc-batch-request', () => { }, ] it('fetch error', async () => { - requestMock.mockResolvedValueOnce({ statusCode: 500 }) + global.fetch = jest.fn(() => + Promise.resolve({ + status: 500, + }) + ) as jest.Mock await expect(rpcBatchRequest('url', options)).rejects.toThrow( new Error(`indexer request failed with HTTP code 500`) ) }) it('result is order by id', async () => { - requestMock.mockResolvedValueOnce({ - statusCode: 200, - body: { + global.fetch = jest.fn(() => + Promise.resolve({ + status: 200, json() { return Promise.resolve([ { @@ -38,8 +37,9 @@ describe('rpc-batch-request', () => { }, ]) }, - }, - }) + }) + ) as jest.Mock + const res = await rpcBatchRequest('url', options) expect(res).toEqual([ { @@ -60,21 +60,25 @@ describe('rpc-request', () => { params: 1, } it('fetch error', async () => { - requestMock.mockResolvedValueOnce({ statusCode: 500 }) + global.fetch = jest.fn(() => + Promise.resolve({ + status: 500, + }) + ) as jest.Mock await expect(rpcRequest('url', option)).rejects.toThrow(new Error(`indexer request failed with HTTP code 500`)) }) it('fetch success', async () => { - requestMock.mockResolvedValueOnce({ - statusCode: 200, - body: { + global.fetch = jest.fn(() => + Promise.resolve({ + status: 200, json() { return Promise.resolve({ id: 2, result: 2, }) }, - }, - }) + }) + ) as jest.Mock const res = await rpcRequest('url', option) expect(res).toEqual(2) }) diff --git a/yarn.lock b/yarn.lock index fea9265fb4..a925a6d045 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2658,11 +2658,6 @@ resolved "https://registry.yarnpkg.com/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz#c05ed35ad82df8e6ac616c68b92c2282bd083ba4" integrity sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ== -"@fastify/busboy@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.0.0.tgz#f22824caff3ae506b18207bad4126dbc6ccdb6b8" - integrity sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ== - "@floating-ui/core@^1.4.2": version "1.5.2" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.2.tgz#53a0f7a98c550e63134d504f26804f6b83dbc071" @@ -19981,13 +19976,6 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== -undici@5.28.4: - version "5.28.4" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068" - integrity sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g== - dependencies: - "@fastify/busboy" "^2.0.0" - unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" From d83ec24921b4df343ee82ac05915983bcc91d0e0 Mon Sep 17 00:00:00 2001 From: devchenyan Date: Tue, 30 Jul 2024 14:51:33 +0800 Subject: [PATCH 03/23] feat: Error message for duplicate import of watch wallet (#3214) * feat: Error message for duplicate import of watch wallet * fix: comments --- .../neuron-ui/src/containers/Main/hooks.ts | 35 +++++++++++++++++++ .../neuron-ui/src/containers/Main/index.tsx | 2 ++ packages/neuron-ui/src/locales/en.json | 4 +++ packages/neuron-ui/src/locales/es.json | 4 +++ packages/neuron-ui/src/locales/fr.json | 4 +++ packages/neuron-ui/src/locales/zh-tw.json | 4 +++ packages/neuron-ui/src/locales/zh.json | 4 +++ .../neuron-ui/src/types/Subject/index.d.ts | 1 + packages/neuron-wallet/src/controllers/api.ts | 16 ++++++++- .../neuron-wallet/src/controllers/wallets.ts | 3 +- .../src/models/subjects/command.ts | 1 + .../neuron-wallet/src/services/wallets.ts | 14 ++++++-- 12 files changed, 88 insertions(+), 4 deletions(-) diff --git a/packages/neuron-ui/src/containers/Main/hooks.ts b/packages/neuron-ui/src/containers/Main/hooks.ts index 307e69e956..44f337fde2 100644 --- a/packages/neuron-ui/src/containers/Main/hooks.ts +++ b/packages/neuron-ui/src/containers/Main/hooks.ts @@ -1,5 +1,6 @@ import { useEffect, useCallback, useState } from 'react' import { useLocation, NavigateFunction, useNavigate } from 'react-router-dom' +import type { TFunction } from 'i18next' import { NeuronWalletActions, StateDispatch, AppActions } from 'states/stateProvider/reducer' import { updateTransactionList, @@ -9,6 +10,7 @@ import { initAppState, showGlobalAlertDialog, updateLockWindowInfo, + dismissGlobalAlertDialog, } from 'states/stateProvider/actionCreators' import { @@ -18,6 +20,7 @@ import { setCurrentNetwork, startNodeIgnoreExternal, startSync, + replaceWallet, } from 'services/remote' import { DataUpdate as DataUpdateSubject, @@ -117,6 +120,7 @@ export const useSubscription = ({ showSwitchNetwork, lockWindowInfo, setIsLockDialogShow, + t, }: { walletID: string chain: State.Chain @@ -127,6 +131,7 @@ export const useSubscription = ({ showSwitchNetwork: () => void lockWindowInfo: State.App['lockWindowInfo'] setIsLockDialogShow: (v: boolean) => void + t: TFunction }) => { const { pageNo, pageSize, keywords } = chain.transactions @@ -318,6 +323,36 @@ export const useSubscription = ({ setIsLockDialogShow(true) } break + case 'import-exist-xpubkey': { + if (payload) { + const { existWalletIsWatchOnly, existingWalletId, id: importedWalletId } = JSON.parse(payload) + if (existWalletIsWatchOnly) { + showGlobalAlertDialog({ + type: 'warning', + message: t('main.import-exist-xpubkey-dialog.replace-tip'), + action: 'all', + onOk: () => { + replaceWallet({ + existingWalletId, + importedWalletId, + }).then(res => { + if (isSuccessResponse(res)) { + dismissGlobalAlertDialog()(dispatch) + navigate(RoutePath.Overview) + } + }) + }, + })(dispatch) + } else { + showGlobalAlertDialog({ + type: 'warning', + message: t('main.import-exist-xpubkey-dialog.delete-tip'), + action: 'ok', + })(dispatch) + } + } + break + } default: { break } diff --git a/packages/neuron-ui/src/containers/Main/index.tsx b/packages/neuron-ui/src/containers/Main/index.tsx index f0960f8e07..ac887ec701 100644 --- a/packages/neuron-ui/src/containers/Main/index.tsx +++ b/packages/neuron-ui/src/containers/Main/index.tsx @@ -64,6 +64,7 @@ const MainContent = () => { showSwitchNetwork, lockWindowInfo, setIsLockDialogShow, + t, }) useOnCurrentWalletChange({ @@ -147,6 +148,7 @@ const MainContent = () => { action={globalAlertDialog?.action} type={globalAlertDialog?.type ?? 'success'} onCancel={onCancelGlobalDialog} + onOk={globalAlertDialog?.onOk} /> {t('messages.rebuild-sync') diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index 96a15d9fd2..a403c2f639 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -1234,6 +1234,10 @@ "tip": "Due to insufficient disk space, synchronization has been stopped.
Please allocate more disk space or migrate the data to another disk.", "continue-sync": "Continue Sync", "migrate-data": "Migrate Data" + }, + "import-exist-xpubkey-dialog": { + "replace-tip": "The watch-wallet has been imported before, would you replace it?", + "delete-tip": "The same original wallet existed. If you want to continue with the import, please delete it.." } }, "cell-manage": { diff --git a/packages/neuron-ui/src/locales/es.json b/packages/neuron-ui/src/locales/es.json index 788b07d9ef..8078c667f2 100644 --- a/packages/neuron-ui/src/locales/es.json +++ b/packages/neuron-ui/src/locales/es.json @@ -1214,6 +1214,10 @@ "tip": "La sincronización se ha detenido debido a falta de espacio en disco.
Asigne más espacio en disco o migre los datos a otro disco.", "continue-sync": "Continuar sincronización", "migrate-data": "Migrar datos" + }, + "import-exist-xpubkey-dialog": { + "replace-tip": "El monedero de observación ya ha sido importado antes, ¿desea reemplazarlo?", + "delete-tip": "Existe el mismo monedero original. Si desea continuar con la importación, por favor elimínelo." } }, "cell-manage": { diff --git a/packages/neuron-ui/src/locales/fr.json b/packages/neuron-ui/src/locales/fr.json index cf3cc0fece..87586f292f 100644 --- a/packages/neuron-ui/src/locales/fr.json +++ b/packages/neuron-ui/src/locales/fr.json @@ -1224,6 +1224,10 @@ "tip": "En raison d'un espace disque insuffisant, la synchronisation a été interrompue.
Veuillez allouer plus d'espace disque ou migrer les données vers un autre disque.", "continue-sync": "Continuer la synchronisation", "migrate-data": "Migration des données" + }, + "import-exist-xpubkey-dialog": { + "replace-tip": "Le portefeuille de visualisation a déjà été importé, souhaitez-vous le remplacer?", + "delete-tip": "Le même portefeuille original existe déjà. Si vous souhaitez continuer l'importation, veuillez le supprimer." } }, "cell-manage": { diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index 59b067dd6f..b630e971a6 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -1227,6 +1227,10 @@ "tip": "由於磁盤空間不足,同步已停止。
請分配更多磁盤空間或將數據遷移到其他磁盤。", "continue-sync": "繼續同步", "migrate-data": "遷移數據" + }, + "import-exist-xpubkey-dialog": { + "replace-tip": "該觀察錢包已被導入過,您想要替換它嗎?", + "delete-tip": "已存在相同的原始錢包。如果您想繼續導入,請刪除它。" } }, "cell-manage": { diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 81575c0477..5dcb60bf89 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -1226,6 +1226,10 @@ "tip": "由于磁盘空间不足,同步已停止。
请分配更多磁盘空间或将数据迁移到其他磁盘。", "continue-sync": "继续同步", "migrate-data": "迁移数据" + }, + "import-exist-xpubkey-dialog": { + "replace-tip": "该观察钱包已被导入过,您想要替换它吗?", + "delete-tip": "已存在相同的原始钱包。如果您想继续导入,请删除它。" } }, "cell-manage": { diff --git a/packages/neuron-ui/src/types/Subject/index.d.ts b/packages/neuron-ui/src/types/Subject/index.d.ts index aaf34f6a82..b6f47004c1 100644 --- a/packages/neuron-ui/src/types/Subject/index.d.ts +++ b/packages/neuron-ui/src/types/Subject/index.d.ts @@ -17,6 +17,7 @@ declare namespace Command { | 'sign-verify' | 'multisig-address' | 'lock-window' + | 'import-exist-xpubkey' type Payload = string | null } diff --git a/packages/neuron-wallet/src/controllers/api.ts b/packages/neuron-wallet/src/controllers/api.ts index d60b6325af..f7f75d5b9e 100644 --- a/packages/neuron-wallet/src/controllers/api.ts +++ b/packages/neuron-wallet/src/controllers/api.ts @@ -66,6 +66,8 @@ import { UpdateCellLocalInfo } from '../database/chain/entities/cell-local-info' import { CKBLightRunner } from '../services/light-runner' import { type OutPoint } from '@ckb-lumos/lumos' import { updateApplicationMenu } from './app/menu' +import { DuplicateImportWallet } from '../exceptions' +import CommandSubject from '../models/subjects/command' export type Command = 'export-xpubkey' | 'import-xpubkey' | 'delete-wallet' | 'backup-wallet' // Handle channel messages from renderer process and user actions. @@ -105,7 +107,19 @@ export default class ApiController { DataUpdateSubject.next({ dataType: 'new-xpubkey-wallet', actionType: 'create' }) }) .catch(error => { - dialog.showMessageBox({ type: 'error', buttons: [], message: error.message }) + if (error instanceof DuplicateImportWallet) { + const window = BrowserWindow.getFocusedWindow() + if (window) { + CommandSubject.next({ + winID: window.id, + type: 'import-exist-xpubkey', + payload: error.message, + dispatchToUI: true, + }) + } + } else { + dialog.showMessageBox({ type: 'error', buttons: [], message: error.message }) + } }) break } diff --git a/packages/neuron-wallet/src/controllers/wallets.ts b/packages/neuron-wallet/src/controllers/wallets.ts index 50dd050c3f..b46d67b2ac 100644 --- a/packages/neuron-wallet/src/controllers/wallets.ts +++ b/packages/neuron-wallet/src/controllers/wallets.ts @@ -22,6 +22,7 @@ import { MainnetAddressRequired, TestnetAddressRequired, UnsupportedCkbCliKeystore, + DuplicateImportWallet, } from '../exceptions' import AddressService from '../services/addresses' import TransactionSender from '../services/transaction-sender' @@ -338,7 +339,7 @@ export default class WalletsController { result: wallet, } } catch (e) { - if (e instanceof UsedName) { + if (e instanceof UsedName || e instanceof DuplicateImportWallet) { throw e } throw new InvalidJSON() diff --git a/packages/neuron-wallet/src/models/subjects/command.ts b/packages/neuron-wallet/src/models/subjects/command.ts index 75f1a52fd6..bb5b27a86d 100644 --- a/packages/neuron-wallet/src/models/subjects/command.ts +++ b/packages/neuron-wallet/src/models/subjects/command.ts @@ -13,6 +13,7 @@ const CommandSubject = new Subject<{ | 'sign-verify' | 'multisig-address' | 'lock-window' + | 'import-exist-xpubkey' payload: string | null dispatchToUI: boolean }>() diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts index 2fb2bd6f4f..6121bca49b 100644 --- a/packages/neuron-wallet/src/services/wallets.ts +++ b/packages/neuron-wallet/src/services/wallets.ts @@ -405,9 +405,19 @@ export default class WalletService { wallet.saveKeystore(props.keystore!) } - if (this.getAll().find(item => item.extendedKey === props.extendedKey)) { + const existWalletInfo = this.getAll().find(item => item.extendedKey === props.extendedKey) + if (existWalletInfo) { + const existWallet = FileKeystoreWallet.fromJSON(existWalletInfo) + const existWalletIsWatchOnly = existWallet.isHDWallet() && existWallet.loadKeystore().isEmpty() this.importedWallet = wallet - throw new DuplicateImportWallet(JSON.stringify({ extendedKey: props.extendedKey, id })) + throw new DuplicateImportWallet( + JSON.stringify({ + extendedKey: props.extendedKey, + existWalletIsWatchOnly, + existingWalletId: existWallet.id, + id, + }) + ) } this.listStore.writeSync(this.walletsKey, [...this.getAll(), wallet.toJSON()]) From 79a8dc213c3aa41ef5d339815f4e574f21c2f3ff Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 31 Jul 2024 16:08:28 +0800 Subject: [PATCH 04/23] Update ckb client versions (#3217) feat: update ckb client versions Co-authored-by: Keith-CY <7271329+Keith-CY@users.noreply.github.com> --- .ckb-version | 2 +- compatible.json | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.ckb-version b/.ckb-version index a0633b073d..efb66e359e 100644 --- a/.ckb-version +++ b/.ckb-version @@ -1 +1 @@ -v0.116.1 +v0.117.0 diff --git a/compatible.json b/compatible.json index b2865b7a18..d0e70db790 100644 --- a/compatible.json +++ b/compatible.json @@ -1,5 +1,6 @@ { "fullVersions": [ + "0.117", "0.116", "0.115", "0.114", @@ -22,6 +23,7 @@ "compatible": { "0.111": { "full": [ + "0.117", "0.116", "0.115", "0.114", @@ -38,6 +40,7 @@ }, "0.110": { "full": [ + "0.117", "0.116", "0.115", "0.114", @@ -70,6 +73,7 @@ }, "0.112": { "full": [ + "0.117", "0.116", "0.115", "0.114", @@ -86,6 +90,7 @@ }, "0.114": { "full": [ + "0.117", "0.116", "0.115", "0.114", @@ -102,6 +107,7 @@ }, "0.116": { "full": [ + "0.117", "0.116", "0.115", "0.114", From d4c3527d58fcd1d223bdf4c307045bd438f88ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com> Date: Thu, 1 Aug 2024 15:59:01 +0800 Subject: [PATCH 05/23] fix: After update lumos, the `min_replace_fee` field has transfer to `minReplaceFee ` (#3218) * fix: After update lumos, the `min_replace_fee` field has transfer to `minReplaceFee ` * fix: Fix typo --- .../AmendPendingTransactionDialog/hooks.ts | 14 ++++++-------- .../AmendSUDTSend/amendSUDTSend.module.scss | 3 --- .../src/components/AmendSUDTSend/hooks.ts | 14 ++++++-------- .../src/components/AmendSend/amendSend.module.scss | 3 --- .../neuron-ui/src/components/AmendSend/hooks.ts | 14 ++++++-------- .../neuron-ui/src/components/History/RowExtend.tsx | 3 +-- .../src/components/SendFieldset/index.tsx | 2 +- .../SendFieldset/sendFieldset.module.scss | 2 +- 8 files changed, 21 insertions(+), 34 deletions(-) diff --git a/packages/neuron-ui/src/components/AmendPendingTransactionDialog/hooks.ts b/packages/neuron-ui/src/components/AmendPendingTransactionDialog/hooks.ts index cef45ef8e9..dfb4afc26a 100644 --- a/packages/neuron-ui/src/components/AmendPendingTransactionDialog/hooks.ts +++ b/packages/neuron-ui/src/components/AmendPendingTransactionDialog/hooks.ts @@ -41,12 +41,11 @@ export const useInitialize = ({ const fetchInitData = useCallback(async () => { const res = await getOnChainTransaction(tx.hash) const { - // @ts-expect-error Replace-By-Fee (RBF) - min_replace_fee: minFee, + minReplaceFee, transaction: { outputsData }, } = res - if (!minFee) { + if (!minReplaceFee) { setIsConfirmedAlertShown(true) } @@ -60,8 +59,8 @@ export const useInitialize = ({ }) setSize(txResult.size) - if (minFee) { - const mPrice = ((BigInt(minFee) * BigInt(FEE_RATIO)) / BigInt(txResult.size)).toString() + if (minReplaceFee) { + const mPrice = ((BigInt(minReplaceFee) * BigInt(FEE_RATIO)) / BigInt(txResult.size)).toString() setMinPrice(mPrice) setPrice(mPrice) } @@ -83,9 +82,8 @@ export const useInitialize = ({ const onSubmit = useCallback(async () => { try { - // @ts-expect-error Replace-By-Fee (RBF) - const { min_replace_fee: minFee } = await getOnChainTransaction(tx.hash) - if (!minFee) { + const { minReplaceFee } = await getOnChainTransaction(tx.hash) + if (!minReplaceFee) { setIsConfirmedAlertShown(true) return } diff --git a/packages/neuron-ui/src/components/AmendSUDTSend/amendSUDTSend.module.scss b/packages/neuron-ui/src/components/AmendSUDTSend/amendSUDTSend.module.scss index 8194ab6031..7b971044b1 100644 --- a/packages/neuron-ui/src/components/AmendSUDTSend/amendSUDTSend.module.scss +++ b/packages/neuron-ui/src/components/AmendSUDTSend/amendSUDTSend.module.scss @@ -38,9 +38,6 @@ $noticeHeight: 60px; padding: 20px 16px 18px; border-radius: 16px; margin-bottom: 16px; - .addresstField { - font-family: 'JetBrains Mono'; - } .textFieldClass { margin-bottom: 10px; } diff --git a/packages/neuron-ui/src/components/AmendSUDTSend/hooks.ts b/packages/neuron-ui/src/components/AmendSUDTSend/hooks.ts index 199ec5f37c..b82355cec2 100644 --- a/packages/neuron-ui/src/components/AmendSUDTSend/hooks.ts +++ b/packages/neuron-ui/src/components/AmendSUDTSend/hooks.ts @@ -46,11 +46,10 @@ export const useInitialize = ({ const fetchInitData = useCallback(async () => { const { - // @ts-expect-error Replace-By-Fee (RBF) - min_replace_fee: minFee, + minReplaceFee, transaction: { outputsData }, } = await getOnChainTransaction(hash) - if (!minFee) { + if (!minReplaceFee) { setIsConfirmedAlertShown(true) } @@ -76,8 +75,8 @@ export const useInitialize = ({ setTransaction({ ...tx, outputsData }) setSize(tx.size) - if (minFee) { - const mPrice = ((BigInt(minFee) * BigInt(FEE_RATIO)) / BigInt(tx.size)).toString() + if (minReplaceFee) { + const mPrice = ((BigInt(minReplaceFee) * BigInt(FEE_RATIO)) / BigInt(tx.size)).toString() setMinPrice(mPrice) setPrice(mPrice) } @@ -98,9 +97,8 @@ export const useInitialize = ({ return } try { - // @ts-expect-error Replace-By-Fee (RBF) - const { min_replace_fee: minFee } = await getOnChainTransaction(hash) - if (!minFee) { + const { minReplaceFee } = await getOnChainTransaction(hash) + if (!minReplaceFee) { setIsConfirmedAlertShown(true) return } diff --git a/packages/neuron-ui/src/components/AmendSend/amendSend.module.scss b/packages/neuron-ui/src/components/AmendSend/amendSend.module.scss index 0b823d03ec..10302555ae 100644 --- a/packages/neuron-ui/src/components/AmendSend/amendSend.module.scss +++ b/packages/neuron-ui/src/components/AmendSend/amendSend.module.scss @@ -38,9 +38,6 @@ $noticeHeight: 60px; padding: 20px 16px 18px; border-radius: 16px; margin-bottom: 16px; - .addresstField { - font-family: 'JetBrains Mono'; - } .textFieldClass { margin-bottom: 10px; } diff --git a/packages/neuron-ui/src/components/AmendSend/hooks.ts b/packages/neuron-ui/src/components/AmendSend/hooks.ts index 26402bea26..5e82ccdc02 100644 --- a/packages/neuron-ui/src/components/AmendSend/hooks.ts +++ b/packages/neuron-ui/src/components/AmendSend/hooks.ts @@ -70,11 +70,10 @@ export const useInitialize = ({ const fetchInitData = useCallback(async () => { const res = await getOnChainTransaction(hash) const { - // @ts-expect-error Replace-By-Fee (RBF) - min_replace_fee: minFee, + minReplaceFee, transaction: { outputsData }, } = res - if (!minFee) { + if (!minReplaceFee) { setIsConfirmedAlertShown(true) } @@ -87,8 +86,8 @@ export const useInitialize = ({ }) setSize(tx.size) - if (minFee) { - const mPrice = ((BigInt(minFee) * BigInt(FEE_RATIO)) / BigInt(tx.size)).toString() + if (minReplaceFee) { + const mPrice = ((BigInt(minReplaceFee) * BigInt(FEE_RATIO)) / BigInt(tx.size)).toString() setMinPrice(mPrice) updateTransactionPrice(mPrice) } @@ -113,9 +112,8 @@ export const useInitialize = ({ return } try { - // @ts-expect-error Replace-By-Fee (RBF) - const { min_replace_fee: minFee } = await getOnChainTransaction(hash) - if (!minFee) { + const { minReplaceFee } = await getOnChainTransaction(hash) + if (!minReplaceFee) { setIsConfirmedAlertShown(true) return } diff --git a/packages/neuron-ui/src/components/History/RowExtend.tsx b/packages/neuron-ui/src/components/History/RowExtend.tsx index 8012a6c6b7..4a6c995223 100644 --- a/packages/neuron-ui/src/components/History/RowExtend.tsx +++ b/packages/neuron-ui/src/components/History/RowExtend.tsx @@ -86,8 +86,7 @@ const RowExtend = ({ column, columns, isMainnet, id, bestBlockNumber, isWatchOnl 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 + const { minReplaceFee } = tx if (minReplaceFee) { setAmendabled(true) } diff --git a/packages/neuron-ui/src/components/SendFieldset/index.tsx b/packages/neuron-ui/src/components/SendFieldset/index.tsx index cdbf2b0f23..3d536a693e 100644 --- a/packages/neuron-ui/src/components/SendFieldset/index.tsx +++ b/packages/neuron-ui/src/components/SendFieldset/index.tsx @@ -68,7 +68,7 @@ const SendFieldset = ({ return (
Date: Thu, 1 Aug 2024 16:32:55 +0800 Subject: [PATCH 06/23] fix: Do not close multisig dialog when open action dialog for multisig. (#3219) --- packages/neuron-ui/src/components/MultisigAddress/index.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/neuron-ui/src/components/MultisigAddress/index.tsx b/packages/neuron-ui/src/components/MultisigAddress/index.tsx index 03be73c04e..50b0c5a485 100644 --- a/packages/neuron-ui/src/components/MultisigAddress/index.tsx +++ b/packages/neuron-ui/src/components/MultisigAddress/index.tsx @@ -123,10 +123,6 @@ const MultisigAddress = () => { const { deleteAction, infoAction, sendAction, approveAction } = useActions({ deleteConfigById }) const [showDeleteDialog, setShowDeleteDialog] = useState(false) - const showMainDialog = useMemo( - () => !(infoAction.isDialogOpen || sendAction.isDialogOpen || approveAction.isDialogOpen || isCreateDialogOpen), - [infoAction.isDialogOpen, sendAction.isDialogOpen, approveAction.isDialogOpen, isCreateDialogOpen] - ) const onClickItem = useCallback( (multisigConfig: MultisigConfig) => (e: React.SyntheticEvent) => { const { @@ -226,7 +222,7 @@ const MultisigAddress = () => { return (
{t('multisig-address.window-title')} From f0b53875bb5ee57a74caac79d4d7eb7cc5b95ff2 Mon Sep 17 00:00:00 2001 From: devchenyan Date: Fri, 2 Aug 2024 09:24:03 +0800 Subject: [PATCH 07/23] fix: Exception on checking addresses (#3216) * fix: Exception on checking addresses * fix: comment --------- Co-authored-by: Chen Yu --- packages/neuron-ui/src/locales/en.json | 3 ++- packages/neuron-ui/src/locales/es.json | 1 + packages/neuron-ui/src/locales/fr.json | 1 + packages/neuron-ui/src/locales/zh-tw.json | 1 + packages/neuron-ui/src/locales/zh.json | 1 + packages/neuron-wallet/src/controllers/wallets.ts | 5 +++++ packages/neuron-wallet/src/exceptions/address.ts | 8 ++++++++ packages/neuron-wallet/src/locales/en.ts | 1 + packages/neuron-wallet/src/locales/es.ts | 1 + packages/neuron-wallet/src/locales/fr.ts | 1 + packages/neuron-wallet/src/locales/zh-tw.ts | 1 + packages/neuron-wallet/src/locales/zh.ts | 1 + packages/neuron-wallet/src/services/cells.ts | 2 +- packages/neuron-wallet/tests/services/cells.test.ts | 2 +- 14 files changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index a403c2f639..e9e8e74d39 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -687,6 +687,7 @@ "308": "Amount is not enough", "309": "The receiver needs to upgrade her account address to accept more transfer.", "310": "Please enter a {{tagName}} address", + "311": "Please enter address", "402": "CKB App does not open. Please open the CKB App on your device.", "403": "No device detected. Please connect your device", "404": "Multiple device detected. Only one device of the same model can be connected.", @@ -1237,7 +1238,7 @@ }, "import-exist-xpubkey-dialog": { "replace-tip": "The watch-wallet has been imported before, would you replace it?", - "delete-tip": "The same original wallet existed. If you want to continue with the import, please delete it.." + "delete-tip": "The same original wallet existed. If you want to continue with the import, please delete it." } }, "cell-manage": { diff --git a/packages/neuron-ui/src/locales/es.json b/packages/neuron-ui/src/locales/es.json index 8078c667f2..77c7261a60 100644 --- a/packages/neuron-ui/src/locales/es.json +++ b/packages/neuron-ui/src/locales/es.json @@ -670,6 +670,7 @@ "308": "La cantidad no es suficiente", "309": "El destinatario debe actualizar la dirección de su cuenta para aceptar más transferencias.", "310": "Por favor introduzca una dirección {{tagName}}", + "311": "Por favor introduzca una dirección", "402": "La aplicación CKB no se abre. Abra la aplicación CKB en su dispositivo.", "403": "No se detectó ningún dispositivo. Por favor conecte su dispositivo", "404": "Se detectaron varios dispositivos. Sólo se puede conectar un dispositivo del mismo modelo.", diff --git a/packages/neuron-ui/src/locales/fr.json b/packages/neuron-ui/src/locales/fr.json index 87586f292f..28eedd730d 100644 --- a/packages/neuron-ui/src/locales/fr.json +++ b/packages/neuron-ui/src/locales/fr.json @@ -677,6 +677,7 @@ "308": "Le montant n'est pas suffisant", "309": "Le destinataire doit mettre à niveau son adresse de compte pour accepter davantage de transferts.", "310": "Veuillez entrer une adresse {{tagName}}", + "311": "Veuillez entrer une adresse", "402": "L'application CKB n'est pas ouverte. Veuillez ouvrir l'application CKB sur votre appareil.", "403": "Aucun appareil détecté. Veuillez connecter votre appareil", "404": "Plusieurs appareils détectés. Un seul appareil du même modèle peut être connecté.", diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index b630e971a6..8fa1bbc4e2 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -681,6 +681,7 @@ "308": "餘額不足", "309": "收款人需要升級資產賬戶才能繼續接收轉賬。", "310": "請輸入 {{tagName}} 地址", + "311": "請輸入地址", "402": "CKB 應用未打開。請在妳的設備打開 CKB 應用。", "403": "未檢測到設備,請檢查妳的設備連接", "404": "檢測到多個設備,同一型號的設備只能同時連接一個。", diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 5dcb60bf89..b6e0b16b36 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -680,6 +680,7 @@ "308": "余额不足", "309": "收款人需要升级资产账户才能继续接收转账。", "310": "请输入 {{tagName}} 地址", + "311": "请输入地址", "402": "CKB 应用未打开。请在你的设备打开 CKB 应用。", "403": "未检测到设备,请检查你的设备连接", "404": "检查到多个设备,同一型号的设备只能同时连接一个。", diff --git a/packages/neuron-wallet/src/controllers/wallets.ts b/packages/neuron-wallet/src/controllers/wallets.ts index b46d67b2ac..a47f3c2015 100644 --- a/packages/neuron-wallet/src/controllers/wallets.ts +++ b/packages/neuron-wallet/src/controllers/wallets.ts @@ -23,6 +23,7 @@ import { TestnetAddressRequired, UnsupportedCkbCliKeystore, DuplicateImportWallet, + AddressRequired, } from '../exceptions' import AddressService from '../services/addresses' import TransactionSender from '../services/transaction-sender' @@ -620,6 +621,10 @@ export default class WalletsController { private checkAddresses = (addresses: string[]) => { const isMainnet = NetworksService.getInstance().isMainnet() addresses.forEach(address => { + if (!address) { + throw new AddressRequired() + } + if (isMainnet && !address.startsWith('ckb')) { throw new MainnetAddressRequired(address) } diff --git a/packages/neuron-wallet/src/exceptions/address.ts b/packages/neuron-wallet/src/exceptions/address.ts index 5263214b63..080ce9d679 100644 --- a/packages/neuron-wallet/src/exceptions/address.ts +++ b/packages/neuron-wallet/src/exceptions/address.ts @@ -39,10 +39,18 @@ export class NoMatchAddressForSign extends Error { } } +export class AddressRequired extends Error { + public code = 311 + constructor() { + super(t('messages.address-required')) + } +} + export default { InvalidAddress, MainnetAddressRequired, TestnetAddressRequired, AddressNotFound, NoMatchAddressForSign, + AddressRequired, } diff --git a/packages/neuron-wallet/src/locales/en.ts b/packages/neuron-wallet/src/locales/en.ts index 6a7dad241b..fec1bc382c 100644 --- a/packages/neuron-wallet/src/locales/en.ts +++ b/packages/neuron-wallet/src/locales/en.ts @@ -141,6 +141,7 @@ export default { '{{path}} has no CKB Node config and storage, press confirm to synchronize from scratch', 'light-client-sudt-acp-error': "Light client mode doesn't support sending assets to other's asset account", 'could-not-connect-service': 'Could not connect to the service, please try again later.', + 'address-required': 'The address cannot be empty.', }, messageBox: { button: { diff --git a/packages/neuron-wallet/src/locales/es.ts b/packages/neuron-wallet/src/locales/es.ts index 001e571013..9d70452efd 100644 --- a/packages/neuron-wallet/src/locales/es.ts +++ b/packages/neuron-wallet/src/locales/es.ts @@ -143,6 +143,7 @@ export default { 'light-client-sudt-acp-error': 'El modo cliente ligero no admite el envío de activos a la cuenta de activos de otra persona', 'could-not-connect-service': 'No se pudo conectar al servicio, por favor, inténtalo nuevamente más tarde.', + 'address-required': 'La dirección no puede estar vacía.', }, messageBox: { button: { diff --git a/packages/neuron-wallet/src/locales/fr.ts b/packages/neuron-wallet/src/locales/fr.ts index bcc1a30316..58e11e3d3a 100644 --- a/packages/neuron-wallet/src/locales/fr.ts +++ b/packages/neuron-wallet/src/locales/fr.ts @@ -144,6 +144,7 @@ export default { "{{path}} n'a pas de configuration et de stockage de noeud CKB, appuyez sur Confirmer pour synchroniser à partir de zéro", 'light-client-sudt-acp-error': "Le mode client léger ne prend pas en charge l'envoi d'actifs vers le compte d'actifs d'autrui", + 'address-required': "L'adresse ne peut pas être vide.", }, messageBox: { button: { diff --git a/packages/neuron-wallet/src/locales/zh-tw.ts b/packages/neuron-wallet/src/locales/zh-tw.ts index a3c0d5c876..cc75c175f4 100644 --- a/packages/neuron-wallet/src/locales/zh-tw.ts +++ b/packages/neuron-wallet/src/locales/zh-tw.ts @@ -129,6 +129,7 @@ export default { 'no-exist-ckb-node-data': '{{path}} 目錄下沒有找到 CKB Node 配置和數據, 點擊確認重新同步', 'light-client-sudt-acp-error': '輕節點模式不支持發送資產給其他用戶的資產賬戶', 'could-not-connect-service': '無法連接到服務,請稍後再試。', + 'address-required': '地址不可為空', }, messageBox: { button: { diff --git a/packages/neuron-wallet/src/locales/zh.ts b/packages/neuron-wallet/src/locales/zh.ts index 77812478eb..5200e52497 100644 --- a/packages/neuron-wallet/src/locales/zh.ts +++ b/packages/neuron-wallet/src/locales/zh.ts @@ -130,6 +130,7 @@ export default { 'no-exist-ckb-node-data': '{{path}} 目录下没有找到 CKB Node 配置和数据, 点击确认重新同步', 'light-client-sudt-acp-error': '轻节点模式不支持发送资产给其他用户的资产账户', 'could-not-connect-service': '无法连接到服务,请稍后再试。', + 'address-required': '地址不可为空', }, messageBox: { button: { diff --git a/packages/neuron-wallet/src/services/cells.ts b/packages/neuron-wallet/src/services/cells.ts index 48c876e88d..04012b9d39 100644 --- a/packages/neuron-wallet/src/services/cells.ts +++ b/packages/neuron-wallet/src/services/cells.ts @@ -1420,7 +1420,7 @@ export default class CellsService { typeHashType: SystemScriptInfo.DAO_HASH_TYPE, }) .getMany() - if (!inputEntities.length) throw new Error(`No unlock transaction use ${unlockHash} as input`) + if (!inputEntities.length) throw new Error(`This is not an unlock dao transaction ${unlockHash}`) const inputPreviousTxHashes = inputEntities.map(v => v.outPointTxHash) const outputEntities = await getConnection() .getRepository(OutputEntity) diff --git a/packages/neuron-wallet/tests/services/cells.test.ts b/packages/neuron-wallet/tests/services/cells.test.ts index d2f7f90637..a50acacc32 100644 --- a/packages/neuron-wallet/tests/services/cells.test.ts +++ b/packages/neuron-wallet/tests/services/cells.test.ts @@ -1897,7 +1897,7 @@ describe('CellsService', () => { it('no input', async () => { const input = await saveTxAndInput() await expect(CellsService.getDaoWithdrawAndDeposit(input.transactionHash)).rejects.toThrow( - new Error(`No unlock transaction use ${input.transactionHash} as input`) + new Error(`This is not an unlock dao transaction ${input.transactionHash}`) ) }) it('can not find output', async () => { From 83cea84d54c0eac798582cdbe27a157218e24e0b Mon Sep 17 00:00:00 2001 From: homura Date: Fri, 2 Aug 2024 14:33:41 +0900 Subject: [PATCH 08/23] refactor: log error when ledger signing failed (#3212) * refactor: log error when ledger signing failed * refactor: pretty error message * feat: encrypt log * chore: typo * chore: remove key fields in env files * feat: use random aes key to simplify implementation * chore: add some comment * docs: how to handle a protected key --------- Co-authored-by: Chen Yu --- .../src/services/hardware/hardware.ts | 2 +- .../src/services/hardware/ledger.ts | 31 ++++- .../src/services/log-encryption.ts | 127 ++++++++++++++++++ .../src/services/transaction-sender.ts | 2 +- .../tests/services/log-encryption.test.ts | 19 +++ scripts/admin/decrypt-log/README.md | 58 ++++++++ scripts/admin/decrypt-log/run.ts | 10 ++ 7 files changed, 241 insertions(+), 8 deletions(-) create mode 100644 packages/neuron-wallet/src/services/log-encryption.ts create mode 100644 packages/neuron-wallet/tests/services/log-encryption.test.ts create mode 100644 scripts/admin/decrypt-log/README.md create mode 100644 scripts/admin/decrypt-log/run.ts diff --git a/packages/neuron-wallet/src/services/hardware/hardware.ts b/packages/neuron-wallet/src/services/hardware/hardware.ts index 9a1c818090..8ec5592068 100644 --- a/packages/neuron-wallet/src/services/hardware/hardware.ts +++ b/packages/neuron-wallet/src/services/hardware/hardware.ts @@ -90,7 +90,7 @@ export abstract class Hardware { i => witnessesArgs[0].lockArgs.slice(0, 42) === Multisig.hash([i.blake160]) )!.blake160 const serializedMultiSign: string = Multisig.serialize([blake160]) - const witnesses = await TransactionSender.signSingleMultiSignScript( + const witnesses = TransactionSender.signSingleMultiSignScript( path, serializedWitnesses, txHash, diff --git a/packages/neuron-wallet/src/services/hardware/ledger.ts b/packages/neuron-wallet/src/services/hardware/ledger.ts index 1d6a7b791d..34bfcbb38a 100644 --- a/packages/neuron-wallet/src/services/hardware/ledger.ts +++ b/packages/neuron-wallet/src/services/hardware/ledger.ts @@ -11,6 +11,7 @@ import { hd } from '@ckb-lumos/lumos' import logger from '../../utils/logger' import NetworksService from '../../services/networks' import { generateRPC } from '../../utils/ckb-rpc' +import LogEncryption from '../log-encryption' const UNCOMPRESSED_KEY_LENGTH = 130 const compressPublicKey = (key: string) => { @@ -78,12 +79,30 @@ export default class Ledger extends Hardware { context = txs.map(i => rpc.paramsFormatter.toRawTransaction(i.transaction)) } - const signature = await this.ledgerCKB!.signTransaction( - path === hd.AccountExtendedPublicKey.pathForReceiving(0) ? this.defaultPath : path, - rawTx, - witnesses, - context, - this.defaultPath + const hdPath = path === hd.AccountExtendedPublicKey.pathForReceiving(0) ? this.defaultPath : path + const signature = await this.ledgerCKB!.signTransaction(hdPath, rawTx, witnesses, context, this.defaultPath).catch( + error => { + const errorMessage = error instanceof Error ? error.message : String(error) + const encryption = LogEncryption.getInstance() + logger.error( + encryption.encrypt( + JSON.stringify([ + 'Ledger: failed to sign the transaction ', + errorMessage, + ' HD path:', + hdPath, + ' raw transaction:', + JSON.stringify(rawTx), + ' witnesses:', + JSON.stringify(witnesses), + ' context:', + JSON.stringify(context), + ]) + ) + ) + + return Promise.reject(error) + } ) return signature diff --git a/packages/neuron-wallet/src/services/log-encryption.ts b/packages/neuron-wallet/src/services/log-encryption.ts new file mode 100644 index 0000000000..2ae29e9e19 --- /dev/null +++ b/packages/neuron-wallet/src/services/log-encryption.ts @@ -0,0 +1,127 @@ +import { randomBytes, createCipheriv, publicEncrypt, privateDecrypt, createDecipheriv } from 'node:crypto' +import logger from '../utils/logger' + +export const DEFAULT_ALGORITHM = 'aes-256-cbc' + +export default class LogEncryption { + /** + * We use CBC mode here to prevent pattern-discerned + * > a one-bit change in a plaintext or initialization vector (IV) affects all following ciphertext blocks + * @private + */ + private readonly algorithm = DEFAULT_ALGORITHM + + /** + * A PEM-formatted RSA public key + * @private + */ + private readonly adminPublicKey: string + + /** + * + * @param adminPublicKey a PEM-formatted RSA public key + */ + constructor(adminPublicKey: string) { + this.adminPublicKey = adminPublicKey + } + + /** + * Encrypt a message + * @param message + */ + encrypt(message: unknown): string { + if (message == null) return '' + if (!this.adminPublicKey) return 'The admin public key does not exist, skip encrypting message' + + const localLogKey = randomBytes(32) + const iv = randomBytes(16) + + const cipher = createCipheriv(this.algorithm, localLogKey, iv) + const serializedMessage = typeof message === 'string' ? message : JSON.stringify(message, JSONSerializer) + + const encryptedLogKey = publicEncrypt(this.adminPublicKey, localLogKey).toString('base64') + const encryptedMsg = Buffer.concat([cipher.update(serializedMessage), cipher.final()]).toString('base64') + + return `[key:${encryptedLogKey}] [iv:${iv.toString('base64')}] ${encryptedMsg}` + } + + private static instance: LogEncryption + + static getInstance(): LogEncryption { + if (!LogEncryption.instance) { + const adminPublicKey = process.env.LOG_ENCRYPTION_PUBLIC_KEY ?? '' + if (!adminPublicKey) { + logger.warn('LOG_ENCRYPTION_PUBLIC_KEY is required to create LogEncryption instance') + } + + LogEncryption.instance = new LogEncryption(adminPublicKey) + } + + return LogEncryption.instance + } +} + +export class LogDecryption { + private readonly adminPrivateKey: string + + constructor(adminPrivateKey: string) { + this.adminPrivateKey = adminPrivateKey + } + + decrypt(encryptedMessage: string): string { + const { iv, key, content } = parseMessage(encryptedMessage) + + const decipher = createDecipheriv( + DEFAULT_ALGORITHM, + privateDecrypt(this.adminPrivateKey, Buffer.from(key, 'base64')), + Buffer.from(iv, 'base64') + ) + + return Buffer.concat([decipher.update(content, 'base64'), decipher.final()]).toString('utf-8') + } +} + +/** + * Parse a message into a JSON + * + * Input: + * ``` + * [key1:value2] [key2:value2] remain content + * ``` + * Output: + * ```json + * { + * "key1": "value1", + * "key2": "value2", + * "content": "remain content" + * } + * ``` + * @param message + */ +function parseMessage(message: string) { + const result: Record = {} + const regex = /\[([^\]:]+):([^\]]+)]/g + let match + let lastIndex = 0 + + while ((match = regex.exec(message)) !== null) { + const [, key, value] = match + result[key.trim()] = value.trim() + lastIndex = regex.lastIndex + } + + // Extract remaining content after the last bracket + const remainingContent = message.slice(lastIndex).trim() + if (remainingContent) { + result.content = remainingContent + } + + return result +} + +const JSONSerializer = (_key: string, value: any) => { + if (typeof value === 'bigint') { + return String(value) + 'n' + } + return value +} diff --git a/packages/neuron-wallet/src/services/transaction-sender.ts b/packages/neuron-wallet/src/services/transaction-sender.ts index 621b87835e..09dfd25c10 100644 --- a/packages/neuron-wallet/src/services/transaction-sender.ts +++ b/packages/neuron-wallet/src/services/transaction-sender.ts @@ -395,7 +395,7 @@ export default class TransactionSender { return tx } - public static async signSingleMultiSignScript( + public static signSingleMultiSignScript( privateKeyOrPath: string, witnesses: (string | WitnessArgs)[], txHash: string, diff --git a/packages/neuron-wallet/tests/services/log-encryption.test.ts b/packages/neuron-wallet/tests/services/log-encryption.test.ts new file mode 100644 index 0000000000..a389233ff2 --- /dev/null +++ b/packages/neuron-wallet/tests/services/log-encryption.test.ts @@ -0,0 +1,19 @@ +import LogEncryption, { LogDecryption } from '../../src/services/log-encryption' +import { generateKeyPairSync } from 'node:crypto' + +describe('Test LogEncryption', () => { + it('encrypted message should be able to decrypt', () => { + const { publicKey: adminPublicKey, privateKey: adminPrivateKey } = generateKeyPairSync('rsa', { + modulusLength: 2048, + }) + + const encryption = new LogEncryption(adminPublicKey.export({ format: 'pem', type: 'pkcs1' }).toString()) + const decryption = new LogDecryption(adminPrivateKey.export({ format: 'pem', type: 'pkcs1' }).toString()) + + const message = 'hello' + const encryptedMessage = encryption.encrypt(message) + const decryptedMessage = decryption.decrypt(encryptedMessage) + + expect(decryptedMessage).toBe(message) + }) +}) diff --git a/scripts/admin/decrypt-log/README.md b/scripts/admin/decrypt-log/README.md new file mode 100644 index 0000000000..71e46c6a4f --- /dev/null +++ b/scripts/admin/decrypt-log/README.md @@ -0,0 +1,58 @@ +## Log Encryption & Decryption + +## Encryption + +An environment variable `LOG_ENCRYPTION_PUBLIC_KEY` must be set to enable log encryption when releasing Neuron. If the variable is not set, a placeholder message will be left in the log file. + +To generate a keypair + +```sh +# for ADMIN_PRIVATE_KEY +openssl genrsa -out key.pem 2048 + +# for LOG_ENCRYPTION_PUBLIC_KEY +openssl rsa -in key.pem -outform PEM -pubout -out public.pem +``` + +## Decryption + +An encrypted log is following the pattern + +``` +[key:] [iv:] +``` + +And here is a real world example + +``` +[2024-07-31T11:15:06.811Z] [info] [key:sWFKSuG+GzC52QlqDUcLhCvWFevSR8JjcvlIwCmB6U750UbO59zQZlQFyIUCBMH2Vamdr/ScZaF00wObzyi2BERMkKCQ9XY1ELcQSvCaAjUy4251B4MIyrnYPu4Bf+bca5U/906ko37G6dZMDNCcm2J5pm3+0TvqwXFA+BDXsAeZ7YWXpNha+WTMbQJiGj+ltbjIlodXhtqGWBhkLHgeZtfpM/OQDclOUfSP4SDva1LUvjdkQjnmUB+5dLumEAQpm7u7mroXl5eMTpVhyVtULm+QkQ4aA/D9Q/Y1dGUxl8jU2zcgL1h8Uhrb9FMpCaLyu13gGZr42HlFVU4j/VzD/g==] [iv:/jDhuN6b/qEetyHnU2WPDw==] 0+B+gimzrZgbxfxBTtznyA== +``` + +To decrypt the message + +```sh +export LOG_MESSAGE="" +export ADMIN_PRIVATE_KEY="" +bun run.ts +``` + +### Advanced + +If the admin key is protected, please decrypt the log key by the following script + +```sh +openssl pkeyutl -pkeyopt rsa_padding_mode:oaep -passin "${MY_KEY_PASSWORD}" -decrypt -inkey private.key -in "${LOCAL_KEY}" -out "aes.key" +``` + +```js +const aesKey = ""; +const iv = Buffer.from("", "base64"); +const decipher = createDecipheriv("aes-256-cbc", aesKey, iv); + +console.log( + Buffer.concat([ + decipher.update("", "base64"), + decipher.final(), + ]).toString("utf-8") +); +``` diff --git a/scripts/admin/decrypt-log/run.ts b/scripts/admin/decrypt-log/run.ts new file mode 100644 index 0000000000..6324209296 --- /dev/null +++ b/scripts/admin/decrypt-log/run.ts @@ -0,0 +1,10 @@ +import { LogDecryption } from "../../../packages/neuron-wallet/src/services/log-encryption"; + +const ADMIN_PRIVATE_KEY = process.env + .ADMIN_PRIVATE_KEY!.split(/\r?\n/) + .map((line) => line.trim()) + .join("\n"); +const LOG_MESSAGE = process.env.LOG_MESSAGE!; + +const decryption = new LogDecryption(ADMIN_PRIVATE_KEY); +console.log(decryption.decrypt(LOG_MESSAGE)); From 0f3e0c27d4cc72cbd088ec8fed984f8e02675354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com> Date: Fri, 2 Aug 2024 18:56:19 +0800 Subject: [PATCH 09/23] fix: Fix typo check for passin (#3222) fix: Fix typo check with passin --- _typos.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/_typos.toml b/_typos.toml index 0afbdd74c5..7a852c75b4 100644 --- a/_typos.toml +++ b/_typos.toml @@ -3,6 +3,7 @@ thur = "thur" numer = "numer" HD = "HD" hd = "hd" +passin = 'passin' # defined in database schema lastest = "lastest" From 6d3338024d6b40017c180183ec8a87cec585bc71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com> Date: Mon, 5 Aug 2024 09:55:12 +0800 Subject: [PATCH 10/23] fix: Use GlobalAlertDialog to replace Notification (#3221) --- .../src/components/NervosDAO/hooks.ts | 28 ++++------- .../src/components/SUDTUpdateDialog/index.tsx | 15 +++--- .../src/components/SpecialAssetList/index.tsx | 50 ++++++++----------- .../stateProvider/actionCreators/app.ts | 14 ++---- .../src/states/stateProvider/reducer.ts | 11 ---- 5 files changed, 43 insertions(+), 75 deletions(-) diff --git a/packages/neuron-ui/src/components/NervosDAO/hooks.ts b/packages/neuron-ui/src/components/NervosDAO/hooks.ts index e0e900c72c..a0a8097ab2 100644 --- a/packages/neuron-ui/src/components/NervosDAO/hooks.ts +++ b/packages/neuron-ui/src/components/NervosDAO/hooks.ts @@ -1,6 +1,6 @@ import { useEffect, useCallback, useState } from 'react' import { AppActions, StateAction } from 'states/stateProvider/reducer' -import { updateNervosDaoData, clearNervosDaoData } from 'states/stateProvider/actionCreators' +import { updateNervosDaoData, clearNervosDaoData, showGlobalAlertDialog } from 'states/stateProvider/actionCreators' import { NavigateFunction } from 'react-router-dom' import { type CKBComponents } from '@ckb-lumos/lumos/rpc' @@ -142,14 +142,11 @@ export const useOnWithdrawDialogSubmit = ({ } }) .catch((err: Error) => { - dispatch({ - type: AppActions.AddNotification, - payload: { - type: 'alert', - timestamp: +new Date(), - content: err.message, - }, - }) + showGlobalAlertDialog({ + type: 'failed', + message: err.message, + action: 'ok', + })(dispatch) }) } setActiveRecord(null) @@ -208,14 +205,11 @@ export const useOnActionClick = ({ } }) .catch((err: Error) => { - dispatch({ - type: AppActions.AddNotification, - payload: { - type: 'alert', - timestamp: +new Date(), - content: err.message, - }, - }) + showGlobalAlertDialog({ + type: 'failed', + message: err.message, + action: 'ok', + })(dispatch) }) } else { setActiveRecord(record) diff --git a/packages/neuron-ui/src/components/SUDTUpdateDialog/index.tsx b/packages/neuron-ui/src/components/SUDTUpdateDialog/index.tsx index e439dac575..3a52c9420d 100644 --- a/packages/neuron-ui/src/components/SUDTUpdateDialog/index.tsx +++ b/packages/neuron-ui/src/components/SUDTUpdateDialog/index.tsx @@ -1,7 +1,7 @@ import React, { useReducer, useMemo, useCallback } from 'react' import { useTranslation } from 'react-i18next' import { destroyAssetAccount } from 'services/remote' -import { useState as useGlobalState, useDispatch, AppActions } from 'states' +import { useState as useGlobalState, useDispatch, AppActions, showGlobalAlertDialog } from 'states' import { UDTType, isSuccessResponse } from 'utils' import TextField from 'widgets/TextField' import Dialog from 'widgets/Dialog' @@ -123,14 +123,11 @@ const SUDTUpdateDialog = ({ }, }) } else { - globalDispatch({ - 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', + })(globalDispatch) } }) }, [globalDispatch, walletId, accountId]) diff --git a/packages/neuron-ui/src/components/SpecialAssetList/index.tsx b/packages/neuron-ui/src/components/SpecialAssetList/index.tsx index 7f4c5eb6b9..ed6c7057b0 100644 --- a/packages/neuron-ui/src/components/SpecialAssetList/index.tsx +++ b/packages/neuron-ui/src/components/SpecialAssetList/index.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useMemo, useCallback } from 'react' -import { useState as useGlobalState, useDispatch, AppActions } from 'states' +import { useState as useGlobalState, useDispatch, AppActions, showGlobalAlertDialog } from 'states' import { useTranslation } from 'react-i18next' import { useNavigate, useLocation } from 'react-router-dom' import Pagination from 'widgets/Pagination' @@ -195,14 +195,11 @@ const SpecialAssetList = () => { setCells(items) setTotalCount(+count) } 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) } }) .then(() => { @@ -280,10 +277,11 @@ const SpecialAssetList = () => { } = e.target const cell = cells.find(c => c.outPoint.txHash === txHash && c.outPoint.index === idx) if (!cell) { - dispatch({ - type: AppActions.AddNotification, - payload: { type: 'alert', timestamp: +new Date(), content: 'Cannot find the cell' }, - }) + showGlobalAlertDialog({ + type: 'failed', + message: 'Cannot find the cell', + action: 'ok', + })(dispatch) return } if (cell.customizedAssetInfo.type === 'NFT') { @@ -322,14 +320,11 @@ const SpecialAssetList = () => { }, }) } 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) } } switch (cell.customizedAssetInfo.lock) { @@ -355,14 +350,11 @@ const SpecialAssetList = () => { }) } } 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) } }) } else { diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts index 1a92c5b6b0..d4bc8867ad 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts @@ -81,15 +81,11 @@ export const addPopup = (text: string) => (dispatch: StateDispatch) => { export const addNotification = (message: State.Message) => (dispatch: StateDispatch) => { dispatch({ - type: AppActions.AddNotification, - payload: message, - }) -} - -export const dismissNotification = (timestamp: number) => (dispatch: StateDispatch) => { - dispatch({ - type: AppActions.DismissNotification, - payload: timestamp, + type: AppActions.UpdateGlobalAlertDialog, + payload: { + type: ['success', 'failed'].includes(message.type) ? message.type : 'warning', + message: message.content, + } as State.GlobalAlertDialog, }) } diff --git a/packages/neuron-ui/src/states/stateProvider/reducer.ts b/packages/neuron-ui/src/states/stateProvider/reducer.ts index 23995000a3..3d430d22e9 100644 --- a/packages/neuron-ui/src/states/stateProvider/reducer.ts +++ b/packages/neuron-ui/src/states/stateProvider/reducer.ts @@ -38,7 +38,6 @@ export enum AppActions { ClearSendState = 'clearSendState', UpdateMessage = 'updateMessage', SetGlobalDialog = 'setGlobalDialog', - AddNotification = 'addNotification', DismissNotification = 'dismissNotification', ClearNotificationsOfCode = 'clearNotificationsOfCode', ClearNotifications = 'clearNotifications', @@ -84,7 +83,6 @@ export type StateAction = | { type: AppActions.ClearSendState } | { type: AppActions.UpdateMessage; payload: any } | { type: AppActions.SetGlobalDialog; payload: State.GlobalDialogType } - | { type: AppActions.AddNotification; payload: State.Message } | { type: AppActions.DismissNotification; payload: number } // payload: timestamp | { type: AppActions.ClearNotificationsOfCode; payload: ErrorCode } // payload: code | { type: AppActions.ClearNotifications } @@ -318,15 +316,6 @@ export const reducer = produce((state: Draft, action: state.app.globalDialog = action.payload break } - case AppActions.AddNotification: { - /** - * payload: { type, content } - */ - // NOTICE: for simplicity, only one notification will be displayed - state.app.notifications.push(action.payload) - state.app.showTopAlert = true - break - } case AppActions.DismissNotification: { /** * payload: timestamp From b8ba97bd498fe7c68ff56d666eb0679ebef760a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com> Date: Mon, 5 Aug 2024 11:00:08 +0800 Subject: [PATCH 11/23] fix: Ignore deleted wallet when ignore duplicate script. (#3220) fix: Ignore delete wallet when ignore duplicate script. --- .../sync/light-synchronizer.ts | 12 ++++++------ .../block-sync-renderer/sync/synchronizer.ts | 11 +++++++---- .../neuron-wallet/src/services/sync-progress.ts | 17 +++++++++++++++++ .../block-sync-renderer/synchronizer.test.ts | 4 ++-- 4 files changed, 32 insertions(+), 12 deletions(-) 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 aafa963859..2f7829c674 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 @@ -236,8 +236,8 @@ export default class LightSynchronizer extends Synchronizer { 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) await SyncProgressService.updateSyncProgressFlag(walletIds) + await SyncProgressService.initSyncProgress(addScripts) } private async initMultisigSyncProgress() { @@ -345,16 +345,16 @@ export default class LightSynchronizer extends Synchronizer { } async processTxsInNextBlockNumber() { - const [nextBlockNumber, txHashesInNextBlock] = await this.getTxHashesWithNextUnprocessedBlockNumber() - const minSyncBlockNumber = await SyncProgressService.getCurrentWalletMinSyncedBlockNumber( - this.syncMultisig ? SyncAddressType.Multisig : undefined - ) + const [nextBlockNumber, txHashesInNextBlock, walletIds] = await this.getTxHashesWithNextUnprocessedBlockNumber() + const minSyncBlockNumber = this.syncMultisig + ? await SyncProgressService.getCurrentWalletMinSyncedBlockNumber(SyncAddressType.Multisig) + : await SyncProgressService.getMinSyncedBlockNumberInWallets(walletIds) if ( nextBlockNumber !== undefined && txHashesInNextBlock.length && // For light client, if tx hash has been called with fetch_transaction, the tx can not return by get_transactions // So before derived address synced to bigger than next synced block number, do not sync the next block number - minSyncBlockNumber >= parseInt(nextBlockNumber) && + (minSyncBlockNumber === undefined || minSyncBlockNumber >= parseInt(nextBlockNumber)) && // check whether the tx is sync from light client, after split the light client and full DB file, this check will remove (await this.checkTxExist(txHashesInNextBlock)) ) { 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 ff9c576f8c..e3b58c0eb7 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/synchronizer.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/synchronizer.ts @@ -69,7 +69,7 @@ export abstract class Synchronizer { await this.processNextBlockNumberQueue?.drain() } - protected async getTxHashesWithNextUnprocessedBlockNumber(): Promise<[string | undefined, string[]]> { + protected async getTxHashesWithNextUnprocessedBlockNumber(): Promise<[string | undefined, string[], string[]]> { const txHashCachesByNextBlockNumberAndAddress = await Promise.all( [...this.addressesByWalletId.keys()].map(async walletId => IndexerCacheService.nextUnprocessedTxsGroupedByBlockNumber(walletId) @@ -87,12 +87,15 @@ export abstract class Synchronizer { const nextUnprocessedBlockNumber = [...groupedTxHashCaches.keys()].sort((a, b) => parseInt(a) - parseInt(b)).shift() if (!nextUnprocessedBlockNumber) { - return [undefined, []] + return [undefined, [], []] } const txHashCachesInNextUnprocessedBlockNumber = groupedTxHashCaches.get(nextUnprocessedBlockNumber) - - return [nextUnprocessedBlockNumber, txHashCachesInNextUnprocessedBlockNumber!.map(({ txHash }) => txHash)] + return [ + nextUnprocessedBlockNumber, + txHashCachesInNextUnprocessedBlockNumber!.map(({ txHash }) => txHash), + [...new Set(txHashCachesInNextUnprocessedBlockNumber!.map(({ walletId }) => walletId))], + ] } protected async notifyAndSyncNext(indexerTipNumber: number) { diff --git a/packages/neuron-wallet/src/services/sync-progress.ts b/packages/neuron-wallet/src/services/sync-progress.ts index 0d5060ec1b..67de4b1d9c 100644 --- a/packages/neuron-wallet/src/services/sync-progress.ts +++ b/packages/neuron-wallet/src/services/sync-progress.ts @@ -19,6 +19,9 @@ export default class SyncProgressService { .getRepository(SyncProgress) .find({ select: ['hash'], + where: { + delete: false, + }, }) const existHashes = new Set(existProgresses.map(v => v.hash)) const newSyncProgreses = syncProgresses.filter(v => !existHashes.has(v.hash)) @@ -101,6 +104,20 @@ export default class SyncProgressService { return item?.syncedBlockNumber || 0 } + static async getMinSyncedBlockNumberInWallets(walletIds: string[]) { + const item = await getConnection() + .getRepository(SyncProgress) + .createQueryBuilder() + .where({ + delete: false, + addressType: SyncAddressType.Default, + walletId: In(walletIds), + }) + .orderBy('syncedBlockNumber', 'ASC') + .getOne() + return item?.syncedBlockNumber + } + static async getWalletMinLocalSavedBlockNumber() { const items = await getConnection() .getRepository(SyncProgress) 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 fc51c62735..dad25e6599 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/synchronizer.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/synchronizer.test.ts @@ -142,7 +142,7 @@ describe('unit tests for IndexerConnector', () => { stubbedNextUnprocessedTxsGroupedByBlockNumberFn.mockResolvedValue([]) // @ts-ignore private method const result = await synchronizer.getTxHashesWithNextUnprocessedBlockNumber() - expect(result).toStrictEqual([undefined, []]) + expect(result).toStrictEqual([undefined, [], []]) }) it('get cached tx and sort by block number', async () => { stubbedNextUnprocessedTxsGroupedByBlockNumberFn.mockImplementation(walletId => @@ -166,7 +166,7 @@ describe('unit tests for IndexerConnector', () => { ) // @ts-ignore private method const result = await synchronizer.getTxHashesWithNextUnprocessedBlockNumber() - expect(result).toStrictEqual(['2', ['hash2']]) + expect(result).toStrictEqual(['2', ['hash2'], [walletId2]]) }) }) From 4e46d039f56b48324942216199aafcc27f43f9c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com> Date: Wed, 7 Aug 2024 15:26:34 +0800 Subject: [PATCH 12/23] fix: Remove unexpected value in log (#3224) --- .../ReplaceDuplicateWalletDialog/index.tsx | 17 ++++++++++------- packages/neuron-ui/src/containers/Main/hooks.ts | 6 +++--- packages/neuron-wallet/src/services/wallets.ts | 15 ++++++++------- .../tests/services/wallets.test.ts | 4 ++-- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx b/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx index c644a496e9..133c7067d4 100644 --- a/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx +++ b/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx @@ -9,7 +9,7 @@ import { replaceWallet } from 'services/remote' import styles from './replaceDuplicateWalletDialog.module.scss' const useReplaceDuplicateWallet = () => { - const [extendedKey, setExtendedKey] = useState('') + const [duplicateWalletIds, setDuplicateWalletIds] = useState([]) const [importedWalletId, setImportedWalletId] = useState('') const onClose = useCallback(() => { @@ -28,7 +28,7 @@ const useReplaceDuplicateWallet = () => { const msg = typeof message === 'string' ? '' : message.content if (msg) { const obj = JSON.parse(msg) - setExtendedKey(obj.extendedKey) + setDuplicateWalletIds(obj.duplicateWalletIds) setImportedWalletId(obj.id) } } catch (error) { @@ -36,14 +36,14 @@ const useReplaceDuplicateWallet = () => { } } - const show = useMemo(() => !!extendedKey && !!importedWalletId, [importedWalletId, extendedKey]) + const show = useMemo(() => !!duplicateWalletIds.length && !!importedWalletId, [importedWalletId, duplicateWalletIds]) return { onImportingExitingWalletError, dialogProps: { show, onClose, - extendedKey, + duplicateWalletIds, importedWalletId, }, } @@ -52,12 +52,12 @@ const useReplaceDuplicateWallet = () => { const ReplaceDuplicateWalletDialog = ({ show, onClose, - extendedKey, + duplicateWalletIds, importedWalletId, }: { show: boolean onClose: () => void - extendedKey: string + duplicateWalletIds: string[] importedWalletId: string }) => { const { @@ -68,7 +68,10 @@ const ReplaceDuplicateWalletDialog = ({ const [selectedId, setSelectedId] = useState('') const [t] = useTranslation() - const group = useMemo(() => wallets.filter(item => item.extendedKey === extendedKey), [wallets, extendedKey]) + const group = useMemo( + () => wallets.filter(item => duplicateWalletIds.includes(item.id)), + [wallets, duplicateWalletIds] + ) const handleGroupChange = useCallback( (checked: string) => { diff --git a/packages/neuron-ui/src/containers/Main/hooks.ts b/packages/neuron-ui/src/containers/Main/hooks.ts index 44f337fde2..29fa5a37e0 100644 --- a/packages/neuron-ui/src/containers/Main/hooks.ts +++ b/packages/neuron-ui/src/containers/Main/hooks.ts @@ -325,15 +325,15 @@ export const useSubscription = ({ break case 'import-exist-xpubkey': { if (payload) { - const { existWalletIsWatchOnly, existingWalletId, id: importedWalletId } = JSON.parse(payload) - if (existWalletIsWatchOnly) { + const { duplicateWatchedWalletIds, id: importedWalletId } = JSON.parse(payload) + if (duplicateWatchedWalletIds.length) { showGlobalAlertDialog({ type: 'warning', message: t('main.import-exist-xpubkey-dialog.replace-tip'), action: 'all', onOk: () => { replaceWallet({ - existingWalletId, + existingWalletId: duplicateWatchedWalletIds[0], importedWalletId, }).then(res => { if (isSuccessResponse(res)) { diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts index 6121bca49b..77ac8df4a5 100644 --- a/packages/neuron-wallet/src/services/wallets.ts +++ b/packages/neuron-wallet/src/services/wallets.ts @@ -405,16 +405,17 @@ export default class WalletService { wallet.saveKeystore(props.keystore!) } - const existWalletInfo = this.getAll().find(item => item.extendedKey === props.extendedKey) - if (existWalletInfo) { - const existWallet = FileKeystoreWallet.fromJSON(existWalletInfo) - const existWalletIsWatchOnly = existWallet.isHDWallet() && existWallet.loadKeystore().isEmpty() + const existWalletsProperties = this.getAll().filter(item => item.extendedKey === props.extendedKey) + if (existWalletsProperties.length) { + const existWallets = existWalletsProperties.map(v => this.get(v.id)) + const duplicateWatchedWalletIds = existWallets + .filter(v => v.isHDWallet() && v.loadKeystore().isEmpty()) + .map(v => v.id) this.importedWallet = wallet throw new DuplicateImportWallet( JSON.stringify({ - extendedKey: props.extendedKey, - existWalletIsWatchOnly, - existingWalletId: existWallet.id, + duplicateWalletIds: existWallets.map(v => v.id), + duplicateWatchedWalletIds, id, }) ) diff --git a/packages/neuron-wallet/tests/services/wallets.test.ts b/packages/neuron-wallet/tests/services/wallets.test.ts index 0d7eec58c6..bcaaf419e8 100644 --- a/packages/neuron-wallet/tests/services/wallets.test.ts +++ b/packages/neuron-wallet/tests/services/wallets.test.ts @@ -525,9 +525,9 @@ describe('wallet service', () => { try { walletService.create(wallet5) } catch (error) { - const { extendedKey, id } = JSON.parse(error.message) + const { duplicateWalletIds, id } = JSON.parse(error.message) await walletService.replace(createdWallet2.id, id) - expect(extendedKey).toBe(prefixWith0x('b'.repeat(66) + '2'.repeat(64))) + expect(duplicateWalletIds).toStrictEqual([createdWallet2.id]) expect(() => walletService.get(createdWallet2.id)).toThrowError() expect(walletService.get(id).name).toBe(wallet5.name) } From 56acea426926e35ae85ea39098ed3c1223ba38d9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 16:33:54 +0900 Subject: [PATCH 13/23] chore: Update ckb node assume valid target (#3227) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- packages/neuron-wallet/.env | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/neuron-wallet/.env b/packages/neuron-wallet/.env index b5240ac1d7..7d71ecbddd 100644 --- a/packages/neuron-wallet/.env +++ b/packages/neuron-wallet/.env @@ -117,6 +117,6 @@ DAO_CODE_HASH=0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e MULTISIG_CODE_HASH=0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8 # CKB NODE OPTIONS -CKB_NODE_ASSUME_VALID_TARGET='0x6dd077b407d019a0bce0cbad8c34e69a524ae4b2599b9feda2c7491f3559d32c' -CKB_NODE_ASSUME_VALID_TARGET_BLOCK_NUMBER=13007704 -CKB_NODE_DATA_SIZE=56 +CKB_NODE_ASSUME_VALID_TARGET='0xca44ae8f7bc12ba8eab3224cbe3156c913e2284693e36dc1d01e4d30f362f3c2' +CKB_NODE_ASSUME_VALID_TARGET_BLOCK_NUMBER=13705152 +CKB_NODE_DATA_SIZE=58 From ded929dd8a0874775a77cd73a3f22f855a6e1c0b Mon Sep 17 00:00:00 2001 From: Keith Date: Thu, 8 Aug 2024 17:54:40 +0900 Subject: [PATCH 14/23] chore: update versions and changelogs --- CHANGELOG.md | 60 ++++++++++++++++++++++++----- lerna.json | 2 +- package.json | 2 +- packages/neuron-ui/package.json | 2 +- packages/neuron-wallet/package.json | 4 +- 5 files changed, 56 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14a324aac9..96e3daf3b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,45 @@ +# 0.117.0 (2024-08-12) + +### CKB Node & Light Client + +- [CKB@v0.117.0](https://github.com/nervosnetwork/ckb/releases/tag/v0.117.0) was released on Jul. 29th, 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 `0xca44ae8f7bc12ba8eab3224cbe3156c913e2284693e36dc1d01e4d30f362f3c2`(at height `13,705,152`) will be skipped in validation.(https://github.com/nervosnetwork/neuron/pull/3227) + +--- + + + + + +--- + +## New features + +- #3206: Support XUDT asset management.(@yanguoyu) +- #3207: Support connecting to an external light client.(@devchenyan) +- #3167: Support cells consolidation.(@devchenyan) +- #3199: Validate pending transactions periodically.(@devchenyan) +- #3200: Optimize the process of generating a wallet.(@devchenyan) +- #3176: Support setting start block numbers of multisig addresses.(@yanguoyu) +- #3160: Optimize synchronization in light client mode for multiple wallets.(@yanguoyu) +- #3169: Be compatible with multisig transaction JSON file exported from CKB CLI.(@devchenyan) +- #3197: Support resetting pin code for window lock.(@yanguoyu) +- #3194: Add a tip for multisig addresses.(@yanguoyu) + +## Bug fixes + +- #3195: Fix the synchronization status check.(@yanguoyu) + +## New Contributors + +- @tcpdumppy made their first contribution in https://github.com/nervosnetwork/neuron/pull/3182 + +**Full Changelog**: https://github.com/nervosnetwork/neuron/compare/v0.116.2...v0.117.0 + # 0.116.2 (2024-05-29) ### CKB Node & Light Client @@ -57,18 +99,18 @@ 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) +- #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) +- #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 diff --git a/lerna.json b/lerna.json index 42a9f9ef22..6e01055745 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "0.116.2", + "version": "0.117.0", "npmClient": "yarn", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } diff --git a/package.json b/package.json index e3cbe767f7..460937a772 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "neuron", "productName": "Neuron", "description": "CKB Neuron Wallet", - "version": "0.116.2", + "version": "0.117.0", "private": true, "author": { "name": "Nervos Core Dev", diff --git a/packages/neuron-ui/package.json b/packages/neuron-ui/package.json index 633abb7d55..9f0ef450d2 100644 --- a/packages/neuron-ui/package.json +++ b/packages/neuron-ui/package.json @@ -1,6 +1,6 @@ { "name": "neuron-ui", - "version": "0.116.2", + "version": "0.117.0", "private": true, "author": { "name": "Nervos Core Dev", diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json index 52d4387f3c..dcd3c40b61 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.116.2", + "version": "0.117.0", "private": true, "author": { "name": "Nervos Core Dev", @@ -92,7 +92,7 @@ "electron-builder": "24.9.1", "electron-devtools-installer": "3.2.0", "jest-when": "3.6.0", - "neuron-ui": "0.116.2", + "neuron-ui": "0.117.0", "typescript": "5.3.3" } } From 21699734e102fe53209b662e8af7aa8bfd885123 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:01:41 +0900 Subject: [PATCH 15/23] Update Neuron compatibility table (#3229) Co-authored-by: Keith-CY <7271329+Keith-CY@users.noreply.github.com> --- compatible.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/compatible.json b/compatible.json index d0e70db790..125c5a0fc3 100644 --- a/compatible.json +++ b/compatible.json @@ -121,6 +121,23 @@ "0.3", "0.2" ] + }, + "0.117": { + "full": [ + "0.117", + "0.116", + "0.115", + "0.114", + "0.113", + "0.112", + "0.111", + "0.110", + "0.109" + ], + "light": [ + "0.3", + "0.2" + ] } } } From dc6d50c202f21e692308bb3b2302152a41a282f3 Mon Sep 17 00:00:00 2001 From: homura Date: Sun, 11 Aug 2024 21:55:10 +0900 Subject: [PATCH 16/23] chore: make run.ts independent from neuron (#3231) --- scripts/admin/decrypt-log/run.ts | 62 +++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/scripts/admin/decrypt-log/run.ts b/scripts/admin/decrypt-log/run.ts index 6324209296..ccf266bcc6 100644 --- a/scripts/admin/decrypt-log/run.ts +++ b/scripts/admin/decrypt-log/run.ts @@ -1,4 +1,64 @@ -import { LogDecryption } from "../../../packages/neuron-wallet/src/services/log-encryption"; +import { createDecipheriv, privateDecrypt } from "node:crypto"; + +export const DEFAULT_ALGORITHM = "aes-256-cbc"; + +export class LogDecryption { + private readonly adminPrivateKey: string; + + constructor(adminPrivateKey: string) { + this.adminPrivateKey = adminPrivateKey; + } + + decrypt(encryptedMessage: string): string { + const { iv, key, content } = parseMessage(encryptedMessage); + + const decipher = createDecipheriv( + DEFAULT_ALGORITHM, + privateDecrypt(this.adminPrivateKey, Buffer.from(key, "base64")), + Buffer.from(iv, "base64") + ); + + return Buffer.concat([decipher.update(content, "base64"), decipher.final()]).toString("utf-8"); + } +} + +/** + * Parse a message into a JSON + * + * Input: + * ``` + * [key1:value2] [key2:value2] remain content + * ``` + * Output: + * ```json + * { + * "key1": "value1", + * "key2": "value2", + * "content": "remain content" + * } + * ``` + * @param message + */ +function parseMessage(message: string) { + const result: Record = {}; + const regex = /\[([^\]:]+):([^\]]+)]/g; + let match; + let lastIndex = 0; + + while ((match = regex.exec(message)) !== null) { + const [, key, value] = match; + result[key.trim()] = value.trim(); + lastIndex = regex.lastIndex; + } + + // Extract remaining content after the last bracket + const remainingContent = message.slice(lastIndex).trim(); + if (remainingContent) { + result.content = remainingContent; + } + + return result; +} const ADMIN_PRIVATE_KEY = process.env .ADMIN_PRIVATE_KEY!.split(/\r?\n/) From dfdae8e33e5b7a94557e1a9ce0ff794192a3e34b Mon Sep 17 00:00:00 2001 From: Chen Yu Date: Mon, 12 Aug 2024 12:23:06 +0900 Subject: [PATCH 17/23] feat: encrypt blake160 list in the log (#3234) --- .../src/controllers/export-debug.ts | 8 ++++- .../src/services/log-encryption.ts | 4 +++ .../tests/controllers/export-debug.test.ts | 29 ++++++++++++++++++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/neuron-wallet/src/controllers/export-debug.ts b/packages/neuron-wallet/src/controllers/export-debug.ts index c393984bd3..0504fe8ce7 100644 --- a/packages/neuron-wallet/src/controllers/export-debug.ts +++ b/packages/neuron-wallet/src/controllers/export-debug.ts @@ -14,6 +14,7 @@ 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' +import LogEncryption from '../services/log-encryption' export default class ExportDebugController { #I18N_PATH = 'export-debug-info' @@ -158,7 +159,12 @@ export default class ExportDebugController { csv += row } const csvFileName = 'hd_public_key_info.csv' - this.archive.append(csv, { name: csvFileName }) + const encryption = LogEncryption.getInstance() + if (encryption.isEnabled) { + this.archive.append(encryption.encrypt(csv), { name: `encrypted_${csvFileName}` }) + } else { + this.archive.append(csv, { name: csvFileName }) + } } catch (error) { logger.error(`Export Debug:\t export public key info error: ${error}`) } diff --git a/packages/neuron-wallet/src/services/log-encryption.ts b/packages/neuron-wallet/src/services/log-encryption.ts index 2ae29e9e19..b27dbcfc32 100644 --- a/packages/neuron-wallet/src/services/log-encryption.ts +++ b/packages/neuron-wallet/src/services/log-encryption.ts @@ -17,6 +17,10 @@ export default class LogEncryption { */ private readonly adminPublicKey: string + public get isEnabled(): boolean { + return !!this.adminPublicKey + } + /** * * @param adminPublicKey a PEM-formatted RSA public key diff --git a/packages/neuron-wallet/tests/controllers/export-debug.test.ts b/packages/neuron-wallet/tests/controllers/export-debug.test.ts index 9e9f4b797e..f346649c3d 100644 --- a/packages/neuron-wallet/tests/controllers/export-debug.test.ts +++ b/packages/neuron-wallet/tests/controllers/export-debug.test.ts @@ -100,6 +100,19 @@ jest.mock('../../src/services/wallets', () => { } }) +const encryptionMock = { + isEnabled: false, + encrypt: jest.fn(v => v), +} + +jest.mock('../../src/services/log-encryption.ts', () => { + return { + getInstance() { + return encryptionMock + }, + } +}) + import { dialog } from 'electron' import logger from '../../src/utils/logger' import ExportDebugController from '../../src/controllers/export-debug' @@ -148,7 +161,7 @@ describe('Test ExportDebugController', () => { }) it('should call required methods', () => { - expect.assertions(9) + expect.assertions(10) expect(showSaveDialogMock).toHaveBeenCalled() expect(addBundledCKBLogMock).toHaveBeenCalled() @@ -159,6 +172,7 @@ describe('Test ExportDebugController', () => { expect(showMessageBoxMock).toHaveBeenCalled() expect(showErrorBoxMock).not.toHaveBeenCalled() expect(logger.error).not.toHaveBeenCalled() + expect(encryptionMock.encrypt).not.toHaveBeenCalled() 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' })) @@ -200,4 +214,17 @@ describe('Test ExportDebugController', () => { expect(showErrorBoxMock).toHaveBeenCalled() }) }) + + describe('when encryption is enabled', () => { + beforeEach(() => { + encryptionMock.isEnabled = true + showSaveDialogMock.mockResolvedValue({ canceled: false, filePath: 'mock_path' }) + return exportDebugController.export() + }) + + it('encrypt should be called', () => { + expect.assertions(1) + expect(encryptionMock.encrypt).toHaveBeenCalled() + }) + }) }) From ee2a69b9bd48b27fa5f4572e14df24a55cc59bce Mon Sep 17 00:00:00 2001 From: Chen Yu Date: Mon, 12 Aug 2024 12:28:24 +0900 Subject: [PATCH 18/23] ci: inject environment variables from github action (#3233) Co-authored-by: homura --- .github/workflows/package.yml | 5 +++++ packages/neuron-wallet/src/services/log-encryption.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index d1d21db08c..10200e7ebb 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -103,6 +103,11 @@ jobs: env: CI: false + # Inject LOG_ENCRYPTION_PUBLIC_KEY to encrypt sensitive log + - name: Inject environment variables + run: | + echo "\nLOG_ENCRYPTION_PUBLIC_KEY=${{ secrets.LOG_ENCRYPTION_PUBLIC_KEY }}\n" >> packages/neuron-wallet/.env + - name: Package for MacOS if: matrix.os == 'macos-latest' run: | diff --git a/packages/neuron-wallet/src/services/log-encryption.ts b/packages/neuron-wallet/src/services/log-encryption.ts index b27dbcfc32..31c024d19c 100644 --- a/packages/neuron-wallet/src/services/log-encryption.ts +++ b/packages/neuron-wallet/src/services/log-encryption.ts @@ -26,7 +26,7 @@ export default class LogEncryption { * @param adminPublicKey a PEM-formatted RSA public key */ constructor(adminPublicKey: string) { - this.adminPublicKey = adminPublicKey + this.adminPublicKey = adminPublicKey.replace(/\\n/g, '\n') } /** From 47ad12f297c8cebed118c10cbc9a06d82cec533e Mon Sep 17 00:00:00 2001 From: Keith Date: Mon, 12 Aug 2024 12:54:31 +0900 Subject: [PATCH 19/23] docs: update youtube address of v0.117.0 --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96e3daf3b4..0f48947346 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,9 @@ Block before `0xca44ae8f7bc12ba8eab3224cbe3156c913e2284693e36dc1d01e4d30f362f3c2 --- - +[![Neuron@v0.117.0](https://github.com/user-attachments/assets/7d2eba67-e33e-4fca-a714-7ba1709d8bd3)](https://youtu.be/zf78Y094m60) - +YouTube: https://youtu.be/zf78Y094m60 --- From 19b23bf847c8a5733c7088de7c70921608156a8e Mon Sep 17 00:00:00 2001 From: Chen Yu Date: Mon, 12 Aug 2024 18:59:40 +0900 Subject: [PATCH 20/23] fix: remove CRs when inject environment variables (#3236) --- .github/workflows/package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 10200e7ebb..126b544948 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -106,7 +106,7 @@ jobs: # Inject LOG_ENCRYPTION_PUBLIC_KEY to encrypt sensitive log - name: Inject environment variables run: | - echo "\nLOG_ENCRYPTION_PUBLIC_KEY=${{ secrets.LOG_ENCRYPTION_PUBLIC_KEY }}\n" >> packages/neuron-wallet/.env + echo "LOG_ENCRYPTION_PUBLIC_KEY=${{ secrets.LOG_ENCRYPTION_PUBLIC_KEY }}" >> packages/neuron-wallet/.env - name: Package for MacOS if: matrix.os == 'macos-latest' From 3c34ab3b931cdaa9853718bad6231f98567cca92 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 13 Aug 2024 15:45:12 +0900 Subject: [PATCH 21/23] Update ckb client versions (#3238) feat: update ckb client versions Co-authored-by: Keith-CY <7271329+Keith-CY@users.noreply.github.com> --- .ckb-light-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ckb-light-version b/.ckb-light-version index 534baa8df5..dd351eb971 100644 --- a/.ckb-light-version +++ b/.ckb-light-version @@ -1 +1 @@ -v0.3.7 +v0.3.8 From e9eab5ee3851106181675e4adc4a19b2b1fad044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com> Date: Wed, 14 Aug 2024 16:07:50 +0800 Subject: [PATCH 22/23] feat: Support load env from env file. (#3226) If we have set the env that includes scripts info, use the env value to replace the lumos config. --- README.md | 12 +++++ .../src/models/asset-account-info.ts | 4 +- .../src/utils/scriptAndAddress.ts | 6 +-- .../neuron-wallet/src/utils/systemScripts.ts | 46 ++++++++++++++++++- .../tests/models/asset-account-info.test.ts | 4 +- packages/neuron-wallet/tests/setup.ts | 26 +++++++---- .../tests/utils/systemScripts.test.ts | 15 ++++++ 7 files changed, 96 insertions(+), 17 deletions(-) create mode 100644 packages/neuron-wallet/tests/utils/systemScripts.test.ts diff --git a/README.md b/README.md index 8732315577..e953803473 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,18 @@ $ yarn start:ui $ yarn start:wallet ``` +If you want to develop with devnet, replace the testnet script with the dev script in the `packages/neuron-wallet/.env` file. + +Here is a script env example, it is constructed by network、script name and filed name. + +``` +TESTNET_SUDT_DEP_TXHASH=0xe12877ebd2c3c364dc46c5c992bcfaf4fee33fa13eebdf82c591fc9825aab769 +TESTNET_SUDT_DEP_INDEX=0 +TESTNET_SUDT_DEP_TYPE=code +TESTNET_SUDT_SCRIPT_CODEHASH=0xc5e5dcf215925f7ef4dfaf5f4b4f105bc321c02776d6e7d52a1db3fcd9d011a4 +TESTNET_SUDT_SCRIPT_HASHTYPE=type +``` + ### Test ```shell diff --git a/packages/neuron-wallet/src/models/asset-account-info.ts b/packages/neuron-wallet/src/models/asset-account-info.ts index 1cd76177ba..b4cb7a8d82 100644 --- a/packages/neuron-wallet/src/models/asset-account-info.ts +++ b/packages/neuron-wallet/src/models/asset-account-info.ts @@ -1,5 +1,4 @@ import { bytes, struct, createFixedBytesCodec } from '@ckb-lumos/lumos/codec' -import { predefined } from '@ckb-lumos/config-manager' import CellDep, { DepType } from './chain/cell-dep' import Script, { ScriptHashType } from './chain/script' import OutPoint from './chain/out-point' @@ -9,6 +8,7 @@ import SystemScriptInfo from './system-script-info' import { Address } from './address' import { UDTType } from '../utils/const' import { predefinedSporeConfigs, SporeConfig, SporeScript } from '@spore-sdk/core' +import { AGGRON4, LINA } from '../utils/systemScripts' const createFixedHexBytesCodec = (byteLength: number) => createFixedBytesCodec({ byteLength, pack: bytes.bytify, unpack: bytes.hexify }) @@ -48,7 +48,7 @@ export default class AssetAccountInfo { constructor(genesisBlockHash: string = NetworksService.getInstance().getCurrent().genesisHash) { const isMainnet = genesisBlockHash === AssetAccountInfo.MAINNET_GENESIS_BLOCK_HASH - const { XUDT, SUDT, ANYONE_CAN_PAY } = isMainnet ? predefined.LINA.SCRIPTS : predefined.AGGRON4.SCRIPTS + const { XUDT, SUDT, ANYONE_CAN_PAY } = isMainnet ? LINA.SCRIPTS : AGGRON4.SCRIPTS this.xudt = { cellDep: new CellDep(new OutPoint(XUDT.TX_HASH, XUDT.INDEX), XUDT.DEP_TYPE as DepType), codeHash: XUDT.CODE_HASH, diff --git a/packages/neuron-wallet/src/utils/scriptAndAddress.ts b/packages/neuron-wallet/src/utils/scriptAndAddress.ts index cbceca958c..035aad39af 100644 --- a/packages/neuron-wallet/src/utils/scriptAndAddress.ts +++ b/packages/neuron-wallet/src/utils/scriptAndAddress.ts @@ -1,7 +1,7 @@ import { type Script, hd } from '@ckb-lumos/lumos' -import { predefined } from '@ckb-lumos/lumos/config' import { encodeToAddress, parseAddress } from '@ckb-lumos/lumos/helpers' import { systemScripts } from './systemScripts' +import { AGGRON4, LINA } from '../utils/systemScripts' export enum DefaultAddressNumber { Change = 10, @@ -22,7 +22,7 @@ export const publicKeyToAddress = (publicKey: string, isMainnet = false) => { } export const scriptToAddress = (script: CKBComponents.Script, isMainnet = true): string => { - const lumosConfig = !isMainnet ? predefined.AGGRON4 : predefined.LINA + const lumosConfig = !isMainnet ? AGGRON4 : LINA return encodeToAddress( // omit keys other than codeHash, args and hashType { @@ -39,6 +39,6 @@ export const addressToScript = (address: string): Script => { if (prefix !== 'ckt' && prefix !== 'ckb') { throw new Error('Invalid address prefix') } - const lumosConfig = prefix === 'ckt' ? predefined.AGGRON4 : predefined.LINA + const lumosConfig = prefix === 'ckt' ? AGGRON4 : LINA return parseAddress(address, { config: lumosConfig }) } diff --git a/packages/neuron-wallet/src/utils/systemScripts.ts b/packages/neuron-wallet/src/utils/systemScripts.ts index d11165fe21..469491cfe5 100644 --- a/packages/neuron-wallet/src/utils/systemScripts.ts +++ b/packages/neuron-wallet/src/utils/systemScripts.ts @@ -1,4 +1,4 @@ -import { predefined } from '@ckb-lumos/lumos/config' +import { predefined, createConfig, type ScriptConfig } from '@ckb-lumos/config-manager' const systemScriptsMainnet = predefined.LINA.SCRIPTS const systemScriptsTestnet = predefined.AGGRON4.SCRIPTS @@ -25,3 +25,47 @@ export const systemScripts = { HASH_TYPE: systemScriptsTestnet.ANYONE_CAN_PAY.HASH_TYPE, }, } + +function getPredefinedFromEnv( + isMainnet: boolean, + envScriptName: 'SUDT' | 'ACP' | 'XUDT', + scriptConfigKey: keyof typeof predefined.LINA.SCRIPTS +): Partial | undefined { + const prefix = `${isMainnet ? 'MAINNET_' : 'TESTNET_'}${envScriptName}_` + const CODE_HASH = process.env[`${prefix}SCRIPT_CODEHASH`] + const HASH_TYPE = process.env[`${prefix}SCRIPT_HASHTYPE`] as ScriptConfig['HASH_TYPE'] + const TX_HASH = process.env[`${prefix}DEP_TXHASH`] + const INDEX = process.env[`${prefix}DEP_INDEX`] + const DEP_TYPE = process.env[`${prefix}DEP_TYPE`] as ScriptConfig['DEP_TYPE'] + if (CODE_HASH && HASH_TYPE && TX_HASH && INDEX && DEP_TYPE) { + return { + [scriptConfigKey]: { + CODE_HASH, + HASH_TYPE, + TX_HASH, + INDEX, + DEP_TYPE, + }, + } + } +} + +export const LINA = createConfig({ + PREFIX: predefined.LINA.PREFIX, + SCRIPTS: { + ...predefined.LINA.SCRIPTS, + ...getPredefinedFromEnv(true, 'SUDT', 'SUDT'), + ...getPredefinedFromEnv(true, 'XUDT', 'XUDT'), + ...getPredefinedFromEnv(true, 'ACP', 'ANYONE_CAN_PAY'), + }, +}) + +export const AGGRON4 = createConfig({ + PREFIX: predefined.AGGRON4.PREFIX, + SCRIPTS: { + ...predefined.AGGRON4.SCRIPTS, + ...getPredefinedFromEnv(false, 'SUDT', 'SUDT'), + ...getPredefinedFromEnv(false, 'XUDT', 'XUDT'), + ...getPredefinedFromEnv(false, 'ACP', 'ANYONE_CAN_PAY'), + }, +}) 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 7a21201fef..e869acaed7 100644 --- a/packages/neuron-wallet/tests/models/asset-account-info.test.ts +++ b/packages/neuron-wallet/tests/models/asset-account-info.test.ts @@ -1,4 +1,4 @@ -import { predefined } from '@ckb-lumos/config-manager' +import { AGGRON4 } from '../../src/utils/systemScripts' 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' @@ -8,7 +8,7 @@ import AddressMeta from '../../src/database/address/meta' const { AddressType } = hd describe('AssetAccountInfo', () => { - const { SUDT, ANYONE_CAN_PAY } = predefined.AGGRON4.SCRIPTS + const { SUDT, ANYONE_CAN_PAY } = AGGRON4.SCRIPTS const testnetSudtInfo = { cellDep: new CellDep(new OutPoint(SUDT.TX_HASH, SUDT.INDEX), SUDT.DEP_TYPE as DepType), codeHash: SUDT.CODE_HASH, diff --git a/packages/neuron-wallet/tests/setup.ts b/packages/neuron-wallet/tests/setup.ts index aaae2c253d..a0baee9a06 100644 --- a/packages/neuron-wallet/tests/setup.ts +++ b/packages/neuron-wallet/tests/setup.ts @@ -1,6 +1,5 @@ import '../src/locales/i18n' import { LedgerHID, LedgerCkbApp } from './mock/hardware' -import { systemScripts } from '../src/utils/systemScripts' jest.mock('levelup', () => { return () => ({ @@ -17,14 +16,18 @@ jest.mock('dotenv', () => ({ process.env.MAINNET_SUDT_DEP_TXHASH = '0x0000000000000000000000000000000000000000000000000000000000000000' process.env.MAINNET_SUDT_DEP_INDEX = '0' process.env.MAINNET_SUDT_DEP_TYPE = 'code' - process.env.MAINNET_SUDT_SCRIPT_CODEHASH = '0x0000000000000000000000000000000000000000000000000000000000000000' process.env.MAINNET_SUDT_SCRIPT_HASHTYPE = 'data' + process.env.MAINNET_XUDT_DEP_TXHASH = '0x0000000000000000000000000000000000000000000000000000000000000001' + process.env.MAINNET_XUDT_DEP_INDEX = '0' + process.env.MAINNET_XUDT_DEP_TYPE = 'code' + process.env.MAINNET_XUDT_SCRIPT_CODEHASH = '0x0000000000000000000000000000000000000000000000000000000000000001' + process.env.MAINNET_XUDT_SCRIPT_HASHTYPE = 'data' process.env.MAINNET_ACP_DEP_TXHASH = '0x0000000000000000000000000000000000000000000000000000000000000000' process.env.MAINNET_ACP_DEP_INDEX = '0' process.env.MAINNET_ACP_DEP_TYPE = 'code' - process.env.MAINNET_ACP_SCRIPT_CODEHASH = systemScripts.ANYONE_CAN_PAY_MAINNET.CODE_HASH - process.env.MAINNET_ACP_SCRIPT_HASHTYPE = systemScripts.ANYONE_CAN_PAY_MAINNET.HASH_TYPE + process.env.MAINNET_ACP_SCRIPT_CODEHASH = '0xd369597ff47f29fbc0d47d2e3775370d1250b85140c670e4718af712983a2354' + process.env.MAINNET_ACP_SCRIPT_HASHTYPE = 'type' process.env.LEGACY_MAINNET_ACP_DEP_TXHASH = '0x0000000000000000000000000000000000000000000000000000000000000001' process.env.LEGACY_MAINNET_ACP_DEP_INDEX = '0' @@ -59,12 +62,17 @@ jest.mock('dotenv', () => ({ process.env.TESTNET_SUDT_DEP_TYPE = 'code' process.env.TESTNET_SUDT_SCRIPT_CODEHASH = '0x48dbf59b4c7ee1547238021b4869bceedf4eea6b43772e5d66ef8865b6ae7212' process.env.TESTNET_SUDT_SCRIPT_HASHTYPE = 'data' + process.env.TESTNET_XUDT_DEP_TXHASH = '0x0000000000000000000000000000000000000000000000000000000000000011' + process.env.TESTNET_XUDT_DEP_INDEX = '0' + process.env.TESTNET_XUDT_DEP_TYPE = 'code' + process.env.TESTNET_XUDT_SCRIPT_CODEHASH = '0x0000000000000000000000000000000000000000000000000000000000000011' + process.env.TESTNET_XUDT_SCRIPT_HASHTYPE = 'data' process.env.TESTNET_ACP_DEP_TXHASH = '0x4f32b3e39bd1b6350d326fdfafdfe05e5221865c3098ae323096f0bfc69e0a8c' process.env.TESTNET_ACP_DEP_INDEX = '0' process.env.TESTNET_ACP_DEP_TYPE = 'depGroup' - process.env.TESTNET_ACP_SCRIPT_CODEHASH = systemScripts.ANYONE_CAN_PAY_TESTNET.CODE_HASH - process.env.TESTNET_ACP_SCRIPT_HASHTYPE = systemScripts.ANYONE_CAN_PAY_TESTNET.HASH_TYPE + process.env.TESTNET_ACP_SCRIPT_CODEHASH = '0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356' + process.env.TESTNET_ACP_SCRIPT_HASHTYPE = 'type' process.env.LEGACY_TESTNET_ACP_DEP_TXHASH = '0x0000000000000000000000000000000000000000000000000000000000000001' process.env.LEGACY_TESTNET_ACP_DEP_INDEX = '0' @@ -85,9 +93,9 @@ jest.mock('dotenv', () => ({ process.env.TESTNET_CHEQUE_SCRIPT_CODEHASH = '0x0000000000000000000000000000000000000000000000000000000000000003' process.env.TESTNET_CHEQUE_SCRIPT_HASHTYPE = 'type' - process.env.SECP256K1_CODE_HASH = systemScripts.SECP256K1_BLAKE160.CODE_HASH - process.env.DAO_CODE_HASH = systemScripts.DAO.CODE_HASH - process.env.MULTISIG_CODE_HASH = systemScripts.SECP256K1_BLAKE160_MULTISIG.CODE_HASH + process.env.SECP256K1_CODE_HASH = '0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8' + process.env.DAO_CODE_HASH = '0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e' + process.env.MULTISIG_CODE_HASH = '0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8' process.env.TESTNET_NFT_SCRIPT_CODEHASH = '0xb1837b5ad01a88558731953062d1f5cb547adf89ece01e8934a9f0aeed2d959f' process.env.TESTNET_NFT_SCRIPT_HASH_TYPE = 'type' diff --git a/packages/neuron-wallet/tests/utils/systemScripts.test.ts b/packages/neuron-wallet/tests/utils/systemScripts.test.ts new file mode 100644 index 0000000000..decdb0948e --- /dev/null +++ b/packages/neuron-wallet/tests/utils/systemScripts.test.ts @@ -0,0 +1,15 @@ +// Add import env to load env +import env from '../../src/env' +import { AGGRON4, LINA } from '../../src/utils/systemScripts' + +describe('Test env is loaded', () => { + it('test xudt', () => { + console.info(env.fileBasePath) + expect(LINA.SCRIPTS.XUDT.CODE_HASH).toBe('0x0000000000000000000000000000000000000000000000000000000000000001') + expect(AGGRON4.SCRIPTS.XUDT.CODE_HASH).toBe('0x0000000000000000000000000000000000000000000000000000000000000011') + }) + it('test sudt', () => { + expect(LINA.SCRIPTS.SUDT.CODE_HASH).toBe('0x48dbf59b4c7ee1547238021b4869bceedf4eea6b43772e5d66ef8865b6ae7212') + expect(AGGRON4.SCRIPTS.SUDT.CODE_HASH).toBe('0x48dbf59b4c7ee1547238021b4869bceedf4eea6b43772e5d66ef8865b6ae7212') + }) +}) From 5c11fff135151d51b6235e0a507161fd8ce1b8a1 Mon Sep 17 00:00:00 2001 From: devchenyan Date: Tue, 27 Aug 2024 08:25:13 +0800 Subject: [PATCH 23/23] fix: the remaining time display in Nervos DAO is abnormal (#3239) * fix: the remaining time display is abnormal * fix * fix * fix --- .../neuron-ui/src/components/NervosDAORecord/index.tsx | 8 ++++++-- packages/neuron-ui/src/locales/en.json | 3 ++- packages/neuron-ui/src/locales/es.json | 3 ++- packages/neuron-ui/src/locales/fr.json | 3 ++- packages/neuron-ui/src/locales/zh-tw.json | 3 ++- packages/neuron-ui/src/locales/zh.json | 3 ++- 6 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/neuron-ui/src/components/NervosDAORecord/index.tsx b/packages/neuron-ui/src/components/NervosDAORecord/index.tsx index 58ee5342cd..b031f61381 100644 --- a/packages/neuron-ui/src/components/NervosDAORecord/index.tsx +++ b/packages/neuron-ui/src/components/NervosDAORecord/index.tsx @@ -137,7 +137,9 @@ export const DAORecord = ({ break } case CellStatus.Deposited: { - if (leftHours) { + if (!compensationEndEpochValue) { + message = t('nervos-dao.compensation-period.stage-messages.calculating-the-compensation-cycle') + } else if (leftHours) { message = t('nervos-dao.compensation-period.stage-messages.next-compensation-cycle-hours', { hours: leftHours || '--', }) @@ -149,7 +151,9 @@ export const DAORecord = ({ break } case CellStatus.Locked: { - if (leftHours) { + if (!compensationEndEpochValue) { + message = t('nervos-dao.compensation-period.stage-messages.calculating-the-compensation-cycle') + } else if (leftHours) { message = t('nervos-dao.compensation-period.stage-messages.compensation-cycle-will-end-hours', { hours: leftHours || '--', }) diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index e9e8e74d39..dfd55fdb08 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -784,7 +784,8 @@ "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..." + "unlocking": "Unlocking...", + "calculating-the-compensation-cycle": "Calculating the compensation cycle" } } }, diff --git a/packages/neuron-ui/src/locales/es.json b/packages/neuron-ui/src/locales/es.json index 77c7261a60..0cb4932d15 100644 --- a/packages/neuron-ui/src/locales/es.json +++ b/packages/neuron-ui/src/locales/es.json @@ -767,7 +767,8 @@ "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..." + "unlocking": "Desbloqueando...", + "calculating-the-compensation-cycle": "Calculando el ciclo de compensación" } } }, diff --git a/packages/neuron-ui/src/locales/fr.json b/packages/neuron-ui/src/locales/fr.json index 28eedd730d..6d5890ad11 100644 --- a/packages/neuron-ui/src/locales/fr.json +++ b/packages/neuron-ui/src/locales/fr.json @@ -774,7 +774,8 @@ "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..." + "unlocking": "Déverrouillage en cours...", + "calculating-the-compensation-cycle": "Calculer le cycle de compensation" } } }, diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index 8fa1bbc4e2..8f96a5ed44 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -778,7 +778,8 @@ "compensation-cycle-will-end": "補償週期約在 {{days}} 天后結束", "compensation-cycle-will-end-hours": "補償週期約在 {{hours}} 小時后結束", "compensation-cycle-has-ended": "補償週期已結束, 可以解鎖 CKB", - "unlocking": "解鎖中..." + "unlocking": "解鎖中...", + "calculating-the-compensation-cycle": "正在計算補償週期" } } }, diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index b6e0b16b36..ed50683b81 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -777,7 +777,8 @@ "compensation-cycle-will-end": "补偿周期约在 {{days}} 天后结束", "compensation-cycle-will-end-hours": "补偿周期约在 {{hours}} 小时后结束", "compensation-cycle-has-ended": "补偿周期已结束, 可以解锁 CKB", - "unlocking": "解锁中..." + "unlocking": "解锁中...", + "calculating-the-compensation-cycle": "正在计算补偿周期" } } },