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:
hailin 2026-01-26 07:41:24 -08:00
parent 3727b0e817
commit a516006117
4 changed files with 110 additions and 9 deletions

View File

@ -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() }
)
}

View File

@ -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)
}
}

View File

@ -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("交易记录") },

View File

@ -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