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:
@@ -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", "Safe(Web3钱包)");
|
||||||
|
|
||||||
|
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 Proxy(MetaMask 钱包用户)- 通过合约调用获取地址
|
* 2. Safe Proxy(MetaMask 钱包用户)- 通过合约调用获取地址
|
||||||
*
|
*
|
||||||
* @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 Proxy(MetaMask 用户)
|
// Safe Proxy(MetaMask 用户)
|
||||||
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 市场条件ID(bytes32,必须是 0x 开头的 66 位十六进制字符串)
|
* @param conditionId 市场条件ID(bytes32,必须是 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()) {
|
||||||
|
|||||||
+5
-3
@@ -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"
|
||||||
|
|||||||
+12
-7
@@ -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
|
||||||
@@ -45,6 +46,10 @@ class RelayClientService(
|
|||||||
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()
|
||||||
retrofitFactory.createEthereumRpcApi(rpcUrl)
|
retrofitFactory.createEthereumRpcApi(rpcUrl)
|
||||||
@@ -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 Gasless,SAFE 使用 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 Key(Gasless)"))
|
return Result.failure(IllegalStateException("Magic 账户赎回必须配置 Builder API Key(Gasless)"))
|
||||||
}
|
}
|
||||||
@@ -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,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"
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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": "请选择一个代理地址",
|
||||||
|
|||||||
@@ -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": "請選擇一個代理地址",
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user