diff --git a/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/MainActivity.kt b/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/MainActivity.kt index e5774592..e69022e1 100644 --- a/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/MainActivity.kt +++ b/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/MainActivity.kt @@ -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(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() } ) } diff --git a/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/data/repository/TssRepository.kt b/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/data/repository/TssRepository.kt index 1e297665..d3ed45b0 100644 --- a/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/data/repository/TssRepository.kt +++ b/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/data/repository/TssRepository.kt @@ -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) } } diff --git a/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/presentation/screens/TransactionHistoryScreen.kt b/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/presentation/screens/TransactionHistoryScreen.kt index 4534d1c5..74114487 100644 --- a/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/presentation/screens/TransactionHistoryScreen.kt +++ b/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/presentation/screens/TransactionHistoryScreen.kt @@ -37,12 +37,24 @@ fun TransactionHistoryScreen( transactions: List, 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("交易记录") }, diff --git a/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/presentation/viewmodel/MainViewModel.kt b/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/presentation/viewmodel/MainViewModel.kt index 8818a9c4..8b6d838b 100644 --- a/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/presentation/viewmodel/MainViewModel.kt +++ b/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/presentation/viewmodel/MainViewModel.kt @@ -925,6 +925,13 @@ class MainViewModel @Inject constructor( private val _isSyncingHistory = MutableStateFlow(false) val isSyncingHistory: StateFlow = _isSyncingHistory.asStateFlow() + private val _syncResultMessage = MutableStateFlow(null) + val syncResultMessage: StateFlow = _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