Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
65 changes: 48 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 'secret' was never used; consider replacing with '_' or removing it

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 All @@ -313,7 +351,7 @@
/// Check if a transaction spends a closed channel's funding UTXO
private func findClosedChannelForTransaction(txid: String) async -> String? {
do {
let closedChannels = try await getAllClosedChannels(sortDirection: .desc)

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

View workflow job for this annotation

GitHub Actions / Run Tests

no 'async' operations occur within 'await' expression
guard !closedChannels.isEmpty else { return nil }

let txDetails = try await AddressChecker.getTransaction(txid: txid)
Expand Down Expand Up @@ -693,25 +731,18 @@
"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 original activity as doesExist = false instead of deleting it
// This allows it to be displayed with the "removed" status
Logger.debug(
"Attempting to delete original activity \(activityId) before RBF replacement", context: "CoreService.boostOnchainTransaction"
"Marking original activity \(activityId) as doesExist = false (replaced by RBF)", 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"
)
}
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
7 changes: 6 additions & 1 deletion Bitkit/Views/Wallets/Activity/ActivityItemView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,12 @@ struct ActivityItemView: View {
BodySSBText(t("wallet__activity_failed"), textColor: .purpleAccent)
}
case let .onchain(activity):
if activity.confirmed == true {
if !activity.doesExist {
Image("x-circle")
.foregroundColor(.textSecondary)
.frame(width: 16, height: 16)
BodySSBText(t("wallet__activity_removed_title"), textColor: .textSecondary)
} 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