diff --git a/packages/neuron-ui/src/components/HardwareSign/HardwareSignOnPage.tsx b/packages/neuron-ui/src/components/HardwareSign/HardwareSignOnPage.tsx new file mode 100644 index 0000000000..50793cdb1d --- /dev/null +++ b/packages/neuron-ui/src/components/HardwareSign/HardwareSignOnPage.tsx @@ -0,0 +1,111 @@ +import React from 'react' +import { useTranslation } from 'react-i18next' +import { useState as useGlobalState } from 'states' +import { errorFormatter, useGoBack } from 'utils' +import AlertDialog from 'widgets/AlertDialog' +import Button from 'widgets/Button' +import { Export, Sign } from 'widgets/Icons/icon' +import HDWalletSign from '../HDWalletSign' +import useHardwareSign, { HardwareSignProps } from './hooks' +import styles from './hardwareSign.module.scss' + +const HardwareSignOnPage = ({ + signType, + signMessage, + wallet, + onDismiss, + offlineSignJSON, + offlineSignType, + walletID, + actionType, + multisigConfig, + onSuccess, +}: HardwareSignProps & State.PasswordRequest) => { + const { + app: { + send: { description, generatedTx }, + loadings: { sending: isSending = false }, + }, + experimental, + } = useGlobalState() + const [t] = useTranslation() + const onGoBack = useGoBack() + const { + offlineSignActionType, + status, + error, + isLoading, + isNotAvailableToSign, + productName, + signAndExportFromGenerateTx, + sign, + reconnect, + exportTransaction, + } = useHardwareSign({ + signType, + signMessage, + wallet, + offlineSignJSON, + offlineSignType, + description, + generatedTx, + isSending, + passwordRequest: { walletID, actionType, multisigConfig, onSuccess }, + experimental, + }) + + if (error) { + return + } + + return ( +
+
+

+ {t('hardware-sign.device')} + {productName} +

+

+ {t('hardware-sign.status.label')} + {status} +

+ +
{wallet.isHD && generatedTx ? : null}
+ + {offlineSignJSON === undefined && signType === 'transaction' ? ( +
+ +
+ +
+ ) : null} +
+
+
+
+ ) +} + +HardwareSignOnPage.displayName = 'HardwareSignOnPage' + +export default HardwareSignOnPage diff --git a/packages/neuron-ui/src/components/HardwareSign/hardwareSign.module.scss b/packages/neuron-ui/src/components/HardwareSign/hardwareSign.module.scss index 1fe600f938..09baaec176 100644 --- a/packages/neuron-ui/src/components/HardwareSign/hardwareSign.module.scss +++ b/packages/neuron-ui/src/components/HardwareSign/hardwareSign.module.scss @@ -49,3 +49,15 @@ .warning { color: var(--error-color); } + +.hardwareSignInPage { + .container { + width: 100%; + } + .actions { + display: flex; + justify-content: center; + gap: 12px; + margin-top: 24px; + } +} diff --git a/packages/neuron-ui/src/components/HardwareSign/hooks.ts b/packages/neuron-ui/src/components/HardwareSign/hooks.ts new file mode 100644 index 0000000000..e3cdf418c8 --- /dev/null +++ b/packages/neuron-ui/src/components/HardwareSign/hooks.ts @@ -0,0 +1,425 @@ +import { CkbAppNotFoundException, DeviceNotFoundException } from 'exceptions' +import { useCallback, useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' +import { + DeviceInfo, + OfflineSignJSON, + OfflineSignStatus, + OfflineSignType, + connectDevice, + exportTransactionAsJSON, + getDeviceCkbAppVersion, + getDevices, + getPlatform, + signAndExportTransaction, + updateWallet, +} from 'services/remote' +import { ControllerResponse } from 'services/remote/remoteApiWrapper' +import { + AppActions, + migrateAcp, + sendCreateSUDTAccountTransaction, + sendSUDTTransaction, + sendTransaction, + useDispatch, +} from 'states' +import { ErrorCode, RoutePath, errorFormatter, isSuccessResponse, useDidMount } from 'utils' + +export type SignType = 'message' | 'transaction' + +export interface HardwareSignProps { + signType: SignType + wallet: State.WalletIdentity + offlineSignJSON?: OfflineSignJSON + offlineSignType?: OfflineSignType + signMessage?: (password: string) => Promise + onDismiss?: () => void +} + +export default ({ + signType, + signMessage, + wallet, + offlineSignJSON, + offlineSignType, + description, + generatedTx, + isSending, + passwordRequest, + experimental, + onCancel, +}: { + description: string + generatedTx: State.GeneratedTx | null + isSending: boolean + passwordRequest: State.PasswordRequest + experimental: State.Experimental | null + onCancel?: (dismiss: boolean) => void +} & Omit) => { + const { actionType = null, multisigConfig } = passwordRequest + const [t] = useTranslation() + const dispatch = useDispatch() + const navigate = useNavigate() + const isWin32 = useMemo(() => { + return getPlatform() === 'win32' + }, []) + const [status, setStatus] = useState('') + const connectStatus = t('hardware-sign.status.connect') + const userInputStatus = t('hardware-sign.status.user-input') + const disconnectStatus = t('hardware-sign.status.disconnect') + const ckbAppNotFoundStatus = t(CkbAppNotFoundException.message) + const isNotAvailableToSign = useMemo(() => { + return status === disconnectStatus || status === ckbAppNotFoundStatus + }, [status, disconnectStatus, ckbAppNotFoundStatus]) + const [error, setError] = useState('') + const [deviceInfo, setDeviceInfo] = useState(wallet.device!) + const [isReconnecting, setIsReconnecting] = useState(false) + const isLoading = useMemo(() => { + return status === userInputStatus || isReconnecting || isSending + }, [status, userInputStatus, isReconnecting]) + + const productName = `${wallet.device!.manufacturer} ${wallet.device!.product}` + + const offlineSignActionType = useMemo(() => { + switch (offlineSignJSON?.type) { + case OfflineSignType.CreateSUDTAccount: + return 'create-sudt-account' + case OfflineSignType.SendSUDT: + return 'send-sudt' + case OfflineSignType.UnlockDAO: + return 'unlock' + case OfflineSignType.SendFromMultisigOnlySig: + return 'send-from-multisig' + default: + return 'send' + } + }, [offlineSignJSON]) + + const signAndExportFromJSON = useCallback(async () => { + const res = await signAndExportTransaction({ + ...offlineSignJSON!, + walletID: wallet.id, + password: '', + }) + if (!isSuccessResponse(res)) { + setError(errorFormatter(res.message, t)) + return + } + if (res.result) { + dispatch({ + type: AppActions.UpdateLoadedTransaction, + payload: res.result!, + }) + } + onCancel?.(!!res.result) + }, [offlineSignJSON, dispatch, onCancel, t, wallet.id]) + + const signAndExportFromGenerateTx = useCallback(async () => { + setStatus(userInputStatus) + const json: OfflineSignJSON = { + transaction: generatedTx || experimental?.tx, + status: OfflineSignStatus.Signed, + type: offlineSignType!, + description, + asset_account: experimental?.assetAccount, + } + const res = await signAndExportTransaction({ + ...json, + walletID: wallet.id, + password: '', + multisigConfig, + }) + setStatus(connectStatus) + if (!isSuccessResponse(res)) { + setStatus(connectStatus) + setError(errorFormatter(res.message, t)) + return + } + if (res.result) { + dispatch({ + type: AppActions.UpdateLoadedTransaction, + payload: res.result!, + }) + } + onCancel?.(!!res.result) + }, [ + dispatch, + onCancel, + t, + wallet.id, + generatedTx, + userInputStatus, + description, + experimental, + offlineSignType, + connectStatus, + multisigConfig, + ]) + + const ensureDeviceAvailable = useCallback( + async (device: DeviceInfo) => { + try { + const connectionRes = await connectDevice(device) + let { descriptor } = device + if (!isSuccessResponse(connectionRes)) { + // for win32, opening or closing the ckb app changes the HID descriptor(deviceInfo), + // so if we can't connect to the device, we need to re-search device automatically. + // for unix, the descriptor never changes unless user plugs the device into another USB port, + // in that case, mannauly re-search device one time will do. + if (isWin32) { + setIsReconnecting(true) + const devicesRes = await getDevices(device) + setIsReconnecting(false) + if (isSuccessResponse(devicesRes) && Array.isArray(devicesRes.result) && devicesRes.result.length > 0) { + const [updatedDeviceInfo] = devicesRes.result + descriptor = updatedDeviceInfo.descriptor + setDeviceInfo(updatedDeviceInfo) + } else { + throw new DeviceNotFoundException() + } + } else { + throw new DeviceNotFoundException() + } + } + + // getDeviceCkbAppVersion will halt forever while in win32 sleep mode. + const ckbVersionRes = await Promise.race([ + getDeviceCkbAppVersion(descriptor), + new Promise((_, reject) => { + setTimeout(() => reject(), 1000) + }), + ]).catch(() => { + return { status: ErrorCode.DeviceInSleep } + }) + + if (!isSuccessResponse(ckbVersionRes)) { + if (ckbVersionRes.status !== ErrorCode.DeviceInSleep) { + throw new CkbAppNotFoundException() + } else { + throw new DeviceNotFoundException() + } + } + setStatus(connectStatus) + } catch (err) { + if (err instanceof CkbAppNotFoundException) { + setStatus(ckbAppNotFoundStatus) + } else { + setStatus(disconnectStatus) + } + } + }, + [connectStatus, disconnectStatus, ckbAppNotFoundStatus, isWin32] + ) + + const signTx = useCallback(async () => { + try { + await ensureDeviceAvailable(deviceInfo) + setStatus(userInputStatus) + const type = actionType || offlineSignActionType + const tx = offlineSignJSON?.transaction || generatedTx + // eslint-disable-next-line camelcase + const assetAccount = offlineSignJSON?.asset_account ?? experimental?.assetAccount + if (offlineSignJSON !== undefined) { + await signAndExportFromJSON() + return + } + switch (type) { + case 'send': + case 'send-nft': + case 'destroy-asset-account': + case 'send-cheque': + case 'claim-cheque': { + if (isSending) { + break + } + sendTransaction({ + walletID: wallet.id, + tx: tx || experimental?.tx, + description, + })(dispatch).then(res => { + if (isSuccessResponse(res)) { + navigate?.(RoutePath.History) + } else { + setError(res.message) + } + }) + break + } + case 'unlock': { + if (isSending) { + break + } + sendTransaction({ walletID: wallet.id, tx, description })(dispatch).then(res => { + if (isSuccessResponse(res)) { + navigate?.(RoutePath.History) + } else { + setError(res.message) + } + }) + break + } + case 'create-sudt-account': + case 'create-account-to-claim-cheque': { + const params: Controller.SendCreateSUDTAccountTransaction.Params = { + walletID: wallet.id, + assetAccount, + tx: tx || experimental?.tx, + } + sendCreateSUDTAccountTransaction(params)(dispatch).then(res => { + if (isSuccessResponse(res)) { + navigate?.(RoutePath.History) + } else { + setError(res.message) + } + }) + break + } + case 'send-ckb-asset': + case 'send-acp-sudt-to-new-cell': + case 'send-acp-ckb-to-new-cell': + case 'send-sudt': { + let skipLastInputs = true + if (actionType === 'send-acp-sudt-to-new-cell' || actionType === 'send-acp-ckb-to-new-cell') { + skipLastInputs = false + } + const params: Controller.SendSUDTTransaction.Params = { + walletID: wallet.id, + tx: tx || experimental?.tx, + skipLastInputs, + } + sendSUDTTransaction(params)(dispatch).then(res => { + if (isSuccessResponse(res)) { + navigate?.(RoutePath.History) + } else { + setError(res.message) + } + }) + break + } + case 'migrate-acp': { + await migrateAcp({ id: wallet.id })(dispatch).then(res => { + if (isSuccessResponse(res)) { + navigate?.(RoutePath.History) + } else { + setError(typeof res.message === 'string' ? res.message : res.message.content ?? 'migrate-acp error') + } + }) + break + } + case 'send-from-multisig-need-one': { + if (isSending) { + break + } + await sendTransaction({ walletID: wallet.id, tx: generatedTx, description, multisigConfig })(dispatch).then( + res => { + if (!isSuccessResponse(res)) { + setError(res.message.content) + } + } + ) + break + } + default: { + break + } + } + } catch (err) { + setStatus(disconnectStatus) + } + }, [ + actionType, + offlineSignActionType, + userInputStatus, + disconnectStatus, + experimental, + generatedTx, + offlineSignJSON, + isSending, + deviceInfo, + wallet.id, + description, + dispatch, + navigate, + signAndExportFromJSON, + ensureDeviceAvailable, + multisigConfig, + ]) + + const signMsg = useCallback(async () => { + await ensureDeviceAvailable(deviceInfo) + setStatus(userInputStatus) + await signMessage?.('') + }, [ensureDeviceAvailable, signMessage, deviceInfo, userInputStatus]) + + const sign = useCallback( + async (e?: React.FormEvent) => { + if (e) { + e.preventDefault() + } + if (signType === 'message') { + await signMsg() + } else { + await signTx() + } + }, + [signType, signTx, signMsg] + ) + + const reconnect = useCallback(async () => { + setIsReconnecting(true) + try { + const res = await getDevices(deviceInfo) + if (isSuccessResponse(res) && Array.isArray(res.result) && res.result.length > 0) { + const [device] = res.result + setDeviceInfo(device) + if (device.descriptor !== deviceInfo.descriptor) { + await updateWallet({ + id: wallet.id, + device, + }) + } + await ensureDeviceAvailable(device) + } + } catch (err) { + setStatus(disconnectStatus) + } finally { + setIsReconnecting(false) + } + }, [deviceInfo, disconnectStatus, ensureDeviceAvailable, wallet.id]) + + const exportTransaction = useCallback(async () => { + const res = await exportTransactionAsJSON({ + transaction: generatedTx || experimental?.tx, + status: OfflineSignStatus.Unsigned, + type: offlineSignType!, + description, + asset_account: experimental?.assetAccount, + }) + if (!isSuccessResponse(res)) { + setError(errorFormatter(res.message, t)) + return + } + onCancel?.(!!res.result) + }, [offlineSignType, generatedTx, onCancel, description, experimental]) + + useDidMount(() => { + ensureDeviceAvailable(deviceInfo) + }) + + return { + offlineSignActionType, + status, + error, + isLoading, + isNotAvailableToSign, + productName, + signAndExportFromJSON, + signAndExportFromGenerateTx, + signTx, + signMsg, + sign, + reconnect, + exportTransaction, + } +} diff --git a/packages/neuron-ui/src/components/HardwareSign/index.tsx b/packages/neuron-ui/src/components/HardwareSign/index.tsx index 49c8e7264e..937d9b084f 100644 --- a/packages/neuron-ui/src/components/HardwareSign/index.tsx +++ b/packages/neuron-ui/src/components/HardwareSign/index.tsx @@ -1,59 +1,31 @@ -import React, { useCallback, useState, useMemo } from 'react' -import { useTranslation } from 'react-i18next' -import { - useState as useGlobalState, - useDispatch, - sendTransaction, - sendCreateSUDTAccountTransaction, - sendSUDTTransaction, - AppActions, - migrateAcp, -} from 'states' -import { ControllerResponse } from 'services/remote/remoteApiWrapper' -import { NavigateFunction } from 'react-router-dom' -import { - connectDevice, - getDevices, - exportTransactionAsJSON, - OfflineSignStatus, - OfflineSignType, - OfflineSignJSON, - signAndExportTransaction, - getDeviceCkbAppVersion, - DeviceInfo, - updateWallet, - getPlatform, -} from 'services/remote' -import { ErrorCode, errorFormatter, isSuccessResponse, RoutePath, useDidMount } from 'utils' +import React, { useCallback } from 'react' +import { AppActions, useDispatch, useState as useGlobalState } from 'states' +import { errorFormatter } from 'utils' import Dialog from 'widgets/Dialog' import AlertDialog from 'widgets/AlertDialog' import Button from 'widgets/Button' import { Export, Sign } from 'widgets/Icons/icon' -import { CkbAppNotFoundException, DeviceNotFoundException } from 'exceptions' +import { useTranslation } from 'react-i18next' import HDWalletSign from '../HDWalletSign' import styles from './hardwareSign.module.scss' - -export type SignType = 'message' | 'transaction' - -export interface HardwareSignProps { - signType: SignType - wallet: State.WalletIdentity - offlineSignJSON?: OfflineSignJSON - offlineSignType?: OfflineSignType - onDismiss: () => void - signMessage?: (password: string) => Promise - navigate?: NavigateFunction -} +import useHardwareSign, { HardwareSignProps } from './hooks' const HardwareSign = ({ signType, signMessage, - navigate, wallet, onDismiss, offlineSignJSON, offlineSignType, }: HardwareSignProps) => { + const { + app: { + send: { description, generatedTx }, + loadings: { sending: isSending = false }, + passwordRequest, + }, + experimental, + } = useGlobalState() const [t] = useTranslation() const dispatch = useDispatch() const onCancel = useCallback( @@ -65,366 +37,35 @@ const HardwareSign = ({ }) } if (dismiss) { - onDismiss() + onDismiss?.() } }, [dispatch, signType, onDismiss] ) - const isWin32 = useMemo(() => { - return getPlatform() === 'win32' - }, []) - const [status, setStatus] = useState('') - const connectStatus = t('hardware-sign.status.connect') - const userInputStatus = t('hardware-sign.status.user-input') - const disconnectStatus = t('hardware-sign.status.disconnect') - const ckbAppNotFoundStatus = t(CkbAppNotFoundException.message) - const isNotAvailableToSign = useMemo(() => { - return status === disconnectStatus || status === ckbAppNotFoundStatus - }, [status, disconnectStatus, ckbAppNotFoundStatus]) - const { - app: { - send: { description, generatedTx }, - loadings: { sending: isSending = false }, - passwordRequest: { actionType = null, multisigConfig }, - }, - experimental, - } = useGlobalState() - const [error, setError] = useState('') - const [deviceInfo, setDeviceInfo] = useState(wallet.device!) - const [isReconnecting, setIsReconnecting] = useState(false) - const isLoading = useMemo(() => { - return status === userInputStatus || isReconnecting || isSending - }, [status, userInputStatus, isReconnecting]) - - const productName = `${wallet.device!.manufacturer} ${wallet.device!.product}` - - const offlineSignActionType = useMemo(() => { - switch (offlineSignJSON?.type) { - case OfflineSignType.CreateSUDTAccount: - return 'create-sudt-account' - case OfflineSignType.SendSUDT: - return 'send-sudt' - case OfflineSignType.UnlockDAO: - return 'unlock' - case OfflineSignType.SendFromMultisigOnlySig: - return 'send-from-multisig' - default: - return 'send' - } - }, [offlineSignJSON]) - - const signAndExportFromJSON = useCallback(async () => { - const res = await signAndExportTransaction({ - ...offlineSignJSON!, - walletID: wallet.id, - password: '', - }) - if (!isSuccessResponse(res)) { - setError(errorFormatter(res.message, t)) - return - } - if (res.result) { - dispatch({ - type: AppActions.UpdateLoadedTransaction, - payload: res.result!, - }) - } - onCancel(!!res.result) - }, [offlineSignJSON, dispatch, onCancel, t, wallet.id]) - - const signAndExportFromGenerateTx = useCallback(async () => { - setStatus(userInputStatus) - const json: OfflineSignJSON = { - transaction: generatedTx || experimental?.tx, - status: OfflineSignStatus.Signed, - type: offlineSignType!, - description, - asset_account: experimental?.assetAccount, - } - const res = await signAndExportTransaction({ - ...json, - walletID: wallet.id, - password: '', - multisigConfig, - }) - setStatus(connectStatus) - if (!isSuccessResponse(res)) { - setStatus(connectStatus) - setError(errorFormatter(res.message, t)) - return - } - if (res.result) { - dispatch({ - type: AppActions.UpdateLoadedTransaction, - payload: res.result!, - }) - } - onCancel(!!res.result) - }, [ - dispatch, - onCancel, - t, - wallet.id, - generatedTx, - userInputStatus, - description, - experimental, - offlineSignType, - connectStatus, - multisigConfig, - ]) - - const ensureDeviceAvailable = useCallback( - async (device: DeviceInfo) => { - try { - const connectionRes = await connectDevice(device) - let { descriptor } = device - if (!isSuccessResponse(connectionRes)) { - // for win32, opening or closing the ckb app changes the HID descriptor(deviceInfo), - // so if we can't connect to the device, we need to re-search device automatically. - // for unix, the descriptor never changes unless user plugs the device into another USB port, - // in that case, mannauly re-search device one time will do. - if (isWin32) { - setIsReconnecting(true) - const devicesRes = await getDevices(device) - setIsReconnecting(false) - if (isSuccessResponse(devicesRes) && Array.isArray(devicesRes.result) && devicesRes.result.length > 0) { - const [updatedDeviceInfo] = devicesRes.result - descriptor = updatedDeviceInfo.descriptor - setDeviceInfo(updatedDeviceInfo) - } else { - throw new DeviceNotFoundException() - } - } else { - throw new DeviceNotFoundException() - } - } - - // getDeviceCkbAppVersion will halt forever while in win32 sleep mode. - const ckbVersionRes = await Promise.race([ - getDeviceCkbAppVersion(descriptor), - new Promise((_, reject) => { - setTimeout(() => reject(), 1000) - }), - ]).catch(() => { - return { status: ErrorCode.DeviceInSleep } - }) - - if (!isSuccessResponse(ckbVersionRes)) { - if (ckbVersionRes.status !== ErrorCode.DeviceInSleep) { - throw new CkbAppNotFoundException() - } else { - throw new DeviceNotFoundException() - } - } - setStatus(connectStatus) - } catch (err) { - if (err instanceof CkbAppNotFoundException) { - setStatus(ckbAppNotFoundStatus) - } else { - setStatus(disconnectStatus) - } - } - }, - [connectStatus, disconnectStatus, ckbAppNotFoundStatus, isWin32] - ) - - const signTx = useCallback(async () => { - try { - await ensureDeviceAvailable(deviceInfo) - setStatus(userInputStatus) - const type = actionType || offlineSignActionType - const tx = offlineSignJSON?.transaction || generatedTx - // eslint-disable-next-line camelcase - const assetAccount = offlineSignJSON?.asset_account ?? experimental?.assetAccount - if (offlineSignJSON !== undefined) { - await signAndExportFromJSON() - return - } - switch (type) { - case 'send': - case 'send-nft': - case 'destroy-asset-account': - case 'send-cheque': - case 'claim-cheque': { - if (isSending) { - break - } - sendTransaction({ - walletID: wallet.id, - tx: tx || experimental?.tx, - description, - })(dispatch).then(res => { - if (isSuccessResponse(res)) { - navigate?.(RoutePath.History) - } else { - setError(res.message) - } - }) - break - } - case 'unlock': { - if (isSending) { - break - } - sendTransaction({ walletID: wallet.id, tx, description })(dispatch).then(res => { - if (isSuccessResponse(res)) { - navigate?.(RoutePath.History) - } else { - setError(res.message) - } - }) - break - } - case 'create-sudt-account': - case 'create-account-to-claim-cheque': { - const params: Controller.SendCreateSUDTAccountTransaction.Params = { - walletID: wallet.id, - assetAccount, - tx: tx || experimental?.tx, - } - sendCreateSUDTAccountTransaction(params)(dispatch).then(res => { - if (isSuccessResponse(res)) { - navigate?.(RoutePath.History) - } else { - setError(res.message) - } - }) - break - } - case 'send-ckb-asset': - case 'send-acp-sudt-to-new-cell': - case 'send-acp-ckb-to-new-cell': - case 'send-sudt': { - let skipLastInputs = true - if (actionType === 'send-acp-sudt-to-new-cell' || actionType === 'send-acp-ckb-to-new-cell') { - skipLastInputs = false - } - const params: Controller.SendSUDTTransaction.Params = { - walletID: wallet.id, - tx: tx || experimental?.tx, - skipLastInputs, - } - sendSUDTTransaction(params)(dispatch).then(res => { - if (isSuccessResponse(res)) { - navigate?.(RoutePath.History) - } else { - setError(res.message) - } - }) - break - } - case 'migrate-acp': { - await migrateAcp({ id: wallet.id })(dispatch).then(res => { - if (isSuccessResponse(res)) { - navigate?.(RoutePath.History) - } else { - setError(typeof res.message === 'string' ? res.message : res.message.content ?? 'migrate-acp error') - } - }) - break - } - case 'send-from-multisig-need-one': { - if (isSending) { - break - } - await sendTransaction({ walletID: wallet.id, tx: generatedTx, description, multisigConfig })(dispatch).then( - res => { - if (!isSuccessResponse(res)) { - setError(res.message.content) - } - } - ) - break - } - default: { - break - } - } - } catch (err) { - setStatus(disconnectStatus) - } - }, [ - actionType, offlineSignActionType, - userInputStatus, - disconnectStatus, - experimental, - generatedTx, + status, + error, + isLoading, + isNotAvailableToSign, + productName, + signAndExportFromGenerateTx, + sign, + reconnect, + exportTransaction, + } = useHardwareSign({ + signType, + signMessage, + wallet, offlineSignJSON, - isSending, - deviceInfo, - wallet.id, + offlineSignType, description, - dispatch, - navigate, - signAndExportFromJSON, - ensureDeviceAvailable, - multisigConfig, - ]) - - const signMsg = useCallback(async () => { - await ensureDeviceAvailable(deviceInfo) - setStatus(userInputStatus) - await signMessage?.('') - }, [ensureDeviceAvailable, signMessage, deviceInfo, userInputStatus]) - - const sign = useCallback( - async (e?: React.FormEvent) => { - if (e) { - e.preventDefault() - } - if (signType === 'message') { - await signMsg() - } else { - await signTx() - } - }, - [signType, signTx, signMsg] - ) - - const reconnect = useCallback(async () => { - setIsReconnecting(true) - try { - const res = await getDevices(deviceInfo) - if (isSuccessResponse(res) && Array.isArray(res.result) && res.result.length > 0) { - const [device] = res.result - setDeviceInfo(device) - if (device.descriptor !== deviceInfo.descriptor) { - await updateWallet({ - id: wallet.id, - device, - }) - } - await ensureDeviceAvailable(device) - } - } catch (err) { - setStatus(disconnectStatus) - } finally { - setIsReconnecting(false) - } - }, [deviceInfo, disconnectStatus, ensureDeviceAvailable, wallet.id]) - - const exportTransaction = useCallback(async () => { - const res = await exportTransactionAsJSON({ - transaction: generatedTx || experimental?.tx, - status: OfflineSignStatus.Unsigned, - type: offlineSignType!, - description, - asset_account: experimental?.assetAccount, - }) - if (!isSuccessResponse(res)) { - setError(errorFormatter(res.message, t)) - return - } - onCancel(!!res.result) - }, [offlineSignType, generatedTx, onCancel, description, experimental]) - - useDidMount(() => { - ensureDeviceAvailable(deviceInfo) + generatedTx, + isSending, + passwordRequest, + experimental, + onCancel, }) - if (error) { return } @@ -434,7 +75,7 @@ const HardwareSign = ({ show title={t('hardware-sign.title')} onCancel={onCancel} - showConfirm={(actionType || offlineSignActionType) !== 'send-from-multisig'} + showConfirm={(passwordRequest.actionType || offlineSignActionType) !== 'send-from-multisig'} confirmText={isNotAvailableToSign ? t('hardware-sign.actions.rescan') : t('sign-and-verify.sign')} isLoading={isLoading} onConfirm={isNotAvailableToSign ? reconnect : sign} diff --git a/packages/neuron-ui/src/components/MultisigAddress/index.tsx b/packages/neuron-ui/src/components/MultisigAddress/index.tsx index d6d38e702f..262fbe8596 100644 --- a/packages/neuron-ui/src/components/MultisigAddress/index.tsx +++ b/packages/neuron-ui/src/components/MultisigAddress/index.tsx @@ -12,7 +12,6 @@ import MultisigAddressCreateDialog from 'components/MultisigAddressCreateDialog' import MultisigAddressInfo from 'components/MultisigAddressInfo' import SendFromMultisigDialog from 'components/SendFromMultisigDialog' import { MultisigConfig } from 'services/remote' -import PasswordRequest from 'components/PasswordRequest' import ApproveMultisigTxDialog from 'components/ApproveMultisigTxDialog' import Dialog from 'widgets/Dialog' import Table from 'widgets/Table' @@ -383,7 +382,6 @@ const MultisigAddress = () => { isMainnet={isMainnet} /> ) : null} -
) } diff --git a/packages/neuron-ui/src/components/OfflineSignDialog/index.tsx b/packages/neuron-ui/src/components/OfflineSignDialog/index.tsx index ee497aeb65..a1f3179c6e 100644 --- a/packages/neuron-ui/src/components/OfflineSignDialog/index.tsx +++ b/packages/neuron-ui/src/components/OfflineSignDialog/index.tsx @@ -188,13 +188,7 @@ const OfflineSignDialog = ({ isBroadcast, wallet, offlineSignJSON, onDismiss }: if (wallet.device) { return ( - + ) } diff --git a/packages/neuron-ui/src/components/PasswordRequest/PasswordRequestInPage.tsx b/packages/neuron-ui/src/components/PasswordRequest/PasswordRequestInPage.tsx new file mode 100644 index 0000000000..4f12d12088 --- /dev/null +++ b/packages/neuron-ui/src/components/PasswordRequest/PasswordRequestInPage.tsx @@ -0,0 +1,124 @@ +import React from 'react' +import Button from 'widgets/Button' +import TextField from 'widgets/TextField' +import { ReactComponent as Attention } from 'widgets/Icons/ExperimentalAttention.svg' +import HardwareSignOnPage from 'components/HardwareSign/HardwareSignOnPage' +import { Export, Sign } from 'widgets/Icons/icon' +import { useState as useGlobalState } from 'states' +import { OfflineSignType } from 'services/remote' +import { useTranslation } from 'react-i18next' +import { useGoBack } from 'utils' +import usePasswordResuest from './hooks' +import styles from './passwordRequest.module.scss' + +const PasswordRequestInPage = ({ + walletID = '', + actionType = null, + multisigConfig, + onSuccess, + onCancel, +}: State.PasswordRequest & { onCancel?: () => void }) => { + const { + app: { + send: { description, generatedTx }, + loadings: { sending: isSending = false }, + }, + settings: { wallets = [] }, + experimental, + } = useGlobalState() + + const [t] = useTranslation() + const onGoBack = useGoBack() + + const { + error, + wallet, + isLoading, + signType, + disabled, + password, + onSubmit, + onChange, + exportTransaction, + signAndExportFromGenerateTx, + } = usePasswordResuest({ + description, + generatedTx, + isSending, + passwordRequest: { walletID, actionType, multisigConfig, onSuccess }, + wallets, + experimental, + }) + + if (!wallet) { + return null + } + + if (wallet.device) { + return ( + + ) + } + + return ( +
+ {wallet.isWatchOnly ? ( +
+ + {t('password-request.xpub-notice')} +
+ ) : ( + + )} + {signType !== OfflineSignType.Invalid ? ( +
+ + {!wallet.isWatchOnly && ( + <> +
+ + + )} +
+ ) : null} +
+
+
+ ) +} + +PasswordRequestInPage.displayName = 'PasswordRequestInPage' +export default PasswordRequestInPage diff --git a/packages/neuron-ui/src/components/PasswordRequest/hooks.ts b/packages/neuron-ui/src/components/PasswordRequest/hooks.ts new file mode 100644 index 0000000000..db51eb2835 --- /dev/null +++ b/packages/neuron-ui/src/components/PasswordRequest/hooks.ts @@ -0,0 +1,366 @@ +import { PasswordIncorrectException } from 'exceptions' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' +import { + OfflineSignStatus, + OfflineSignType, + exportTransactionAsJSON, + invokeShowErrorMessage, + requestOpenInExplorer, + signAndExportTransaction, +} from 'services/remote' +import { + AppActions, + backupWallet, + deleteWallet, + migrateAcp, + sendCreateSUDTAccountTransaction, + sendSUDTTransaction, + sendTransaction, + useDispatch, +} from 'states' +import { ErrorCode, RoutePath, errorFormatter, isSuccessResponse } from 'utils' + +export default ({ + description, + generatedTx, + isSending, + passwordRequest, + wallets, + experimental, + onDismiss, +}: { + description: string + generatedTx: State.GeneratedTx | null + isSending: boolean + passwordRequest: State.PasswordRequest + wallets: State.WalletIdentity[] + experimental: State.Experimental | null + onDismiss?: () => void +}) => { + const dispatch = useDispatch() + const [t] = useTranslation() + const navigate = useNavigate() + + const [password, setPassword] = useState('') + const [error, setError] = useState('') + const { walletID = '', actionType = null, multisigConfig, onSuccess } = passwordRequest + + useEffect(() => { + setPassword('') + setError('') + }, [actionType, setError, setPassword]) + + const signType = useMemo(() => { + switch (actionType) { + case 'create-sudt-account': + return OfflineSignType.CreateSUDTAccount + case 'send-ckb-asset': + case 'send-acp-ckb-to-new-cell': + case 'send-acp-sudt-to-new-cell': + case 'transfer-to-sudt': + case 'send-sudt': + return OfflineSignType.SendSUDT + case 'unlock': + return OfflineSignType.UnlockDAO + case 'send-nft': + case 'send-from-multisig-need-one': + case 'send': + return OfflineSignType.Regular + case 'send-from-multisig': + return OfflineSignType.SendFromMultisigOnlySig + default: + return OfflineSignType.Invalid + } + }, [actionType]) + + const exportTransaction = useCallback(async () => { + const res = await exportTransactionAsJSON({ + transaction: generatedTx || experimental?.tx, + status: OfflineSignStatus.Unsigned, + type: signType, + description, + asset_account: experimental?.assetAccount, + }) + if (!isSuccessResponse(res)) { + setError(errorFormatter(res.message, t)) + return + } + if (res.result) { + onDismiss?.() + } + }, [signType, generatedTx, onDismiss, description, experimental]) + + const wallet = useMemo(() => wallets.find(w => w.id === walletID), [walletID, wallets]) + + const isLoading = + [ + 'send', + 'unlock', + 'create-sudt-account', + 'send-sudt', + 'transfer-to-sudt', + 'send-ckb-asset', + 'send-acp-ckb-to-new-cell', + 'send-acp-sudt-to-new-cell', + 'send-cheque', + 'withdraw-cheque', + 'claim-cheque', + 'create-account-to-claim-cheque', + 'send-from-multisig-need-one', + 'send-from-multisig', + 'destroy-asset-account', + ].includes(actionType || '') && isSending + const disabled = !password || isSending + + const onSubmit = useCallback( + async (e?: React.FormEvent) => { + if (e) { + e.preventDefault() + } + if (disabled) { + return + } + const handleSendTxRes = ({ status }: { status: number }) => { + if (isSuccessResponse({ status })) { + if (onSuccess) { + onSuccess() + return + } + navigate(RoutePath.History) + } else if (status === ErrorCode.PasswordIncorrect) { + throw new PasswordIncorrectException() + } + } + try { + switch (actionType) { + case 'send': { + if (isSending) { + break + } + await sendTransaction({ walletID, tx: generatedTx, description, password })(dispatch).then(handleSendTxRes) + break + } + case 'send-from-multisig-need-one': { + if (isSending) { + break + } + await sendTransaction({ walletID, tx: generatedTx, description, password, multisigConfig })(dispatch).then( + (res: { result?: string; status: number; message: string | { content: string } }) => { + if (isSuccessResponse(res)) { + requestOpenInExplorer({ type: 'transaction', key: res.result }) + } else if (res.status === ErrorCode.PasswordIncorrect) { + throw new PasswordIncorrectException() + } else { + invokeShowErrorMessage({ + title: t('messages.error'), + content: typeof res.message === 'string' ? res.message : res.message.content!, + }) + } + } + ) + break + } + case 'delete': { + await deleteWallet({ id: walletID, password })(dispatch).then(status => { + if (status === ErrorCode.PasswordIncorrect) { + throw new PasswordIncorrectException() + } + }) + break + } + case 'backup': { + await backupWallet({ id: walletID, password })(dispatch).then(status => { + if (status === ErrorCode.PasswordIncorrect) { + throw new PasswordIncorrectException() + } + }) + break + } + case 'migrate-acp': { + await migrateAcp({ id: walletID, password })(dispatch).then(({ status }) => { + if (isSuccessResponse({ status })) { + navigate(RoutePath.History) + } else if (status === ErrorCode.PasswordIncorrect) { + throw new PasswordIncorrectException() + } + }) + break + } + case 'unlock': { + if (isSending) { + break + } + await sendTransaction({ walletID, tx: generatedTx, description, password })(dispatch).then(({ status }) => { + if (isSuccessResponse({ status })) { + dispatch({ + type: AppActions.SetGlobalDialog, + payload: 'unlock-success', + }) + onSuccess?.() + } else if (status === ErrorCode.PasswordIncorrect) { + throw new PasswordIncorrectException() + } + }) + break + } + case 'create-sudt-account': { + const params: Controller.SendCreateSUDTAccountTransaction.Params = { + walletID, + assetAccount: experimental?.assetAccount, + tx: experimental?.tx, + password, + } + await sendCreateSUDTAccountTransaction(params)(dispatch).then(handleSendTxRes) + break + } + case 'send-ckb-asset': + case 'send-acp-ckb-to-new-cell': + case 'send-acp-sudt-to-new-cell': + case 'send-sudt': + case 'transfer-to-sudt': { + let skipLastInputs = true + if (actionType === 'send-acp-sudt-to-new-cell' || actionType === 'send-acp-ckb-to-new-cell') { + skipLastInputs = false + } + const params: Controller.SendSUDTTransaction.Params = { + walletID, + tx: experimental?.tx, + password, + skipLastInputs, + } + await sendSUDTTransaction(params)(dispatch).then(handleSendTxRes) + break + } + case 'destroy-asset-account': + case 'send-nft': + case 'send-cheque': { + if (isSending) { + break + } + await sendTransaction({ + walletID, + tx: experimental?.tx, + description: experimental?.tx?.description, + password, + })(dispatch).then(handleSendTxRes) + break + } + case 'claim-cheque': { + if (isSending) { + break + } + await sendTransaction({ walletID, tx: experimental?.tx, password })(dispatch).then(handleSendTxRes) + break + } + case 'create-account-to-claim-cheque': { + if (isSending) { + break + } + await sendCreateSUDTAccountTransaction({ + walletID, + password, + tx: experimental?.tx, + assetAccount: { + ...experimental?.assetAccount, + tokenID: experimental?.assetAccount.tokenId, + balance: '0', + }, + })(dispatch).then(handleSendTxRes) + break + } + case 'withdraw-cheque': { + if (isSending) { + break + } + await sendTransaction({ walletID, tx: experimental?.tx, password })(dispatch).then(handleSendTxRes) + break + } + default: { + break + } + } + } catch (err) { + if (err instanceof PasswordIncorrectException) { + setError(t(err.message)) + } + } + }, + [ + dispatch, + walletID, + password, + actionType, + description, + navigate, + isSending, + generatedTx, + disabled, + experimental, + setError, + t, + multisigConfig, + ] + ) + + const onChange = useCallback( + (e: React.SyntheticEvent) => { + const { value } = e.target as HTMLInputElement + setPassword(value) + setError('') + }, + [setPassword, setError] + ) + + const signAndExportFromGenerateTx = useCallback(async () => { + dispatch({ + type: AppActions.UpdateLoadings, + payload: { + sending: true, + }, + }) + const json = { + transaction: generatedTx || experimental?.tx, + status: OfflineSignStatus.Signed, + type: signType, + description, + asset_account: experimental?.assetAccount, + } + const res = await signAndExportTransaction({ + ...json, + walletID, + password, + multisigConfig, + }) + dispatch({ + type: AppActions.UpdateLoadings, + payload: { sending: false }, + }) + if (!isSuccessResponse(res)) { + setError(errorFormatter(res.message, t)) + return + } + if (res.result) { + dispatch({ + type: AppActions.UpdateLoadedTransaction, + payload: res.result!, + }) + onDismiss?.() + } + }, [description, dispatch, experimental, generatedTx, onDismiss, password, signType, t, walletID, multisigConfig]) + + return { + error, + wallet, + isLoading, + signType, + actionType, + disabled, + password, + onSubmit, + onChange, + exportTransaction, + signAndExportFromGenerateTx, + } +} diff --git a/packages/neuron-ui/src/components/PasswordRequest/index.tsx b/packages/neuron-ui/src/components/PasswordRequest/index.tsx index e653f84423..1310ccc45f 100644 --- a/packages/neuron-ui/src/components/PasswordRequest/index.tsx +++ b/packages/neuron-ui/src/components/PasswordRequest/index.tsx @@ -1,34 +1,14 @@ -import React, { useState, useCallback, useMemo, useEffect } from 'react' -import { useNavigate } from 'react-router-dom' +import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Button from 'widgets/Button' import TextField from 'widgets/TextField' import HardwareSign from 'components/HardwareSign' import { ReactComponent as Attention } from 'widgets/Icons/ExperimentalAttention.svg' -import { ErrorCode, RoutePath, isSuccessResponse, errorFormatter } from 'utils' import Dialog from 'widgets/Dialog' import { Export, Sign } from 'widgets/Icons/icon' - -import { - useState as useGlobalState, - useDispatch, - AppActions, - sendTransaction, - deleteWallet, - backupWallet, - migrateAcp, - sendCreateSUDTAccountTransaction, - sendSUDTTransaction, -} from 'states' -import { - exportTransactionAsJSON, - OfflineSignStatus, - OfflineSignType, - signAndExportTransaction, - requestOpenInExplorer, - invokeShowErrorMessage, -} from 'services/remote' -import { PasswordIncorrectException } from 'exceptions' +import { AppActions, useDispatch, useState as useGlobalState } from 'states' +import { OfflineSignType } from 'services/remote' +import usePasswordResuest from './hooks' import styles from './passwordRequest.module.scss' const PasswordRequest = () => { @@ -36,343 +16,47 @@ const PasswordRequest = () => { app: { send: { description, generatedTx }, loadings: { sending: isSending = false }, - passwordRequest: { walletID = '', actionType = null, multisigConfig, onSuccess }, + passwordRequest, }, settings: { wallets = [] }, experimental, - wallet: currentWallet, } = useGlobalState() - const dispatch = useDispatch() const [t] = useTranslation() - const navigate = useNavigate() - - const [password, setPassword] = useState('') - const [error, setError] = useState('') - - useEffect(() => { - setPassword('') - setError('') - }, [actionType, setError, setPassword]) - + const dispatch = useDispatch() const onDismiss = useCallback(() => { dispatch({ type: AppActions.DismissPasswordRequest, }) }, [dispatch]) - const signType = useMemo(() => { - switch (actionType) { - case 'create-sudt-account': - return OfflineSignType.CreateSUDTAccount - case 'send-ckb-asset': - case 'send-acp-ckb-to-new-cell': - case 'send-acp-sudt-to-new-cell': - case 'transfer-to-sudt': - case 'send-sudt': - return OfflineSignType.SendSUDT - case 'unlock': - return OfflineSignType.UnlockDAO - case 'send-nft': - case 'send-from-multisig-need-one': - case 'send': - return OfflineSignType.Regular - case 'send-from-multisig': - return OfflineSignType.SendFromMultisigOnlySig - default: - return OfflineSignType.Invalid - } - }, [actionType]) - - const exportTransaction = useCallback(async () => { - const res = await exportTransactionAsJSON({ - transaction: generatedTx || experimental?.tx, - status: OfflineSignStatus.Unsigned, - type: signType, - description, - asset_account: experimental?.assetAccount, - }) - if (!isSuccessResponse(res)) { - setError(errorFormatter(res.message, t)) - return - } - if (res.result) { - onDismiss() - } - }, [signType, generatedTx, onDismiss, description, experimental]) - - const wallet = useMemo(() => wallets.find(w => w.id === walletID), [walletID, wallets]) - - const isLoading = - [ - 'send', - 'unlock', - 'create-sudt-account', - 'send-sudt', - 'transfer-to-sudt', - 'send-ckb-asset', - 'send-acp-ckb-to-new-cell', - 'send-acp-sudt-to-new-cell', - 'send-cheque', - 'withdraw-cheque', - 'claim-cheque', - 'create-account-to-claim-cheque', - 'send-from-multisig-need-one', - 'send-from-multisig', - 'destroy-asset-account', - ].includes(actionType || '') && isSending - const disabled = !password || isSending - - const onSubmit = useCallback( - async (e?: React.FormEvent) => { - if (e) { - e.preventDefault() - } - if (disabled) { - return - } - const handleSendTxRes = ({ status }: { status: number }) => { - if (isSuccessResponse({ status })) { - if (onSuccess) { - onSuccess() - return - } - navigate(RoutePath.History) - } else if (status === ErrorCode.PasswordIncorrect) { - throw new PasswordIncorrectException() - } - } - try { - switch (actionType) { - case 'send': { - if (isSending) { - break - } - await sendTransaction({ walletID, tx: generatedTx, description, password })(dispatch).then(handleSendTxRes) - break - } - case 'send-from-multisig-need-one': { - if (isSending) { - break - } - await sendTransaction({ walletID, tx: generatedTx, description, password, multisigConfig })(dispatch).then( - (res: { result?: string; status: number; message: string | { content: string } }) => { - if (isSuccessResponse(res)) { - requestOpenInExplorer({ type: 'transaction', key: res.result }) - } else if (res.status === ErrorCode.PasswordIncorrect) { - throw new PasswordIncorrectException() - } else { - invokeShowErrorMessage({ - title: t('messages.error'), - content: typeof res.message === 'string' ? res.message : res.message.content!, - }) - } - } - ) - break - } - case 'delete': { - await deleteWallet({ id: walletID, password })(dispatch).then(status => { - if (status === ErrorCode.PasswordIncorrect) { - throw new PasswordIncorrectException() - } - }) - break - } - case 'backup': { - await backupWallet({ id: walletID, password })(dispatch).then(status => { - if (status === ErrorCode.PasswordIncorrect) { - throw new PasswordIncorrectException() - } - }) - break - } - case 'migrate-acp': { - await migrateAcp({ id: walletID, password })(dispatch).then(({ status }) => { - if (isSuccessResponse({ status })) { - navigate(RoutePath.History) - } else if (status === ErrorCode.PasswordIncorrect) { - throw new PasswordIncorrectException() - } - }) - break - } - case 'unlock': { - if (isSending) { - break - } - await sendTransaction({ walletID, tx: generatedTx, description, password })(dispatch).then(({ status }) => { - if (isSuccessResponse({ status })) { - dispatch({ - type: AppActions.SetGlobalDialog, - payload: 'unlock-success', - }) - onSuccess?.() - } else if (status === ErrorCode.PasswordIncorrect) { - throw new PasswordIncorrectException() - } - }) - break - } - case 'create-sudt-account': { - const params: Controller.SendCreateSUDTAccountTransaction.Params = { - walletID, - assetAccount: experimental?.assetAccount, - tx: experimental?.tx, - password, - } - await sendCreateSUDTAccountTransaction(params)(dispatch).then(handleSendTxRes) - break - } - case 'send-ckb-asset': - case 'send-acp-ckb-to-new-cell': - case 'send-acp-sudt-to-new-cell': - case 'send-sudt': - case 'transfer-to-sudt': { - let skipLastInputs = true - if (actionType === 'send-acp-sudt-to-new-cell' || actionType === 'send-acp-ckb-to-new-cell') { - skipLastInputs = false - } - const params: Controller.SendSUDTTransaction.Params = { - walletID, - tx: experimental?.tx, - password, - skipLastInputs, - } - await sendSUDTTransaction(params)(dispatch).then(handleSendTxRes) - break - } - case 'destroy-asset-account': - case 'send-nft': - case 'send-cheque': { - if (isSending) { - break - } - await sendTransaction({ - walletID, - tx: experimental?.tx, - description: experimental?.tx?.description, - password, - })(dispatch).then(handleSendTxRes) - break - } - case 'claim-cheque': { - if (isSending) { - break - } - await sendTransaction({ walletID, tx: experimental?.tx, password })(dispatch).then(handleSendTxRes) - break - } - case 'create-account-to-claim-cheque': { - if (isSending) { - break - } - await sendCreateSUDTAccountTransaction({ - walletID, - password, - tx: experimental?.tx, - assetAccount: { - ...experimental?.assetAccount, - tokenID: experimental?.assetAccount.tokenId, - balance: '0', - }, - })(dispatch).then(handleSendTxRes) - break - } - case 'withdraw-cheque': { - if (isSending) { - break - } - await sendTransaction({ walletID, tx: experimental?.tx, password })(dispatch).then(handleSendTxRes) - break - } - default: { - break - } - } - } catch (err) { - if (err instanceof PasswordIncorrectException) { - setError(t(err.message)) - } - } - }, - [ - dispatch, - walletID, - password, - actionType, - description, - navigate, - isSending, - generatedTx, - disabled, - experimental, - setError, - t, - multisigConfig, - ] - ) - - const onChange = useCallback( - (e: React.SyntheticEvent) => { - const { value } = e.target as HTMLInputElement - setPassword(value) - setError('') - }, - [setPassword, setError] - ) - - const signAndExportFromGenerateTx = useCallback(async () => { - dispatch({ - type: AppActions.UpdateLoadings, - payload: { - sending: true, - }, - }) - const json = { - transaction: generatedTx || experimental?.tx, - status: OfflineSignStatus.Signed, - type: signType, - description, - asset_account: experimental?.assetAccount, - } - const res = await signAndExportTransaction({ - ...json, - walletID, - password, - multisigConfig, - }) - dispatch({ - type: AppActions.UpdateLoadings, - payload: { sending: false }, - }) - if (!isSuccessResponse(res)) { - setError(errorFormatter(res.message, t)) - return - } - if (res.result) { - dispatch({ - type: AppActions.UpdateLoadedTransaction, - payload: res.result!, - }) - onDismiss() - } - }, [description, dispatch, experimental, generatedTx, onDismiss, password, signType, t, walletID, multisigConfig]) - + const { + error, + wallet, + isLoading, + signType, + actionType, + disabled, + password, + onSubmit, + onChange, + exportTransaction, + signAndExportFromGenerateTx, + } = usePasswordResuest({ + description, + generatedTx, + isSending, + passwordRequest, + wallets, + experimental, + onDismiss, + }) if (!wallet) { return null } if (wallet.device) { - return ( - - ) + return } return ( @@ -406,13 +90,13 @@ const PasswordRequest = () => { ].includes(actionType ?? '') ? null : (
{wallet ? wallet.name : null}
)} - {currentWallet.isWatchOnly && ( + {wallet.isWatchOnly && (
{t('password-request.xpub-notice')}
)} - {currentWallet.isWatchOnly || ( + {wallet.isWatchOnly || ( { - {!currentWallet.isWatchOnly && ( + {!wallet.isWatchOnly && ( <>