chore: 账户设置与杂项修复

- BlockchainService: 新增 isProxyDeployed、getUsdcAllowance(账户设置检查/授权用)
- RetrofitFactory: 移除 RPC URL 替换 debug 日志
- AccountImportForm: 修复 checkSetupStatus/onSuccess 参数类型(使用 newAccount.id)
- AccountSetupStatusBlock: 移除未使用的 isMobile 与 useMediaQuery 引用

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WrBug
2026-02-18 03:17:40 +08:00
parent 2013a2eb70
commit cbcebf6e28
4 changed files with 58 additions and 8 deletions
@@ -247,6 +247,62 @@ class BlockchainService(
false
}
}
/**
* 检查代理钱包是否已部署(链上有合约代码)
* @param proxyAddress 代理钱包地址
* @return 已部署返回 true
*/
suspend fun isProxyDeployed(proxyAddress: String): Boolean {
if (proxyAddress.isBlank() || !proxyAddress.startsWith("0x") || proxyAddress.length != 42) {
return false
}
return isContract(proxyAddress)
}
/**
* 查询 ERC20 USDC 授权额度 allowance(owner, spender)
* @param owner 代币持有者地址(代理钱包地址)
* @param spender 被授权方地址(如 CTF Exchange
* @return 授权额度(原始值,USDC 为 6 位小数,需除以 1e6 为显示值)
*/
suspend fun getUsdcAllowance(owner: String, spender: String): Result<BigInteger> {
return try {
if (owner.isBlank() || spender.isBlank()) {
return Result.failure(IllegalArgumentException("owner 或 spender 不能为空"))
}
val rpcApi = polygonRpcApi
// ERC20 allowance(address owner, address spender) 选择器
val functionSelector = "0xdd62ed3e"
val ownerEncoded = EthereumUtils.encodeAddress(owner)
val spenderEncoded = EthereumUtils.encodeAddress(spender)
val data = functionSelector + ownerEncoded + spenderEncoded
val rpcRequest = JsonRpcRequest(
method = "eth_call",
params = listOf(
mapOf(
"to" to usdcContractAddress,
"data" to data
),
"latest"
)
)
val response = rpcApi.call(rpcRequest)
if (!response.isSuccessful || response.body() == null) {
return Result.failure(Exception("RPC 请求失败: ${response.code()} ${response.message()}"))
}
val rpcResponse = response.body()!!
if (rpcResponse.error != null) {
return Result.failure(Exception("RPC 错误: ${rpcResponse.error.message}"))
}
val hexResult = rpcResponse.result?.asString ?: return Result.failure(Exception("RPC 响应 result 为空"))
val allowance = EthereumUtils.decodeUint256(hexResult)
Result.success(allowance)
} catch (e: Exception) {
logger.warn("查询 USDC 授权额度失败: ${e.message}")
Result.failure(e)
}
}
/**
* 查询账户 USDC 余额
@@ -400,8 +400,6 @@ class RpcUrlReplaceInterceptor(
private val fixedBaseUrl: String,
private val actualRpcUrl: String
) : Interceptor {
private val logger = LoggerFactory.getLogger(RpcUrlReplaceInterceptor::class.java)
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
@@ -415,8 +413,6 @@ class RpcUrlReplaceInterceptor(
val newUrl = newUrlString.toHttpUrlOrNull()
?: throw IllegalArgumentException("无效的 RPC URL: $newUrlString")
logger.debug("RPC URL 替换: $originalUrlString -> $newUrlString")
val newRequest = originalRequest.newBuilder()
.url(newUrl)
.build()
@@ -269,7 +269,7 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
// 检查账户设置状态
let willShowSetupModal = false
try {
const setupResponse = await apiService.accounts.checkSetupStatus(accountId)
const setupResponse = await apiService.accounts.checkSetupStatus(newAccount.id)
if (setupResponse.data.code === 0 && setupResponse.data.data) {
const status = setupResponse.data.data
setSetupStatus(status)
@@ -284,7 +284,7 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
}
// 未展示设置弹窗时才调用 onSuccess,避免父组件关闭导入弹窗导致设置弹窗被卸载
if (!willShowSetupModal && onSuccess) {
onSuccess(accountId)
onSuccess(newAccount.id)
}
} else if (onSuccess) {
onSuccess(0)
@@ -10,7 +10,6 @@ import {
ReloadOutlined
} from '@ant-design/icons'
import { useTranslation } from 'react-i18next'
import { useMediaQuery } from 'react-responsive'
import { apiService } from '../services/api'
const { Paragraph, Text } = Typography
@@ -47,7 +46,6 @@ const AccountSetupStatusBlock: React.FC<AccountSetupStatusBlockProps> = ({
embedded = false
}) => {
const { t } = useTranslation()
const isMobile = useMediaQuery({ maxWidth: 768 })
const [setupStatus, setSetupStatus] = useState<SetupStatus | null>(null)
const [loading, setLoading] = useState(true)
const [refreshing, setRefreshing] = useState(false)