// Copyright 2017-2025 @pezkuwi/app-accounts authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { Dispatch, SetStateAction } from 'react'; import type { KeyringPair, KeyringPair$Json } from '@pezkuwi/keyring/types'; import type { ActionStatus } from '@pezkuwi/react-components/Status/types'; import type { HexString } from '@pezkuwi/util/types'; import type { ModalProps } from '../types.js'; import React, { useCallback, useMemo, useState } from 'react'; import { AddressRow, Button, InputAddress, InputFile, MarkError, MarkWarning, Modal, Password } from '@pezkuwi/react-components'; import { useApi } from '@pezkuwi/react-hooks'; import { keyring } from '@pezkuwi/ui-keyring'; import { assert, nextTick, u8aToString } from '@pezkuwi/util'; import { useTranslation } from '../translate.js'; import ExternalWarning from './ExternalWarning.js'; interface Props extends ModalProps { className?: string; onClose: () => void; onStatusChange: (status: ActionStatus) => void; } interface PassState { isPassValid: boolean; password: string; } const acceptedFormats = ['application/json', 'text/plain']; function parseFile (file: Uint8Array, setError: Dispatch>, isEthereum: boolean, genesisHash?: HexString | null): KeyringPair | null { try { const pair = keyring.createFromJson(JSON.parse(u8aToString(file)) as KeyringPair$Json, { genesisHash }); if (isEthereum) { assert(pair.type === 'ethereum', 'JSON File does not contain an ethereum type key pair'); } else { assert(pair.type !== 'ethereum', 'JSON contains an ethereum keytype, this is not available on this network'); } return pair; } catch (error) { console.error(error); setError((error as Error).message ? (error as Error).message : (error as Error).toString()); } return null; } function Import ({ className = '', onClose, onStatusChange }: Props): React.ReactElement { const { t } = useTranslation(); const { api, isDevelopment, isEthereum } = useApi(); const [isBusy, setIsBusy] = useState(false); const [pair, setPair] = useState(null); const [error, setError] = useState(null); const [{ isPassValid, password }, setPass] = useState({ isPassValid: false, password: '' }); const apiGenesisHash = useMemo(() => isDevelopment ? null : api.genesisHash.toHex(), [api, isDevelopment]); const differentGenesis = useMemo(() => !!pair?.meta.genesisHash && pair.meta.genesisHash !== apiGenesisHash, [apiGenesisHash, pair]); const _onChangeFile = useCallback( (file: Uint8Array) => setPair(parseFile(file, setError, isEthereum, apiGenesisHash)), [apiGenesisHash, isEthereum] ); const _onChangePass = useCallback( (password: string) => setPass({ isPassValid: keyring.isPassValid(password), password }), [] ); const _onSave = useCallback( (): void => { if (!pair) { return; } setIsBusy(true); nextTick((): void => { const status: Partial = { action: 'restore' }; try { keyring.addPair(pair, password); status.status = 'success'; status.account = pair.address; status.message = t('account restored'); InputAddress.setLastValue('account', pair.address); } catch (error) { setPass((state: PassState) => ({ ...state, isPassValid: false })); status.status = 'error'; status.message = (error as Error).message; console.error(error); } setIsBusy(false); onStatusChange(status as ActionStatus); if (status.status !== 'error') { onClose(); } }); }, [onClose, onStatusChange, pair, password, t] ); return ( {error && ( )} {differentGenesis && ( )}