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", 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/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/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()]) 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"