Stop a slow chain refresh from showing the wrong wallet balance

This commit is contained in:
2026-09-20 01:30:42 +08:00
parent e53c6aa7a9
commit 399502cf72
+31 -6
View File
@@ -23,6 +23,9 @@ final class AppModel: ObservableObject {
private var refreshTask: Task<Void, Never>? private var refreshTask: Task<Void, Never>?
private var lastSample: (height: UInt64, date: Date)? private var lastSample: (height: UInt64, date: Date)?
private var lastCheckpoint = Date.distantPast private var lastCheckpoint = Date.distantPast
/// Invalidates in-flight refreshes: switching chains (or starting a newer
/// refresh) must stop an older request from writing its result to the UI.
private var refreshToken = UUID()
var wallet: WalletRecord? { var wallet: WalletRecord? {
chain == .xrp ? xrpWallet : xmrWallet chain == .xrp ? xrpWallet : xmrWallet
@@ -93,6 +96,7 @@ final class AppModel: ObservableObject {
func select(_ newChain: Chain) async { func select(_ newChain: Chain) async {
chain = newChain chain = newChain
refreshToken = UUID()
errorMessage = nil errorMessage = nil
statusMessage = nil statusMessage = nil
balance = Balance(total: "0", unlocked: "", funded: false) balance = Balance(total: "0", unlocked: "", funded: false)
@@ -200,14 +204,21 @@ final class AppModel: ObservableObject {
func refresh(force: Bool = false) async { func refresh(force: Bool = false) async {
guard hasWallet else { return } guard hasWallet else { return }
let token = UUID()
refreshToken = token
let requestChain = chain
errorMessage = nil errorMessage = nil
if chain == .xrp, let wallet = xrpWallet { if chain == .xrp, let wallet = xrpWallet {
do { do {
async let balanceCall = XRPWalletService.shared.balance(address: wallet.address) async let balanceCall = XRPWalletService.shared.balance(address: wallet.address)
async let txCall = XRPWalletService.shared.transactions(address: wallet.address) async let txCall = XRPWalletService.shared.transactions(address: wallet.address)
balance = try await balanceCall let fetchedBalance = try await balanceCall
transactions = try await txCall let fetchedTransactions = try await txCall
guard !isStale(token, requestChain) else { return }
balance = fetchedBalance
transactions = fetchedTransactions
} catch { } catch {
guard !isStale(token, requestChain) else { return }
errorMessage = error.localizedDescription errorMessage = error.localizedDescription
} }
// The Monero wallet keeps scanning while XRP is on screen, so keep its // The Monero wallet keeps scanning while XRP is on screen, so keep its
@@ -223,22 +234,36 @@ final class AppModel: ObservableObject {
do { do {
// Sync progress is cheap and polled every tick; the balance and transfer // Sync progress is cheap and polled every tick; the balance and transfer
// list touch the wallet's data files, so they are refreshed less often. // list touch the wallet's data files, so they are refreshed less often.
updateSyncStatus(try await MoneroService.shared.syncStatus()) let status = try await MoneroService.shared.syncStatus()
if !isStale(token, requestChain) {
updateSyncStatus(status)
}
refreshTick += 1 refreshTick += 1
await checkpointWalletIfSyncing() await checkpointWalletIfSyncing()
if force || refreshTick == 1 || refreshTick % 4 == 0 { if force || refreshTick == 1 || refreshTick % 4 == 0 {
async let balanceCall = MoneroService.shared.balance() async let balanceCall = MoneroService.shared.balance()
async let txCall = MoneroService.shared.transactions() async let txCall = MoneroService.shared.transactions()
async let accountsCall = MoneroService.shared.accounts() async let accountsCall = MoneroService.shared.accounts()
balance = try await balanceCall let fetchedBalance = try await balanceCall
transactions = try await txCall let fetchedTransactions = try await txCall
moneroAccounts = try await accountsCall let fetchedAccounts = try await accountsCall
guard !isStale(token, requestChain) else { return }
balance = fetchedBalance
transactions = fetchedTransactions
moneroAccounts = fetchedAccounts
} }
} catch { } catch {
guard !isStale(token, requestChain) else { return }
errorMessage = error.localizedDescription errorMessage = error.localizedDescription
} }
} }
/// True when a newer refresh has started or the user moved to another chain
/// while this request was in flight.
private func isStale(_ token: UUID, _ requestChain: Chain) -> Bool {
token != refreshToken || requestChain != chain
}
/// Tracks how fast blocks are being scanned so the UI can show an estimate. /// Tracks how fast blocks are being scanned so the UI can show an estimate.
private func updateSyncStatus(_ status: SyncStatus) { private func updateSyncStatus(_ status: SyncStatus) {
syncStatus = status syncStatus = status