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()])