2025-11-21 04:32:08 +08:00
|
|
|
import { useState } from 'react'
|
|
|
|
|
import { useNavigate } from 'react-router-dom'
|
2025-12-03 21:39:48 +08:00
|
|
|
import { Card, Form, Input, Button, message, Typography, Radio, Space, Alert } from 'antd'
|
2025-11-21 04:32:08 +08:00
|
|
|
import { ArrowLeftOutlined } from '@ant-design/icons'
|
2025-12-04 10:36:28 +08:00
|
|
|
import { useTranslation } from 'react-i18next'
|
2025-11-21 04:32:08 +08:00
|
|
|
import { useAccountStore } from '../store/accountStore'
|
|
|
|
|
import {
|
|
|
|
|
getAddressFromPrivateKey,
|
|
|
|
|
getAddressFromMnemonic,
|
|
|
|
|
getPrivateKeyFromMnemonic,
|
|
|
|
|
isValidWalletAddress,
|
|
|
|
|
isValidPrivateKey,
|
|
|
|
|
isValidMnemonic
|
2025-12-01 23:22:18 +08:00
|
|
|
} from '../utils'
|
2025-11-21 04:32:08 +08:00
|
|
|
import { useMediaQuery } from 'react-responsive'
|
|
|
|
|
|
2025-11-26 22:26:50 +08:00
|
|
|
const { Title } = Typography
|
2025-11-21 04:32:08 +08:00
|
|
|
|
|
|
|
|
type ImportType = 'privateKey' | 'mnemonic'
|
|
|
|
|
|
|
|
|
|
const AccountImport: React.FC = () => {
|
2025-12-04 10:36:28 +08:00
|
|
|
const { t } = useTranslation()
|
2025-11-21 04:32:08 +08:00
|
|
|
const navigate = useNavigate()
|
|
|
|
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
|
|
|
|
const { importAccount, loading } = useAccountStore()
|
|
|
|
|
const [form] = Form.useForm()
|
|
|
|
|
const [importType, setImportType] = useState<ImportType>('privateKey')
|
|
|
|
|
const [derivedAddress, setDerivedAddress] = useState<string>('')
|
|
|
|
|
const [addressError, setAddressError] = useState<string>('')
|
|
|
|
|
|
|
|
|
|
// 当私钥输入时,自动推导地址
|
2025-11-26 22:26:50 +08:00
|
|
|
const handlePrivateKeyChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
2025-11-21 04:32:08 +08:00
|
|
|
const privateKey = e.target.value.trim()
|
|
|
|
|
if (!privateKey) {
|
|
|
|
|
setDerivedAddress('')
|
|
|
|
|
setAddressError('')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 验证私钥格式
|
|
|
|
|
if (!isValidPrivateKey(privateKey)) {
|
2025-12-04 10:36:28 +08:00
|
|
|
setAddressError(t('accountImport.privateKeyInvalid'))
|
2025-11-21 04:32:08 +08:00
|
|
|
setDerivedAddress('')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const address = getAddressFromPrivateKey(privateKey)
|
|
|
|
|
setDerivedAddress(address)
|
|
|
|
|
setAddressError('')
|
|
|
|
|
|
|
|
|
|
// 自动填充钱包地址字段
|
|
|
|
|
form.setFieldsValue({ walletAddress: address })
|
|
|
|
|
} catch (error: any) {
|
2025-12-04 10:36:28 +08:00
|
|
|
setAddressError(error.message || t('accountImport.addressError'))
|
2025-11-21 04:32:08 +08:00
|
|
|
setDerivedAddress('')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 当助记词输入时,自动推导地址
|
2025-11-26 22:26:50 +08:00
|
|
|
const handleMnemonicChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
2025-11-21 04:32:08 +08:00
|
|
|
const mnemonic = e.target.value.trim()
|
|
|
|
|
if (!mnemonic) {
|
|
|
|
|
setDerivedAddress('')
|
|
|
|
|
setAddressError('')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 验证助记词格式
|
|
|
|
|
if (!isValidMnemonic(mnemonic)) {
|
2025-12-04 10:36:28 +08:00
|
|
|
setAddressError(t('accountImport.mnemonicInvalid'))
|
2025-11-21 04:32:08 +08:00
|
|
|
setDerivedAddress('')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const address = getAddressFromMnemonic(mnemonic, 0)
|
|
|
|
|
setDerivedAddress(address)
|
|
|
|
|
setAddressError('')
|
|
|
|
|
|
|
|
|
|
// 自动填充钱包地址字段
|
|
|
|
|
form.setFieldsValue({ walletAddress: address })
|
|
|
|
|
} catch (error: any) {
|
2025-12-04 10:36:28 +08:00
|
|
|
setAddressError(error.message || t('accountImport.addressErrorMnemonic'))
|
2025-11-21 04:32:08 +08:00
|
|
|
setDerivedAddress('')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSubmit = async (values: any) => {
|
|
|
|
|
try {
|
|
|
|
|
let privateKey: string
|
|
|
|
|
let walletAddress: string
|
|
|
|
|
|
|
|
|
|
if (importType === 'privateKey') {
|
|
|
|
|
// 私钥模式
|
|
|
|
|
privateKey = values.privateKey
|
|
|
|
|
walletAddress = values.walletAddress
|
|
|
|
|
|
|
|
|
|
// 验证推导的地址和输入的地址是否一致
|
|
|
|
|
if (derivedAddress && walletAddress !== derivedAddress) {
|
2025-12-04 10:36:28 +08:00
|
|
|
message.error(t('accountImport.walletAddressMismatch'))
|
2025-11-21 04:32:08 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 助记词模式
|
|
|
|
|
if (!values.mnemonic) {
|
2025-12-04 10:36:28 +08:00
|
|
|
message.error(t('accountImport.mnemonicRequired'))
|
2025-11-21 04:32:08 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 从助记词导出私钥和地址
|
|
|
|
|
privateKey = getPrivateKeyFromMnemonic(values.mnemonic, 0)
|
|
|
|
|
const derivedAddressFromMnemonic = getAddressFromMnemonic(values.mnemonic, 0)
|
|
|
|
|
|
|
|
|
|
// 如果用户手动输入了地址,验证是否与推导的地址一致
|
|
|
|
|
if (values.walletAddress) {
|
|
|
|
|
if (values.walletAddress !== derivedAddressFromMnemonic) {
|
|
|
|
|
// 地址不匹配,使用推导的地址(因为私钥是从助记词导出的,必须使用对应的地址)
|
2025-12-04 10:36:28 +08:00
|
|
|
message.warning(`${t('accountImport.walletAddressMismatchMnemonic')}: ${derivedAddressFromMnemonic}`)
|
2025-11-21 04:32:08 +08:00
|
|
|
walletAddress = derivedAddressFromMnemonic
|
|
|
|
|
} else {
|
|
|
|
|
// 地址匹配,使用用户输入的地址
|
|
|
|
|
walletAddress = values.walletAddress
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 如果用户没有输入地址,使用推导的地址
|
|
|
|
|
walletAddress = derivedAddressFromMnemonic
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 验证钱包地址格式
|
|
|
|
|
if (!isValidWalletAddress(walletAddress)) {
|
2025-12-04 10:36:28 +08:00
|
|
|
message.error(t('accountImport.walletAddressInvalid'))
|
2025-11-21 04:32:08 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await importAccount({
|
|
|
|
|
privateKey: privateKey,
|
|
|
|
|
walletAddress: walletAddress,
|
2025-12-03 21:39:48 +08:00
|
|
|
accountName: values.accountName
|
2025-11-21 04:32:08 +08:00
|
|
|
})
|
|
|
|
|
|
2025-12-04 10:36:28 +08:00
|
|
|
message.success(t('accountImport.importSuccess'))
|
2025-11-21 04:32:08 +08:00
|
|
|
navigate('/accounts')
|
|
|
|
|
} catch (error: any) {
|
2025-12-04 10:36:28 +08:00
|
|
|
message.error(error.message || t('accountImport.importFailed'))
|
2025-11-21 04:32:08 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
|
|
|
|
<div style={{ marginBottom: '16px' }}>
|
|
|
|
|
<Button
|
|
|
|
|
icon={<ArrowLeftOutlined />}
|
|
|
|
|
onClick={() => navigate('/accounts')}
|
|
|
|
|
style={{ marginBottom: '16px' }}
|
|
|
|
|
>
|
2025-12-04 10:36:28 +08:00
|
|
|
{t('accountImport.back')}
|
2025-11-21 04:32:08 +08:00
|
|
|
</Button>
|
2025-12-04 10:36:28 +08:00
|
|
|
<Title level={2} style={{ margin: 0 }}>{t('accountImport.title')}</Title>
|
2025-11-21 04:32:08 +08:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Card>
|
|
|
|
|
<Alert
|
2025-12-04 10:36:28 +08:00
|
|
|
message={t('accountImport.securityTip')}
|
|
|
|
|
description={t('accountImport.securityTipDesc')}
|
2025-11-21 04:32:08 +08:00
|
|
|
type="warning"
|
|
|
|
|
showIcon
|
|
|
|
|
style={{ marginBottom: '24px' }}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Form
|
|
|
|
|
form={form}
|
|
|
|
|
layout="vertical"
|
|
|
|
|
onFinish={handleSubmit}
|
|
|
|
|
size={isMobile ? 'middle' : 'large'}
|
|
|
|
|
>
|
2025-12-04 10:36:28 +08:00
|
|
|
<Form.Item label={t('accountImport.importMethod')}>
|
2025-11-21 04:32:08 +08:00
|
|
|
<Radio.Group
|
|
|
|
|
value={importType}
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
setImportType(e.target.value)
|
|
|
|
|
setDerivedAddress('')
|
|
|
|
|
setAddressError('')
|
|
|
|
|
form.setFieldsValue({ walletAddress: '' })
|
|
|
|
|
}}
|
|
|
|
|
>
|
2025-12-04 10:36:28 +08:00
|
|
|
<Radio value="privateKey">{t('accountImport.privateKey')}</Radio>
|
|
|
|
|
<Radio value="mnemonic">{t('accountImport.mnemonic')}</Radio>
|
2025-11-21 04:32:08 +08:00
|
|
|
</Radio.Group>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
{importType === 'privateKey' ? (
|
|
|
|
|
<>
|
|
|
|
|
<Form.Item
|
2025-12-04 10:36:28 +08:00
|
|
|
label={t('accountImport.privateKeyLabel')}
|
2025-11-21 04:32:08 +08:00
|
|
|
name="privateKey"
|
|
|
|
|
rules={[
|
2025-12-04 10:36:28 +08:00
|
|
|
{ required: true, message: t('accountImport.privateKeyRequired') },
|
2025-11-21 04:32:08 +08:00
|
|
|
{
|
|
|
|
|
validator: (_, value) => {
|
|
|
|
|
if (!value) return Promise.resolve()
|
|
|
|
|
if (!isValidPrivateKey(value)) {
|
2025-12-04 10:36:28 +08:00
|
|
|
return Promise.reject(new Error(t('accountImport.privateKeyInvalid')))
|
2025-11-21 04:32:08 +08:00
|
|
|
}
|
|
|
|
|
return Promise.resolve()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]}
|
2025-12-04 10:36:28 +08:00
|
|
|
help={addressError || (derivedAddress ? `${t('accountImport.derivedAddress')}: ${derivedAddress}` : '')}
|
2025-11-21 04:32:08 +08:00
|
|
|
validateStatus={addressError ? 'error' : derivedAddress ? 'success' : ''}
|
|
|
|
|
>
|
|
|
|
|
<Input.TextArea
|
|
|
|
|
rows={3}
|
2025-12-04 10:36:28 +08:00
|
|
|
placeholder={t('accountImport.privateKeyPlaceholder')}
|
2025-11-21 04:32:08 +08:00
|
|
|
onChange={handlePrivateKeyChange}
|
|
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
<Form.Item
|
2025-12-04 10:36:28 +08:00
|
|
|
label={t('accountImport.walletAddress')}
|
2025-11-21 04:32:08 +08:00
|
|
|
name="walletAddress"
|
|
|
|
|
rules={[
|
2025-12-04 10:36:28 +08:00
|
|
|
{ required: true, message: t('accountImport.walletAddressRequired') },
|
2025-11-21 04:32:08 +08:00
|
|
|
{
|
|
|
|
|
validator: (_, value) => {
|
|
|
|
|
if (!value) return Promise.resolve()
|
|
|
|
|
if (!isValidWalletAddress(value)) {
|
2025-12-04 10:36:28 +08:00
|
|
|
return Promise.reject(new Error(t('accountImport.walletAddressInvalid')))
|
2025-11-21 04:32:08 +08:00
|
|
|
}
|
|
|
|
|
if (derivedAddress && value !== derivedAddress) {
|
2025-12-04 10:36:28 +08:00
|
|
|
return Promise.reject(new Error(t('accountImport.walletAddressMismatch')))
|
2025-11-21 04:32:08 +08:00
|
|
|
}
|
|
|
|
|
return Promise.resolve()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]}
|
|
|
|
|
>
|
|
|
|
|
<Input
|
2025-12-04 10:36:28 +08:00
|
|
|
placeholder={t('accountImport.walletAddressPlaceholder')}
|
2025-11-21 04:32:08 +08:00
|
|
|
readOnly={!!derivedAddress}
|
|
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<Form.Item
|
2025-12-04 10:36:28 +08:00
|
|
|
label={t('accountImport.mnemonicLabel')}
|
2025-11-21 04:32:08 +08:00
|
|
|
name="mnemonic"
|
|
|
|
|
rules={[
|
2025-12-04 10:36:28 +08:00
|
|
|
{ required: true, message: t('accountImport.mnemonicRequired') },
|
2025-11-21 04:32:08 +08:00
|
|
|
{
|
|
|
|
|
validator: (_, value) => {
|
|
|
|
|
if (!value) return Promise.resolve()
|
|
|
|
|
if (!isValidMnemonic(value)) {
|
2025-12-04 10:36:28 +08:00
|
|
|
return Promise.reject(new Error(t('accountImport.mnemonicInvalid')))
|
2025-11-21 04:32:08 +08:00
|
|
|
}
|
|
|
|
|
return Promise.resolve()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]}
|
2025-12-04 10:36:28 +08:00
|
|
|
help={addressError || (derivedAddress ? `${t('accountImport.derivedAddress')}: ${derivedAddress}` : '')}
|
2025-11-21 04:32:08 +08:00
|
|
|
validateStatus={addressError ? 'error' : derivedAddress ? 'success' : ''}
|
|
|
|
|
>
|
|
|
|
|
<Input.TextArea
|
|
|
|
|
rows={4}
|
2025-12-04 10:36:28 +08:00
|
|
|
placeholder={t('accountImport.mnemonicPlaceholder')}
|
2025-11-21 04:32:08 +08:00
|
|
|
onChange={handleMnemonicChange}
|
|
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
<Form.Item
|
2025-12-04 10:36:28 +08:00
|
|
|
label={t('accountImport.walletAddress')}
|
2025-11-21 04:32:08 +08:00
|
|
|
name="walletAddress"
|
|
|
|
|
rules={[
|
2025-12-04 10:36:28 +08:00
|
|
|
{ required: true, message: t('accountImport.walletAddressRequired') },
|
2025-11-21 04:32:08 +08:00
|
|
|
{
|
|
|
|
|
validator: (_, value) => {
|
|
|
|
|
if (!value) return Promise.resolve()
|
|
|
|
|
if (!isValidWalletAddress(value)) {
|
2025-12-04 10:36:28 +08:00
|
|
|
return Promise.reject(new Error(t('accountImport.walletAddressInvalid')))
|
2025-11-21 04:32:08 +08:00
|
|
|
}
|
|
|
|
|
if (derivedAddress && value !== derivedAddress) {
|
2025-12-04 10:36:28 +08:00
|
|
|
return Promise.reject(new Error(t('accountImport.walletAddressMismatchMnemonic')))
|
2025-11-21 04:32:08 +08:00
|
|
|
}
|
|
|
|
|
return Promise.resolve()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]}
|
|
|
|
|
>
|
|
|
|
|
<Input
|
2025-12-04 10:36:28 +08:00
|
|
|
placeholder={t('accountImport.walletAddressPlaceholder')}
|
2025-11-21 04:32:08 +08:00
|
|
|
readOnly={!!derivedAddress}
|
|
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<Form.Item
|
2025-12-04 10:36:28 +08:00
|
|
|
label={t('accountImport.accountName')}
|
2025-11-21 04:32:08 +08:00
|
|
|
name="accountName"
|
|
|
|
|
>
|
2025-12-04 10:36:28 +08:00
|
|
|
<Input placeholder={t('accountImport.accountNamePlaceholder')} />
|
2025-11-21 04:32:08 +08:00
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<Form.Item>
|
|
|
|
|
<Space>
|
|
|
|
|
<Button
|
|
|
|
|
type="primary"
|
|
|
|
|
htmlType="submit"
|
|
|
|
|
loading={loading}
|
|
|
|
|
size={isMobile ? 'middle' : 'large'}
|
|
|
|
|
>
|
2025-12-04 10:36:28 +08:00
|
|
|
{t('accountImport.importAccount')}
|
2025-11-21 04:32:08 +08:00
|
|
|
</Button>
|
|
|
|
|
<Button onClick={() => navigate('/accounts')}>
|
2025-12-04 10:36:28 +08:00
|
|
|
{t('common.cancel')}
|
2025-11-21 04:32:08 +08:00
|
|
|
</Button>
|
|
|
|
|
</Space>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Form>
|
|
|
|
|
</Card>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default AccountImport
|
|
|
|
|
|