feat: 钱包类型枚举与前端账号类型展示

后端:
- 新增 WalletType 枚举(MAGIC/SAFE),移除 safe/magic 字符串硬编码
- RelayClientService/BlockchainService/AccountService/OrderSigningService 使用枚举
- Builder Relayer API 类型使用常量 RELAYER_TYPE_PROXY/SAFE

前端:
- 账户列表、详情、导入 Modal 显示账号类型(Magic/Safe Tag)
- 导入账户 Modal 移除安全提示 Alert,移除 showAlert 与相关 i18n
- 移除无用 i18n key:securityTip、securityTipDesc、walletTypeMagic、walletTypeSafe、magicNotSupported
- 钱包类型 Tag 简化为仅显示 Magic 或 Safe

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WrBug
2026-02-14 01:14:04 +08:00
co-authored by Cursor
parent fc6fa8b419
commit 3405a1cda3
13 changed files with 133 additions and 61 deletions
@@ -0,0 +1,51 @@
package com.wrbug.polymarketbot.enums
/**
* 钱包类型枚举
*/
enum class WalletType(val value: String, val description: String) {
/**
* Magic 钱包(邮箱/OAuth 登录)
* 使用 PROXY 代理合约,通过 Builder Relayer 执行 Gasless 交易
*/
MAGIC("magic", "Magic(邮箱/OAuth登录)"),
/**
* Safe 钱包(MetaMask 等 Web3 钱包)
* 使用 Gnosis Safe 代理合约,支持 Builder Relayer Gasless 或手动交易
*/
SAFE("safe", "SafeWeb3钱包)");
companion object {
/**
* 从字符串值解析钱包类型(不区分大小写)
*/
fun fromString(value: String?): WalletType {
if (value.isNullOrBlank()) {
return SAFE // 默认返回 SAFE
}
return values().find { it.value.equals(value, ignoreCase = true) }
?: throw IllegalArgumentException("未知的钱包类型: $value")
}
/**
* 安全地从字符串值解析钱包类型(不区分大小写),解析失败返回默认值
*/
fun fromStringOrDefault(value: String?, default: WalletType = SAFE): WalletType {
if (value.isNullOrBlank()) {
return default
}
return values().find { it.value.equals(value, ignoreCase = true) } ?: default
}
/**
* 检查字符串是否为有效的钱包类型
*/
fun isValid(value: String?): Boolean {
if (value.isNullOrBlank()) {
return false
}
return values().any { it.value.equals(value, ignoreCase = true) }
}
}
}
@@ -3,6 +3,7 @@ package com.wrbug.polymarketbot.service.accounts
import com.wrbug.polymarketbot.api.TradeResponse import com.wrbug.polymarketbot.api.TradeResponse
import com.wrbug.polymarketbot.dto.* import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.entity.Account import com.wrbug.polymarketbot.entity.Account
import com.wrbug.polymarketbot.enums.WalletType
import com.wrbug.polymarketbot.repository.AccountRepository import com.wrbug.polymarketbot.repository.AccountRepository
import com.wrbug.polymarketbot.util.RetrofitFactory import com.wrbug.polymarketbot.util.RetrofitFactory
import com.wrbug.polymarketbot.util.toSafeBigDecimal import com.wrbug.polymarketbot.util.toSafeBigDecimal
@@ -105,7 +106,8 @@ class AccountService(
// 5. 获取代理地址(必须成功,否则导入失败) // 5. 获取代理地址(必须成功,否则导入失败)
// 根据用户选择的钱包类型计算代理地址 // 根据用户选择的钱包类型计算代理地址
val proxyAddress = runBlocking { val proxyAddress = runBlocking {
val proxyResult = blockchainService.getProxyAddress(request.walletAddress, request.walletType) val walletTypeEnum = WalletType.fromStringOrDefault(request.walletType, WalletType.MAGIC)
val proxyResult = blockchainService.getProxyAddress(request.walletAddress, walletTypeEnum)
if (proxyResult.isSuccess) { if (proxyResult.isSuccess) {
val address = proxyResult.getOrNull() val address = proxyResult.getOrNull()
if (address != null) { if (address != null) {
@@ -199,11 +201,11 @@ class AccountService(
coroutineScope { coroutineScope {
val magicDeferred = async { val magicDeferred = async {
try { try {
val proxyAddress = blockchainService.getProxyAddress(request.walletAddress, "magic").getOrNull() val proxyAddress = blockchainService.getProxyAddress(request.walletAddress, WalletType.MAGIC).getOrNull()
if (proxyAddress != null) { if (proxyAddress != null) {
val balance = blockchainService.getWalletBalance(proxyAddress).getOrNull() val balance = blockchainService.getWalletBalance(proxyAddress).getOrNull()
ProxyOptionDto( ProxyOptionDto(
walletType = "magic", walletType = WalletType.MAGIC.value,
proxyAddress = proxyAddress, proxyAddress = proxyAddress,
descriptionKey = "accountImport.proxyOption.magic.description", descriptionKey = "accountImport.proxyOption.magic.description",
availableBalance = balance?.availableBalance ?: "0", availableBalance = balance?.availableBalance ?: "0",
@@ -246,11 +248,11 @@ class AccountService(
val safeDeferred = async { val safeDeferred = async {
try { try {
val proxyAddress = blockchainService.getProxyAddress(request.walletAddress, "safe").getOrNull() val proxyAddress = blockchainService.getProxyAddress(request.walletAddress, WalletType.SAFE).getOrNull()
if (proxyAddress != null) { if (proxyAddress != null) {
val balance = blockchainService.getWalletBalance(proxyAddress).getOrNull() val balance = blockchainService.getWalletBalance(proxyAddress).getOrNull()
ProxyOptionDto( ProxyOptionDto(
walletType = "safe", walletType = WalletType.SAFE.value,
proxyAddress = proxyAddress, proxyAddress = proxyAddress,
descriptionKey = "accountImport.proxyOption.safe.description", descriptionKey = "accountImport.proxyOption.safe.description",
availableBalance = balance?.availableBalance ?: "0", availableBalance = balance?.availableBalance ?: "0",
@@ -300,7 +302,7 @@ class AccountService(
} else { } else {
// 助记词导入:仅获取 Safe 代理地址及资产 // 助记词导入:仅获取 Safe 代理地址及资产
try { try {
val proxyAddress = blockchainService.getProxyAddress(request.walletAddress, "safe").getOrNull() val proxyAddress = blockchainService.getProxyAddress(request.walletAddress, WalletType.SAFE).getOrNull()
if (proxyAddress != null) { if (proxyAddress != null) {
val balance = blockchainService.getWalletBalance(proxyAddress).getOrNull() val balance = blockchainService.getWalletBalance(proxyAddress).getOrNull()
options.add( options.add(
@@ -1398,7 +1400,9 @@ class AccountService(
} }
// 4. 若涉及 Magic 账户,必须已配置 Builder API Key(提前判断,避免执行到深层再报错) // 4. 若涉及 Magic 账户,必须已配置 Builder API Key(提前判断,避免执行到深层再报错)
val hasMagicAccount = accounts.values.any { it.walletType.equals("magic", ignoreCase = true) } val hasMagicAccount = accounts.values.any {
WalletType.fromStringOrDefault(it.walletType, WalletType.SAFE) == WalletType.MAGIC
}
if (hasMagicAccount && !relayClientService.isBuilderApiKeyConfigured()) { if (hasMagicAccount && !relayClientService.isBuilderApiKeyConfigured()) {
return Result.failure( return Result.failure(
IllegalStateException("Builder API Key 未配置,无法执行 Magic 账户赎回(Gasless)。请前往系统设置页面配置 Builder API Key。") IllegalStateException("Builder API Key 未配置,无法执行 Magic 账户赎回(Gasless)。请前往系统设置页面配置 Builder API Key。")
@@ -1468,12 +1472,13 @@ class AccountService(
val decryptedPrivateKey = decryptPrivateKey(account) val decryptedPrivateKey = decryptPrivateKey(account)
// 调用区块链服务赎回仓位 // 调用区块链服务赎回仓位
val walletTypeEnum = WalletType.fromStringOrDefault(account.walletType, WalletType.SAFE)
val redeemResult = blockchainService.redeemPositions( val redeemResult = blockchainService.redeemPositions(
privateKey = decryptedPrivateKey, privateKey = decryptedPrivateKey,
proxyAddress = account.proxyAddress, proxyAddress = account.proxyAddress,
conditionId = marketId, conditionId = marketId,
indexSets = indexSets, indexSets = indexSets,
walletType = account.walletType walletType = walletTypeEnum
) )
redeemResult.fold( redeemResult.fold(
@@ -10,6 +10,7 @@ import com.wrbug.polymarketbot.api.ValueResponse
import com.wrbug.polymarketbot.constants.PolymarketConstants import com.wrbug.polymarketbot.constants.PolymarketConstants
import com.wrbug.polymarketbot.dto.PositionDto import com.wrbug.polymarketbot.dto.PositionDto
import com.wrbug.polymarketbot.dto.WalletBalanceResponse import com.wrbug.polymarketbot.dto.WalletBalanceResponse
import com.wrbug.polymarketbot.enums.WalletType
import com.wrbug.polymarketbot.util.EthereumUtils import com.wrbug.polymarketbot.util.EthereumUtils
import com.wrbug.polymarketbot.util.RetrofitFactory import com.wrbug.polymarketbot.util.RetrofitFactory
import com.wrbug.polymarketbot.util.createClient import com.wrbug.polymarketbot.util.createClient
@@ -93,13 +94,13 @@ class BlockchainService(
* 2. Safe ProxyMetaMask 钱包用户)- 通过合约调用获取地址 * 2. Safe ProxyMetaMask 钱包用户)- 通过合约调用获取地址
* *
* @param walletAddress 用户的钱包地址(EOA * @param walletAddress 用户的钱包地址(EOA
* @param walletType 钱包类型:"magic"(默认)或 "safe" * @param walletType 钱包类型:MAGIC(默认)或 SAFE
* @return 代理钱包地址 * @return 代理钱包地址
*/ */
suspend fun getProxyAddress(walletAddress: String, walletType: String = "magic"): Result<String> { suspend fun getProxyAddress(walletAddress: String, walletType: WalletType = WalletType.MAGIC): Result<String> {
return try { return try {
when (walletType.lowercase()) { when (walletType) {
"safe" -> { WalletType.SAFE -> {
// Safe ProxyMetaMask 用户) // Safe ProxyMetaMask 用户)
val safeProxyResult = getSafeProxyAddress(walletAddress) val safeProxyResult = getSafeProxyAddress(walletAddress)
if (safeProxyResult.isSuccess) { if (safeProxyResult.isSuccess) {
@@ -110,7 +111,7 @@ class BlockchainService(
Result.failure(safeProxyResult.exceptionOrNull() ?: Exception("获取 Safe Proxy 地址失败")) Result.failure(safeProxyResult.exceptionOrNull() ?: Exception("获取 Safe Proxy 地址失败"))
} }
} }
else -> { WalletType.MAGIC -> {
// Magic Proxy(邮箱/OAuth 登录用户)- 默认 // Magic Proxy(邮箱/OAuth 登录用户)- 默认
val magicProxyAddress = calculateMagicProxyAddress(walletAddress) val magicProxyAddress = calculateMagicProxyAddress(walletAddress)
logger.debug("使用 Magic Proxy 地址: $magicProxyAddress") logger.debug("使用 Magic Proxy 地址: $magicProxyAddress")
@@ -586,7 +587,7 @@ class BlockchainService(
* @param proxyAddress 代理地址(Safe 或 Magic 代理钱包地址) * @param proxyAddress 代理地址(Safe 或 Magic 代理钱包地址)
* @param conditionId 市场条件IDbytes32,必须是 0x 开头的 66 位十六进制字符串) * @param conditionId 市场条件IDbytes32,必须是 0x 开头的 66 位十六进制字符串)
* @param indexSets 要赎回的索引集合列表(每个元素是 2^outcomeIndex * @param indexSets 要赎回的索引集合列表(每个元素是 2^outcomeIndex
* @param walletType 钱包类型:"magic" 或 "safe",用于选择执行路径 * @param walletType 钱包类型:MAGIC 或 SAFE,用于选择执行路径
* @return 交易哈希 * @return 交易哈希
*/ */
suspend fun redeemPositions( suspend fun redeemPositions(
@@ -594,7 +595,7 @@ class BlockchainService(
proxyAddress: String, proxyAddress: String,
conditionId: String, conditionId: String,
indexSets: List<BigInteger>, indexSets: List<BigInteger>,
walletType: String = "safe" walletType: WalletType = WalletType.SAFE
): Result<String> { ): Result<String> {
return try { return try {
if (indexSets.isEmpty()) { if (indexSets.isEmpty()) {
@@ -24,11 +24,13 @@ class OrderSigningService {
/** /**
* 根据钱包类型返回 CLOB 订单签名类型 * 根据钱包类型返回 CLOB 订单签名类型
* @param walletType magic=邮箱/社交登录, safe=Web3 钱包 * @param walletType Magic=邮箱/社交登录, Safe=Web3 钱包
* @return 1=POLY_PROXY(Magic), 2=POLY_GNOSIS_SAFE(Safe), 默认 2 * @return 1=POLY_PROXY(Magic), 2=POLY_GNOSIS_SAFE(Safe), 默认 2
*/ */
fun getSignatureTypeForWalletType(walletType: String?): Int = fun getSignatureTypeForWalletType(walletType: String?): Int {
if (walletType?.lowercase() == "magic") 1 else 2 val walletTypeEnum = com.wrbug.polymarketbot.enums.WalletType.fromStringOrDefault(walletType, com.wrbug.polymarketbot.enums.WalletType.SAFE)
return if (walletTypeEnum == com.wrbug.polymarketbot.enums.WalletType.MAGIC) 1 else 2
}
// Polygon 主网合约地址 // Polygon 主网合约地址
private val EXCHANGE_CONTRACT = "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E" private val EXCHANGE_CONTRACT = "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E"
@@ -4,6 +4,7 @@ import com.wrbug.polymarketbot.api.BuilderRelayerApi
import com.wrbug.polymarketbot.api.EthereumRpcApi import com.wrbug.polymarketbot.api.EthereumRpcApi
import com.wrbug.polymarketbot.api.JsonRpcRequest import com.wrbug.polymarketbot.api.JsonRpcRequest
import com.wrbug.polymarketbot.constants.PolymarketConstants import com.wrbug.polymarketbot.constants.PolymarketConstants
import com.wrbug.polymarketbot.enums.WalletType
import com.wrbug.polymarketbot.util.EthereumUtils import com.wrbug.polymarketbot.util.EthereumUtils
import com.wrbug.polymarketbot.util.RetrofitFactory import com.wrbug.polymarketbot.util.RetrofitFactory
import com.wrbug.polymarketbot.util.createClient import com.wrbug.polymarketbot.util.createClient
@@ -44,6 +45,10 @@ class RelayClientService(
private val proxyFactoryAddress = "0xaB45c5A4B0c941a2F231C04C3f49182e1A254052" private val proxyFactoryAddress = "0xaB45c5A4B0c941a2F231C04C3f49182e1A254052"
private val relayHubAddress = "0xD216153c06E857cD7f72665E0aF1d7D82172F494" private val relayHubAddress = "0xD216153c06E857cD7f72665E0aF1d7D82172F494"
private val defaultProxyGasLimit = "10000000" private val defaultProxyGasLimit = "10000000"
// Builder Relayer API 交易类型常量
private val RELAYER_TYPE_PROXY = "PROXY"
private val RELAYER_TYPE_SAFE = "SAFE"
private val polygonRpcApi: EthereumRpcApi by lazy { private val polygonRpcApi: EthereumRpcApi by lazy {
val rpcUrl = rpcNodeService.getHttpUrl() val rpcUrl = rpcNodeService.getHttpUrl()
@@ -212,14 +217,14 @@ class RelayClientService(
* @param privateKey 私钥 * @param privateKey 私钥
* @param proxyAddress 代理钱包地址 * @param proxyAddress 代理钱包地址
* @param safeTx 交易对象to/data/value * @param safeTx 交易对象to/data/value
* @param walletType 钱包类型"magic" 使用 PROXY Gasless"safe" 使用 Safe 流程 * @param walletType 钱包类型MAGIC 使用 PROXY GaslessSAFE 使用 Safe 流程
* @return 交易哈希 * @return 交易哈希
*/ */
suspend fun execute( suspend fun execute(
privateKey: String, privateKey: String,
proxyAddress: String, proxyAddress: String,
safeTx: SafeTransaction, safeTx: SafeTransaction,
walletType: String = "safe" walletType: WalletType = WalletType.SAFE
): Result<String> { ): Result<String> {
return try { return try {
if (proxyAddress.isBlank() || !proxyAddress.startsWith("0x") || proxyAddress.length != 42) { if (proxyAddress.isBlank() || !proxyAddress.startsWith("0x") || proxyAddress.length != 42) {
@@ -230,7 +235,7 @@ class RelayClientService(
val builderSecret = systemConfigService.getBuilderSecret() val builderSecret = systemConfigService.getBuilderSecret()
val builderPassphrase = systemConfigService.getBuilderPassphrase() val builderPassphrase = systemConfigService.getBuilderPassphrase()
if (walletType.lowercase() == "magic") { if (walletType == WalletType.MAGIC) {
if (!isBuilderRelayerEnabled(builderApiKey, builderSecret, builderPassphrase)) { if (!isBuilderRelayerEnabled(builderApiKey, builderSecret, builderPassphrase)) {
return Result.failure(IllegalStateException("Magic 账户赎回必须配置 Builder API KeyGasless")) return Result.failure(IllegalStateException("Magic 账户赎回必须配置 Builder API KeyGasless"))
} }
@@ -289,7 +294,7 @@ class RelayClientService(
val credentials = org.web3j.crypto.Credentials.create(privateKeyBigInt.toString(16)) val credentials = org.web3j.crypto.Credentials.create(privateKeyBigInt.toString(16))
val fromAddress = credentials.address val fromAddress = credentials.address
val relayPayloadResponse = relayerApi.getRelayPayload(fromAddress, "PROXY") val relayPayloadResponse = relayerApi.getRelayPayload(fromAddress, RELAYER_TYPE_PROXY)
if (!relayPayloadResponse.isSuccessful || relayPayloadResponse.body() == null) { if (!relayPayloadResponse.isSuccessful || relayPayloadResponse.body() == null) {
val errorBody = relayPayloadResponse.errorBody()?.string() ?: "未知错误" val errorBody = relayPayloadResponse.errorBody()?.string() ?: "未知错误"
logger.error("获取 Relay Payload 失败: code=${relayPayloadResponse.code()}, body=$errorBody") logger.error("获取 Relay Payload 失败: code=${relayPayloadResponse.code()}, body=$errorBody")
@@ -338,7 +343,7 @@ class RelayClientService(
String.format("%02x", (signature.v as ByteArray).getOrElse(0) { 0 }.toInt() and 0xff) String.format("%02x", (signature.v as ByteArray).getOrElse(0) { 0 }.toInt() and 0xff)
val request = BuilderRelayerApi.TransactionRequest( val request = BuilderRelayerApi.TransactionRequest(
type = "PROXY", type = RELAYER_TYPE_PROXY,
from = fromAddress, from = fromAddress,
to = proxyFactoryAddress, to = proxyFactoryAddress,
proxyWallet = proxyAddress, proxyWallet = proxyAddress,
@@ -520,7 +525,7 @@ class RelayClientService(
val redeemCallData = safeTx.data val redeemCallData = safeTx.data
// 获取 Proxy 的 nonce(通过 Builder Relayer API // 获取 Proxy 的 nonce(通过 Builder Relayer API
val nonceResponse = relayerApi.getNonce(fromAddress, "SAFE") val nonceResponse = relayerApi.getNonce(fromAddress, RELAYER_TYPE_SAFE)
if (!nonceResponse.isSuccessful || nonceResponse.body() == null) { if (!nonceResponse.isSuccessful || nonceResponse.body() == null) {
val errorBody = nonceResponse.errorBody()?.string() ?: "未知错误" val errorBody = nonceResponse.errorBody()?.string() ?: "未知错误"
logger.error("获取 nonce 失败: code=${nonceResponse.code()}, body=$errorBody") logger.error("获取 nonce 失败: code=${nonceResponse.code()}, body=$errorBody")
@@ -587,7 +592,7 @@ class RelayClientService(
// 构建 TransactionRequest(参考 builder-relayer-client/src/builder/safe.ts // 构建 TransactionRequest(参考 builder-relayer-client/src/builder/safe.ts
// 注意:根据 TypeScript 实现,data 和 signature 都应该带 0x 前缀 // 注意:根据 TypeScript 实现,data 和 signature 都应该带 0x 前缀
val request = BuilderRelayerApi.TransactionRequest( val request = BuilderRelayerApi.TransactionRequest(
type = "SAFE", type = RELAYER_TYPE_SAFE,
from = fromAddress, from = fromAddress,
to = safeTx.to, to = safeTx.to,
proxyWallet = proxyAddress, proxyWallet = proxyAddress,
+1 -13
View File
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { Form, Input, Button, Radio, Space, Alert, Card, Spin, message } from 'antd' import { Form, Input, Button, Radio, Space, Card, Spin, message, Alert } from 'antd'
import { CheckCircleOutlined, ExclamationCircleOutlined } from '@ant-design/icons' import { CheckCircleOutlined, ExclamationCircleOutlined } from '@ant-design/icons'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useAccountStore } from '../store/accountStore' import { useAccountStore } from '../store/accountStore'
@@ -22,7 +22,6 @@ interface AccountImportFormProps {
form: any form: any
onSuccess?: (accountId: number) => void onSuccess?: (accountId: number) => void
onCancel?: () => void onCancel?: () => void
showAlert?: boolean
showCancelButton?: boolean showCancelButton?: boolean
} }
@@ -30,7 +29,6 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
form, form,
onSuccess, onSuccess,
onCancel, onCancel,
showAlert = true,
showCancelButton = true showCancelButton = true
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
@@ -266,16 +264,6 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
return ( return (
<> <>
{showAlert && (
<Alert
message={t('accountImport.securityTip')}
description={t('accountImport.securityTipDesc')}
type="warning"
showIcon
style={{ marginBottom: '24px' }}
/>
)}
<Form <Form
form={form} form={form}
layout="vertical" layout="vertical"
+2 -5
View File
@@ -53,6 +53,7 @@
"accountId": "Account ID", "accountId": "Account ID",
"accountName": "Account Name", "accountName": "Account Name",
"walletAddress": "Wallet Address", "walletAddress": "Wallet Address",
"walletType": "Wallet Type",
"balance": "Account Balance", "balance": "Account Balance",
"refreshBalance": "Refresh Balance", "refreshBalance": "Refresh Balance",
"apiCredentials": "API Credentials Configuration", "apiCredentials": "API Credentials Configuration",
@@ -122,6 +123,7 @@
"importAccount": "Import Account", "importAccount": "Import Account",
"accountName": "Account Name", "accountName": "Account Name",
"walletAddress": "Wallet Address", "walletAddress": "Wallet Address",
"walletType": "Wallet Type",
"proxyAddress": "Proxy Wallet Address", "proxyAddress": "Proxy Wallet Address",
"apiCredentials": "API Credentials", "apiCredentials": "API Credentials",
"balance": "Balance", "balance": "Balance",
@@ -178,8 +180,6 @@
"accountImport": { "accountImport": {
"title": "Import Account", "title": "Import Account",
"back": "Back", "back": "Back",
"securityTip": "Security Tip",
"securityTipDesc": "Private keys will be stored in the backend database. Please ensure database access is secure. HTTPS is recommended.",
"importMethod": "Import Method", "importMethod": "Import Method",
"privateKey": "Private Key", "privateKey": "Private Key",
"mnemonic": "Mnemonic", "mnemonic": "Mnemonic",
@@ -213,9 +213,6 @@
"addressErrorMnemonic": "Cannot derive address from mnemonic", "addressErrorMnemonic": "Cannot derive address from mnemonic",
"walletType": "Wallet Type", "walletType": "Wallet Type",
"walletTypeHelp": "Web3 Wallet: Polymarket accounts connected via browser wallets like MetaMask\nMagic: Polymarket accounts logged in via email or social accounts (Google, Twitter, etc.)", "walletTypeHelp": "Web3 Wallet: Polymarket accounts connected via browser wallets like MetaMask\nMagic: Polymarket accounts logged in via email or social accounts (Google, Twitter, etc.)",
"walletTypeMagic": "Magic (Email/Social Login)",
"walletTypeSafe": "Web3 Wallet",
"magicNotSupported": "",
"loadingProxyOptions": "Loading proxy addresses and asset information...", "loadingProxyOptions": "Loading proxy addresses and asset information...",
"selectProxyOption": "Please select a proxy address", "selectProxyOption": "Please select a proxy address",
"proxyOptionRequired": "Please select a proxy address", "proxyOptionRequired": "Please select a proxy address",
+1 -5
View File
@@ -121,6 +121,7 @@
"importAccount": "导入账户", "importAccount": "导入账户",
"accountName": "账户名称", "accountName": "账户名称",
"walletAddress": "钱包地址", "walletAddress": "钱包地址",
"walletType": "钱包类型",
"proxyAddress": "代理钱包地址", "proxyAddress": "代理钱包地址",
"apiCredentials": "API 凭证", "apiCredentials": "API 凭证",
"balance": "余额", "balance": "余额",
@@ -178,8 +179,6 @@
"accountImport": { "accountImport": {
"title": "导入账户", "title": "导入账户",
"back": "返回", "back": "返回",
"securityTip": "安全提示",
"securityTipDesc": "私钥将存储在后端数据库中,请确保数据库访问安全。建议使用 HTTPS 连接。",
"importMethod": "导入方式", "importMethod": "导入方式",
"privateKey": "私钥", "privateKey": "私钥",
"mnemonic": "助记词", "mnemonic": "助记词",
@@ -213,9 +212,6 @@
"addressErrorMnemonic": "无法从助记词推导地址", "addressErrorMnemonic": "无法从助记词推导地址",
"walletType": "钱包类型", "walletType": "钱包类型",
"walletTypeHelp": "Web3钱包:使用 MetaMask 等浏览器钱包连接的 Polymarket 账户\nMagic:通过邮箱或社交账号(如 Google、Twitter)登录的 Polymarket 账户", "walletTypeHelp": "Web3钱包:使用 MetaMask 等浏览器钱包连接的 Polymarket 账户\nMagic:通过邮箱或社交账号(如 Google、Twitter)登录的 Polymarket 账户",
"walletTypeMagic": "Magic(邮箱/社交账号登录)",
"walletTypeSafe": "Web3钱包",
"magicNotSupported": "",
"loadingProxyOptions": "正在获取代理地址和资产信息...", "loadingProxyOptions": "正在获取代理地址和资产信息...",
"selectProxyOption": "请选择代理地址", "selectProxyOption": "请选择代理地址",
"proxyOptionRequired": "请选择一个代理地址", "proxyOptionRequired": "请选择一个代理地址",
+2 -5
View File
@@ -53,6 +53,7 @@
"accountId": "賬戶ID", "accountId": "賬戶ID",
"accountName": "賬戶名稱", "accountName": "賬戶名稱",
"walletAddress": "錢包地址", "walletAddress": "錢包地址",
"walletType": "錢包類型",
"balance": "賬戶餘額", "balance": "賬戶餘額",
"refreshBalance": "刷新餘額", "refreshBalance": "刷新餘額",
"apiCredentials": "API 憑證配置", "apiCredentials": "API 憑證配置",
@@ -122,6 +123,7 @@
"importAccount": "導入賬戶", "importAccount": "導入賬戶",
"accountName": "賬戶名稱", "accountName": "賬戶名稱",
"walletAddress": "錢包地址", "walletAddress": "錢包地址",
"walletType": "錢包類型",
"proxyAddress": "代理錢包地址", "proxyAddress": "代理錢包地址",
"apiCredentials": "API 憑證", "apiCredentials": "API 憑證",
"balance": "餘額", "balance": "餘額",
@@ -178,8 +180,6 @@
"accountImport": { "accountImport": {
"title": "導入賬戶", "title": "導入賬戶",
"back": "返回", "back": "返回",
"securityTip": "安全提示",
"securityTipDesc": "私鑰將存儲在後端數據庫中,請確保數據庫訪問安全。建議使用 HTTPS 連接。",
"importMethod": "導入方式", "importMethod": "導入方式",
"privateKey": "私鑰", "privateKey": "私鑰",
"mnemonic": "助記詞", "mnemonic": "助記詞",
@@ -213,9 +213,6 @@
"addressErrorMnemonic": "無法從助記詞推導地址", "addressErrorMnemonic": "無法從助記詞推導地址",
"walletType": "錢包類型", "walletType": "錢包類型",
"walletTypeHelp": "Web3錢包:使用 MetaMask 等瀏覽器錢包連接的 Polymarket 帳戶\nMagic:透過郵箱或社群帳號(如 Google、Twitter)登入的 Polymarket 帳戶", "walletTypeHelp": "Web3錢包:使用 MetaMask 等瀏覽器錢包連接的 Polymarket 帳戶\nMagic:透過郵箱或社群帳號(如 Google、Twitter)登入的 Polymarket 帳戶",
"walletTypeMagic": "Magic(郵箱/社群帳號登入)",
"walletTypeSafe": "Web3錢包",
"magicNotSupported": "",
"loadingProxyOptions": "正在獲取代理地址和資產信息...", "loadingProxyOptions": "正在獲取代理地址和資產信息...",
"selectProxyOption": "請選擇代理地址", "selectProxyOption": "請選擇代理地址",
"proxyOptionRequired": "請選擇一個代理地址", "proxyOptionRequired": "請選擇一個代理地址",
+7
View File
@@ -181,6 +181,13 @@ const AccountDetail: React.FC = () => {
<Descriptions.Item label={t('account.accountName')}> <Descriptions.Item label={t('account.accountName')}>
{account.accountName || '-'} {account.accountName || '-'}
</Descriptions.Item> </Descriptions.Item>
{account.walletType && (
<Descriptions.Item label={t('account.walletType')}>
<Tag color={account.walletType.toLowerCase() === 'magic' ? 'purple' : 'blue'}>
{account.walletType.toLowerCase() === 'magic' ? 'Magic' : 'Safe'}
</Tag>
</Descriptions.Item>
)}
<Descriptions.Item label={t('account.walletAddress')} span={isMobile ? 1 : 2}> <Descriptions.Item label={t('account.walletAddress')} span={isMobile ? 1 : 2}>
<span style={{ <span style={{
fontFamily: 'monospace', fontFamily: 'monospace',
-2
View File
@@ -35,8 +35,6 @@ const AccountImport: React.FC = () => {
form={form} form={form}
onSuccess={handleSuccess} onSuccess={handleSuccess}
onCancel={() => navigate('/accounts')} onCancel={() => navigate('/accounts')}
showAlert={true}
showCancelButton={true}
/> />
</Card> </Card>
</div> </div>
+31 -4
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Card, Table, Button, Space, Tag, Popconfirm, message, Typography, Spin, Modal, Descriptions, Divider, Form, Input, Alert } from 'antd' import { Card, Table, Button, Space, Tag, Popconfirm, message, Typography, Spin, Modal, Descriptions, Divider, Form, Input } from 'antd'
import { PlusOutlined, ReloadOutlined, EditOutlined, CopyOutlined } from '@ant-design/icons' import { PlusOutlined, ReloadOutlined, EditOutlined, CopyOutlined } from '@ant-design/icons'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useAccountStore } from '../store/accountStore' import { useAccountStore } from '../store/accountStore'
@@ -303,6 +303,20 @@ const AccountList: React.FC = () => {
) )
} }
}, },
{
title: t('accountList.walletType'),
dataIndex: 'walletType',
key: 'walletType',
render: (walletType: string) => {
if (!walletType) return '-'
const type = walletType.toLowerCase()
return (
<Tag color={type === 'magic' ? 'purple' : 'blue'}>
{type === 'magic' ? 'Magic' : 'Safe'}
</Tag>
)
}
},
{ {
title: t('accountList.balance'), title: t('accountList.balance'),
dataIndex: 'balance', dataIndex: 'balance',
@@ -392,7 +406,7 @@ const AccountList: React.FC = () => {
style={{ marginLeft: '4px', padding: '0 4px' }} style={{ marginLeft: '4px', padding: '0 4px' }}
/> />
</div> </div>
<div> <div style={{ marginBottom: '4px' }}>
<strong>{t('accountList.proxyAddress')}:</strong> {record.proxyAddress ? `${record.proxyAddress.slice(0, 6)}...${record.proxyAddress.slice(-4)}` : '-'} <strong>{t('accountList.proxyAddress')}:</strong> {record.proxyAddress ? `${record.proxyAddress.slice(0, 6)}...${record.proxyAddress.slice(-4)}` : '-'}
<Button <Button
type="text" type="text"
@@ -405,6 +419,14 @@ const AccountList: React.FC = () => {
style={{ marginLeft: '4px', padding: '0 4px' }} style={{ marginLeft: '4px', padding: '0 4px' }}
/> />
</div> </div>
{record.walletType && (
<div style={{ marginBottom: '4px' }}>
<strong>{t('accountList.walletType')}:</strong>{' '}
<Tag color={record.walletType.toLowerCase() === 'magic' ? 'purple' : 'blue'} style={{ marginLeft: '4px' }}>
{record.walletType.toLowerCase() === 'magic' ? 'Magic' : 'Safe'}
</Tag>
</div>
)}
</div> </div>
<div style={{ <div style={{
fontSize: '14px', fontSize: '14px',
@@ -654,6 +676,13 @@ const AccountList: React.FC = () => {
/> />
</Space> </Space>
</Descriptions.Item> </Descriptions.Item>
{detailAccount.walletType && (
<Descriptions.Item label={t('accountList.walletType')}>
<Tag color={detailAccount.walletType.toLowerCase() === 'magic' ? 'purple' : 'blue'}>
{detailAccount.walletType.toLowerCase() === 'magic' ? 'Magic' : 'Safe'}
</Tag>
</Descriptions.Item>
)}
<Descriptions.Item label={t('accountList.totalBalance')} span={isMobile ? 1 : 2}> <Descriptions.Item label={t('accountList.totalBalance')} span={isMobile ? 1 : 2}>
{detailBalanceLoading ? ( {detailBalanceLoading ? (
<Spin size="small" /> <Spin size="small" />
@@ -868,8 +897,6 @@ const AccountList: React.FC = () => {
setAccountImportModalVisible(false) setAccountImportModalVisible(false)
accountImportForm.resetFields() accountImportForm.resetFields()
}} }}
showAlert={true}
showCancelButton={true}
/> />
</Modal> </Modal>
</div> </div>
@@ -1120,8 +1120,6 @@ const AddModal: React.FC<AddModalProps> = ({
setAccountImportModalVisible(false) setAccountImportModalVisible(false)
accountImportForm.resetFields() accountImportForm.resetFields()
}} }}
showAlert={true}
showCancelButton={true}
/> />
</Modal> </Modal>