Skip to content
Merged
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
72 changes: 55 additions & 17 deletions Bitkit/Services/CoreService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@

var isConfirmed = false
var confirmedTimestamp: UInt64?
if case let .confirmed(blockHash, height, blockTimestamp) = txStatus {

Check warning on line 130 in Bitkit/Services/CoreService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

immutable value 'height' was never used; consider replacing with '_' or removing it

Check warning on line 130 in Bitkit/Services/CoreService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

immutable value 'blockHash' was never used; consider replacing with '_' or removing it
isConfirmed = true
confirmedTimestamp = blockTimestamp
}
Expand All @@ -150,6 +150,7 @@
let preservedTransferTxId = existingOnchain?.transferTxId
let preservedFeeRate = existingOnchain?.feeRate ?? 1
let preservedAddress = existingOnchain?.address ?? "Loading..."
let preservedDoesExist = existingOnchain?.doesExist ?? true

// Check if this transaction is a channel transfer (open or close)
if preservedChannelId == nil || !preservedIsTransfer {
Expand Down Expand Up @@ -211,6 +212,9 @@
let finalChannelId = preservedChannelId
let finalTransferTxId = preservedTransferTxId

// If confirmed, set doesExist to true; otherwise preserve existing value
let finalDoesExist = isConfirmed ? true : preservedDoesExist

let onchain = OnchainActivity(
id: payment.id,
txType: payment.direction == .outbound ? .sent : .received,
Expand All @@ -224,7 +228,7 @@
isBoosted: shouldMarkAsBoosted, // Mark as boosted if it's a replacement transaction
boostTxIds: boostTxIds,
isTransfer: finalIsTransfer,
doesExist: true,
doesExist: finalDoesExist,
confirmTimestamp: confirmedTimestamp,
channelId: finalChannelId,
transferTxId: finalTransferTxId,
Expand All @@ -241,7 +245,12 @@
print(payment)
addedCount += 1
}

// If a removed transaction confirms, mark its replacement transactions as removed
if !preservedDoesExist && isConfirmed {
try await self.markReplacementTransactionsAsRemoved(originalTxId: txid)
}
} else if case let .bolt11(hash, preimage, secret, description, bolt11) = payment.kind {

Check warning on line 253 in Bitkit/Services/CoreService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

immutable value 'hash' was never used; consider replacing with '_' or removing it
// Skip pending inbound payments, just means they created an invoice
guard !(payment.status == .pending && payment.direction == .inbound) else { continue }

Expand Down Expand Up @@ -298,6 +307,35 @@
}
}

/// Marks replacement transactions (with originalTxId in boostTxIds) as doesExist = false when original confirms
private func markReplacementTransactionsAsRemoved(originalTxId: String) async throws {
let allActivities = try getActivities(
filter: .onchain,
txType: nil,
tags: nil,
search: nil,
minDate: nil,
maxDate: nil,
limit: nil,
sortDirection: nil
)

for activity in allActivities {
guard case let .onchain(onchainActivity) = activity else { continue }

if onchainActivity.boostTxIds.contains(originalTxId) && onchainActivity.doesExist {
Logger.info(
"Marking replacement transaction \(onchainActivity.txId) as doesExist = false (original \(originalTxId) confirmed)",
context: "CoreService.markReplacementTransactionsAsRemoved"
)

var updatedActivity = onchainActivity
updatedActivity.doesExist = false
try updateActivity(activityId: onchainActivity.id, activity: .onchain(updatedActivity))
}
}
}

/// Finds the channel ID associated with a transaction based on its direction
private func findChannelForTransaction(txid: String, direction: PaymentDirection) async -> String? {
switch direction {
Expand Down Expand Up @@ -693,25 +731,25 @@
"Added original transaction \(onchainActivity.txId) to replaced transactions list", context: "CoreService.boostOnchainTransaction"
)

// For RBF, delete the original activity since it's been replaced
// The new transaction will be synced automatically from LDK
// For RBF, mark the old activity as boosted before marking it as replaced
onchainActivity.isBoosted = true
Logger.debug(
"Attempting to delete original activity \(activityId) before RBF replacement", context: "CoreService.boostOnchainTransaction"
"Marked original activity \(activityId) as boosted before RBF replacement",
context: "CoreService.boostOnchainTransaction"
)

// Use the proper delete function that returns a Bool
let deleteResult = try deleteActivityById(activityId: activityId)
Logger.info("Delete result for original activity \(activityId): \(deleteResult)", context: "CoreService.boostOnchainTransaction")

// Double-check that the activity was deleted
let checkActivity = try getActivityById(activityId: activityId)
if checkActivity == nil {
Logger.info("Confirmed: Original activity \(activityId) was successfully deleted", context: "CoreService.boostOnchainTransaction")
} else {
Logger.error(
"Warning: Original activity \(activityId) still exists after deletion attempt", context: "CoreService.boostOnchainTransaction"
)
}
// For RBF, mark the original activity as doesExist = false instead of deleting it
// This allows it to be displayed with the "removed" status
Logger.debug(
"Marking original activity \(activityId) as doesExist = false (replaced by RBF)", context: "CoreService.boostOnchainTransaction"
)

onchainActivity.doesExist = false
try updateActivity(activityId: activityId, activity: .onchain(onchainActivity))
Logger.info(
"Successfully marked activity \(activityId) as doesExist = false (replaced by RBF)",
context: "CoreService.boostOnchainTransaction"
)

self.activitiesChangedSubject.send()
}
Expand Down
10 changes: 10 additions & 0 deletions Bitkit/Views/Wallets/Activity/ActivityIcon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ struct ActivityIcon: View {
let size: CGFloat
let isBoosted: Bool
let isTransfer: Bool
let doesExist: Bool

init(activity: Activity, size: CGFloat = 32) {
self.size = size
Expand All @@ -20,13 +21,15 @@ struct ActivityIcon: View {
txType = ln.txType
isBoosted = false
isTransfer = false
doesExist = true
case let .onchain(onchain):
isLightning = false
status = nil
confirmed = onchain.confirmed
txType = onchain.txType
isBoosted = onchain.isBoosted
isTransfer = onchain.isTransfer
doesExist = onchain.doesExist
}
}

Expand Down Expand Up @@ -55,6 +58,13 @@ struct ActivityIcon: View {
size: size
)
}
} else if !doesExist {
CircularIcon(
icon: "x-mark",
iconColor: .redAccent,
backgroundColor: .red16,
size: size
)
} else if isBoosted && !(confirmed ?? false) {
CircularIcon(
icon: "timer-alt",
Expand Down
12 changes: 11 additions & 1 deletion Bitkit/Views/Wallets/Activity/ActivityItemView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ struct ActivityItemView: View {
private var statusAccessibilityIdentifier: String? {
switch viewModel.activity {
case let .onchain(activity):
if !activity.doesExist {
return "StatusRemoved"
}
if activity.confirmed == true {
return "StatusConfirmed"
}
Expand Down Expand Up @@ -225,7 +228,14 @@ struct ActivityItemView: View {
BodySSBText(t("wallet__activity_failed"), textColor: .purpleAccent)
}
case let .onchain(activity):
if activity.confirmed == true {
if !activity.doesExist {
Image("x-mark")
.resizable()
.scaledToFit()
.foregroundColor(.redAccent)
.frame(width: 16, height: 16)
BodySSBText(t("wallet__activity_removed"), textColor: .redAccent)
} else if activity.confirmed == true {
Image("check-circle")
.foregroundColor(.greenAccent)
.frame(width: 16, height: 16)
Expand Down
4 changes: 4 additions & 0 deletions Bitkit/Views/Wallets/Activity/ActivityRowOnchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ struct ActivityRowOnchain: View {
}

private var description: String {
if !item.doesExist {
return t("wallet__activity_removed")
}

if item.isTransfer {
switch item.txType {
case .sent:
Expand Down
Loading