Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions Bitkit/Services/MigrationsService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ class MigrationsService: ObservableObject {
private static let rnPendingChannelMigrationKey = "rnPendingChannelMigration"
private static let rnPendingBlocktankOrderIdsKey = "rnPendingBlocktankOrderIds"
private static let rnDidAttemptPeerRecoveryKey = "rnDidAttemptMigrationPeerRecovery"
private static let didCleanupInvalidTransfersKey = "didCleanupInvalidMigrationTransfers"

@Published var isShowingMigrationLoading = false {
didSet {
Expand Down Expand Up @@ -759,6 +760,47 @@ extension MigrationsService {
return true
}

/// One-time cleanup for transfers created from unpaid/expired Blocktank orders during migration.
/// The RN backup's paidOrders map could contain orders that were never actually paid.
func cleanupInvalidMigrationTransfers() async {
guard !UserDefaults.standard.bool(forKey: Self.didCleanupInvalidTransfersKey) else { return }
guard rnMigrationCompleted else { return }

guard let transfers = try? TransferStorage.shared.getActiveTransfers() else { return }
let orderTransfers = transfers.filter { $0.type.isToSpending() && $0.lspOrderId != nil }

guard !orderTransfers.isEmpty else {
UserDefaults.standard.set(true, forKey: Self.didCleanupInvalidTransfersKey)
return
}

let orderIds = orderTransfers.compactMap(\.lspOrderId)

guard let orders = try? await CoreService.shared.blocktank.orders(orderIds: orderIds, filter: nil, refresh: true) else {
// Don't mark as done if we can't reach Blocktank — retry next launch
Logger.warn("Cannot cleanup migration transfers: Blocktank unreachable", context: "Migration")
return
}

let now = UInt64(Date().timeIntervalSince1970)
for transfer in orderTransfers {
guard let orderId = transfer.lspOrderId,
let order = orders.first(where: { $0.id == orderId })
else { continue }

if order.state2 != .paid {
try? TransferStorage.shared.markSettled(id: transfer.id, settledAt: now)
Logger.info(
"Cleanup: settled invalid migration transfer \(transfer.id) for order \(orderId) (state: \(order.state2))",
context: "Migration"
)
}
}

UserDefaults.standard.set(true, forKey: Self.didCleanupInvalidTransfersKey)
Logger.info("Migration transfer cleanup completed", context: "Migration")
}

/// Clears all persisted pending migration data from UserDefaults
private func clearPendingMigrationData() {
pendingChannelMigration = nil
Expand Down Expand Up @@ -2280,14 +2322,16 @@ extension MigrationsService {
continue
}

if order.state2 == .executed {
// Only create transfers for orders actually paid and awaiting channel
guard order.state2 == .paid else {
Logger.debug("Skipping order \(orderId) with state \(order.state2) for transfer creation", context: "Migration")
continue
}

let transfer = Transfer(
id: txId,
type: .toSpending,
amountSats: order.clientBalanceSat + order.feeSat,
amountSats: order.clientBalanceSat,
channelId: nil,
fundingTxId: nil,
lspOrderId: orderId,
Expand Down
12 changes: 11 additions & 1 deletion Bitkit/Services/TransferService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,17 @@ class TransferService {
Logger.debug("Channel \(channelId) exists but not yet usable for transfer: \(transfer.id)", context: "TransferService")
}
} else {
// No channel ID resolved - check if we should timeout this transfer
// No channel ID resolved - check if order is expired/terminal
if let orderId = transfer.lspOrderId {
if let orders = try? await blocktankService.orders(orderIds: [orderId], filter: nil, refresh: false),
let order = orders.first,
order.state2 == .expired
{
try await markSettled(id: transfer.id)
Logger.info("Order \(orderId) expired, settled transfer: \(transfer.id)", context: "TransferService")
continue
}
}
Logger.debug(
"Could not resolve channel for transfer: \(transfer.id) orderId: \(transfer.lspOrderId ?? "none")",
context: "TransferService"
Expand Down
2 changes: 1 addition & 1 deletion Bitkit/ViewModels/WalletViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,6 @@ class WalletViewModel: ObservableObject {
break
}


var info: IBtInfo?
do {
info = try await coreService.blocktank.info(refresh: true)
Expand Down Expand Up @@ -658,6 +657,7 @@ class WalletViewModel: ObservableObject {
await lightningService.refreshCache()

do {
await MigrationsService.shared.cleanupInvalidMigrationTransfers()
try? await transferService.syncTransferStates()
let state = try await balanceManager.deriveBalanceState()
balanceInTransferToSavings = Int(state.balanceInTransferToSavings)
Expand Down
Loading