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:
@@ -248,6 +248,62 @@ class BlockchainService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查代理钱包是否已部署(链上有合约代码)
|
||||||
|
* @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 余额
|
* 查询账户 USDC 余额
|
||||||
* 通过 Polygon RPC 查询 ERC-20 代币余额
|
* 通过 Polygon RPC 查询 ERC-20 代币余额
|
||||||
|
|||||||
@@ -400,8 +400,6 @@ class RpcUrlReplaceInterceptor(
|
|||||||
private val fixedBaseUrl: String,
|
private val fixedBaseUrl: String,
|
||||||
private val actualRpcUrl: String
|
private val actualRpcUrl: String
|
||||||
) : Interceptor {
|
) : Interceptor {
|
||||||
private val logger = LoggerFactory.getLogger(RpcUrlReplaceInterceptor::class.java)
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val originalRequest = chain.request()
|
val originalRequest = chain.request()
|
||||||
@@ -415,8 +413,6 @@ class RpcUrlReplaceInterceptor(
|
|||||||
val newUrl = newUrlString.toHttpUrlOrNull()
|
val newUrl = newUrlString.toHttpUrlOrNull()
|
||||||
?: throw IllegalArgumentException("无效的 RPC URL: $newUrlString")
|
?: throw IllegalArgumentException("无效的 RPC URL: $newUrlString")
|
||||||
|
|
||||||
logger.debug("RPC URL 替换: $originalUrlString -> $newUrlString")
|
|
||||||
|
|
||||||
val newRequest = originalRequest.newBuilder()
|
val newRequest = originalRequest.newBuilder()
|
||||||
.url(newUrl)
|
.url(newUrl)
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
// 检查账户设置状态
|
// 检查账户设置状态
|
||||||
let willShowSetupModal = false
|
let willShowSetupModal = false
|
||||||
try {
|
try {
|
||||||
const setupResponse = await apiService.accounts.checkSetupStatus(accountId)
|
const setupResponse = await apiService.accounts.checkSetupStatus(newAccount.id)
|
||||||
if (setupResponse.data.code === 0 && setupResponse.data.data) {
|
if (setupResponse.data.code === 0 && setupResponse.data.data) {
|
||||||
const status = setupResponse.data.data
|
const status = setupResponse.data.data
|
||||||
setSetupStatus(status)
|
setSetupStatus(status)
|
||||||
@@ -284,7 +284,7 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
}
|
}
|
||||||
// 未展示设置弹窗时才调用 onSuccess,避免父组件关闭导入弹窗导致设置弹窗被卸载
|
// 未展示设置弹窗时才调用 onSuccess,避免父组件关闭导入弹窗导致设置弹窗被卸载
|
||||||
if (!willShowSetupModal && onSuccess) {
|
if (!willShowSetupModal && onSuccess) {
|
||||||
onSuccess(accountId)
|
onSuccess(newAccount.id)
|
||||||
}
|
}
|
||||||
} else if (onSuccess) {
|
} else if (onSuccess) {
|
||||||
onSuccess(0)
|
onSuccess(0)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
ReloadOutlined
|
ReloadOutlined
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useMediaQuery } from 'react-responsive'
|
|
||||||
import { apiService } from '../services/api'
|
import { apiService } from '../services/api'
|
||||||
|
|
||||||
const { Paragraph, Text } = Typography
|
const { Paragraph, Text } = Typography
|
||||||
@@ -47,7 +46,6 @@ const AccountSetupStatusBlock: React.FC<AccountSetupStatusBlockProps> = ({
|
|||||||
embedded = false
|
embedded = false
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
|
||||||
const [setupStatus, setSetupStatus] = useState<SetupStatus | null>(null)
|
const [setupStatus, setSetupStatus] = useState<SetupStatus | null>(null)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [refreshing, setRefreshing] = useState(false)
|
const [refreshing, setRefreshing] = useState(false)
|
||||||
|
|||||||
Reference in New Issue
Block a user