fix(android): 修复交易记录同步功能
- 修复 fromBlock 使用 "earliest" 导致 RPC 请求超时的问题 - 改为只扫描最近 50000 个区块(约 1-2 个月历史) - 添加自动获取当前区块号功能 - 进入交易记录页面时自动触发同步 - 添加同步结果提示消息(Snackbar) - 增加详细的调试日志用于排查问题 - 暂时禁用原生 KAVA 交易同步(KavaScan API 需验证) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3727b0e817
commit
a516006117
|
|
@ -113,6 +113,7 @@ fun TssPartyApp(
|
|||
// Transaction history state
|
||||
val transactionRecords by viewModel.transactionRecords.collectAsState()
|
||||
val isSyncingHistory by viewModel.isSyncingHistory.collectAsState()
|
||||
val syncResultMessage by viewModel.syncResultMessage.collectAsState()
|
||||
|
||||
// Current transfer wallet
|
||||
var transferWalletId by remember { mutableStateOf<Long?>(null) }
|
||||
|
|
@ -390,9 +391,13 @@ fun TssPartyApp(
|
|||
val shareId = backStackEntry.arguments?.getString("shareId")?.toLongOrNull() ?: 0L
|
||||
val address = backStackEntry.arguments?.getString("address") ?: ""
|
||||
|
||||
// Load records when entering screen
|
||||
LaunchedEffect(shareId) {
|
||||
// Load records and sync when entering screen
|
||||
LaunchedEffect(shareId, address) {
|
||||
viewModel.loadTransactionRecords(shareId)
|
||||
// Auto-sync from blockchain on first entry
|
||||
if (address.isNotEmpty()) {
|
||||
viewModel.syncTransactionHistory(shareId, address)
|
||||
}
|
||||
}
|
||||
|
||||
TransactionHistoryScreen(
|
||||
|
|
@ -401,8 +406,10 @@ fun TssPartyApp(
|
|||
transactions = transactionRecords,
|
||||
networkType = settings.networkType,
|
||||
isSyncing = isSyncingHistory,
|
||||
syncResultMessage = syncResultMessage,
|
||||
onBack = { navController.popBackStack() },
|
||||
onRefresh = { viewModel.syncTransactionHistory(shareId, address) }
|
||||
onRefresh = { viewModel.syncTransactionHistory(shareId, address) },
|
||||
onClearSyncMessage = { viewModel.clearSyncResultMessage() }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3121,6 +3121,39 @@ data class ParticipantStatusInfo(
|
|||
* 同步 ERC-20 代币的历史交易
|
||||
* 使用 eth_getLogs 查询 Transfer 事件
|
||||
*/
|
||||
/**
|
||||
* 获取当前区块号
|
||||
*/
|
||||
private suspend fun getCurrentBlockNumber(client: okhttp3.OkHttpClient, rpcUrl: String): String? {
|
||||
return try {
|
||||
val jsonMediaType = "application/json; charset=utf-8".toMediaType()
|
||||
val request = """
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "eth_blockNumber",
|
||||
"params": [],
|
||||
"id": 1
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val response = client.newCall(
|
||||
okhttp3.Request.Builder()
|
||||
.url(rpcUrl)
|
||||
.post(request.toRequestBody(jsonMediaType))
|
||||
.build()
|
||||
).execute()
|
||||
|
||||
val body = response.body?.string()
|
||||
if (body != null) {
|
||||
val json = com.google.gson.JsonParser.parseString(body).asJsonObject
|
||||
json.get("result")?.asString
|
||||
} else null
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.w("TssRepository", "[BLOCK-NUMBER] Failed to get current block: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun syncERC20TransactionHistory(
|
||||
shareId: Long,
|
||||
address: String,
|
||||
|
|
@ -3138,6 +3171,19 @@ data class ParticipantStatusInfo(
|
|||
.build()
|
||||
val jsonMediaType = "application/json; charset=utf-8".toMediaType()
|
||||
|
||||
// 获取当前区块号
|
||||
val currentBlockHex = getCurrentBlockNumber(client, rpcUrl)
|
||||
val currentBlock = currentBlockHex?.removePrefix("0x")?.toLongOrNull(16) ?: 0L
|
||||
|
||||
// 只查询最近 50000 个区块的历史(约 1-2 个月)
|
||||
val fromBlock = if (currentBlock > 50000) {
|
||||
"0x${(currentBlock - 50000).toString(16)}"
|
||||
} else {
|
||||
"0x0"
|
||||
}
|
||||
|
||||
android.util.Log.d("TssRepository", "[SYNC-ERC20] Scanning from block $fromBlock to latest (current: $currentBlockHex)")
|
||||
|
||||
// Transfer event signature: keccak256("Transfer(address,address,uint256)")
|
||||
val transferTopic = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
|
||||
|
||||
|
|
@ -3154,7 +3200,7 @@ data class ParticipantStatusInfo(
|
|||
"params": [{
|
||||
"address": "$contractAddress",
|
||||
"topics": ["$transferTopic", "$paddedAddress", null],
|
||||
"fromBlock": "earliest",
|
||||
"fromBlock": "$fromBlock",
|
||||
"toBlock": "latest"
|
||||
}],
|
||||
"id": 1
|
||||
|
|
@ -3169,7 +3215,9 @@ data class ParticipantStatusInfo(
|
|||
).execute()
|
||||
|
||||
val sentBody = sentResponse.body?.string()
|
||||
android.util.Log.d("TssRepository", "[SYNC-ERC20] SENT response code: ${sentResponse.code}")
|
||||
if (sentBody != null) {
|
||||
android.util.Log.d("TssRepository", "[SYNC-ERC20] SENT response preview: ${sentBody.take(500)}")
|
||||
totalSynced += parseAndSaveTransferLogs(sentBody, shareId, address, tokenType, "SENT")
|
||||
}
|
||||
|
||||
|
|
@ -3181,7 +3229,7 @@ data class ParticipantStatusInfo(
|
|||
"params": [{
|
||||
"address": "$contractAddress",
|
||||
"topics": ["$transferTopic", null, "$paddedAddress"],
|
||||
"fromBlock": "earliest",
|
||||
"fromBlock": "$fromBlock",
|
||||
"toBlock": "latest"
|
||||
}],
|
||||
"id": 2
|
||||
|
|
@ -3196,7 +3244,9 @@ data class ParticipantStatusInfo(
|
|||
).execute()
|
||||
|
||||
val receivedBody = receivedResponse.body?.string()
|
||||
android.util.Log.d("TssRepository", "[SYNC-ERC20] RECEIVED response code: ${receivedResponse.code}")
|
||||
if (receivedBody != null) {
|
||||
android.util.Log.d("TssRepository", "[SYNC-ERC20] RECEIVED response preview: ${receivedBody.take(500)}")
|
||||
totalSynced += parseAndSaveTransferLogs(receivedBody, shareId, address, tokenType, "RECEIVED")
|
||||
}
|
||||
|
||||
|
|
@ -3222,7 +3272,17 @@ data class ParticipantStatusInfo(
|
|||
var count = 0
|
||||
try {
|
||||
val json = com.google.gson.JsonParser.parseString(responseBody).asJsonObject
|
||||
val result = json.getAsJsonArray("result") ?: return 0
|
||||
val error = json.get("error")?.asJsonObject
|
||||
if (error != null) {
|
||||
android.util.Log.e("TssRepository", "[SYNC-ERC20] RPC error: ${error.get("message")?.asString}")
|
||||
return 0
|
||||
}
|
||||
val result = json.getAsJsonArray("result")
|
||||
if (result == null) {
|
||||
android.util.Log.w("TssRepository", "[SYNC-ERC20] No result array in response")
|
||||
return 0
|
||||
}
|
||||
android.util.Log.d("TssRepository", "[SYNC-ERC20] Found ${result.size()} logs for $direction ${tokenType.name}")
|
||||
|
||||
for (log in result) {
|
||||
try {
|
||||
|
|
@ -3326,12 +3386,18 @@ data class ParticipantStatusInfo(
|
|||
return@withContext Result.success(0)
|
||||
}
|
||||
|
||||
android.util.Log.d("TssRepository", "[SYNC-KAVA] Response code: ${response.code}, body length: ${responseBody.length}")
|
||||
android.util.Log.d("TssRepository", "[SYNC-KAVA] Response preview: ${responseBody.take(500)}")
|
||||
|
||||
var count = 0
|
||||
try {
|
||||
val json = com.google.gson.JsonParser.parseString(responseBody).asJsonObject
|
||||
val status = json.get("status")?.asString
|
||||
val message = json.get("message")?.asString
|
||||
val result = json.getAsJsonArray("result")
|
||||
|
||||
android.util.Log.d("TssRepository", "[SYNC-KAVA] Parsed: status=$status, message=$message, resultSize=${result?.size() ?: "null"}")
|
||||
|
||||
if (status == "1" && result != null) {
|
||||
for (tx in result) {
|
||||
try {
|
||||
|
|
@ -3434,14 +3500,17 @@ data class ParticipantStatusInfo(
|
|||
}
|
||||
}
|
||||
|
||||
// 同步原生 KAVA
|
||||
// 暂时跳过原生 KAVA 同步 - KavaScan API 格式需要验证
|
||||
android.util.Log.d("TssRepository", "[SYNC-ALL] Skipping native KAVA sync (KavaScan API needs verification)")
|
||||
/* 暂时禁用原生交易同步
|
||||
syncNativeTransactionHistory(shareId, address, networkType)
|
||||
.onSuccess { count -> totalSynced += count }
|
||||
.onFailure { e ->
|
||||
android.util.Log.w("TssRepository", "[SYNC-ALL] Failed to sync native KAVA: ${e.message}")
|
||||
}
|
||||
*/
|
||||
|
||||
android.util.Log.d("TssRepository", "[SYNC-ALL] Completed: synced $totalSynced total transactions")
|
||||
android.util.Log.d("TssRepository", "[SYNC-ALL] Completed: synced $totalSynced ERC-20 transactions (native KAVA sync disabled)")
|
||||
Result.success(totalSynced)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,12 +37,24 @@ fun TransactionHistoryScreen(
|
|||
transactions: List<TransactionRecordEntity>,
|
||||
networkType: NetworkType,
|
||||
isSyncing: Boolean,
|
||||
syncResultMessage: String? = null,
|
||||
onBack: () -> Unit,
|
||||
onRefresh: () -> Unit
|
||||
onRefresh: () -> Unit,
|
||||
onClearSyncMessage: () -> Unit = {}
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
// Show snackbar when sync result message changes
|
||||
LaunchedEffect(syncResultMessage) {
|
||||
syncResultMessage?.let { message ->
|
||||
snackbarHostState.showSnackbar(message)
|
||||
onClearSyncMessage()
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("交易记录") },
|
||||
|
|
|
|||
|
|
@ -925,6 +925,13 @@ class MainViewModel @Inject constructor(
|
|||
private val _isSyncingHistory = MutableStateFlow(false)
|
||||
val isSyncingHistory: StateFlow<Boolean> = _isSyncingHistory.asStateFlow()
|
||||
|
||||
private val _syncResultMessage = MutableStateFlow<String?>(null)
|
||||
val syncResultMessage: StateFlow<String?> = _syncResultMessage.asStateFlow()
|
||||
|
||||
fun clearSyncResultMessage() {
|
||||
_syncResultMessage.value = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载钱包的交易记录
|
||||
*/
|
||||
|
|
@ -952,9 +959,15 @@ class MainViewModel @Inject constructor(
|
|||
result.fold(
|
||||
onSuccess = { count ->
|
||||
android.util.Log.d("MainViewModel", "[SYNC] Synced $count transactions")
|
||||
_syncResultMessage.value = if (count > 0) {
|
||||
"同步完成,新增 $count 条记录"
|
||||
} else {
|
||||
"同步完成,无新记录"
|
||||
}
|
||||
},
|
||||
onFailure = { e ->
|
||||
android.util.Log.e("MainViewModel", "[SYNC] Error syncing: ${e.message}")
|
||||
_syncResultMessage.value = "同步失败: ${e.message}"
|
||||
}
|
||||
)
|
||||
_isSyncingHistory.value = false
|
||||
|
|
|
|||
Loading…
Reference in New Issue