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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 36 additions & 15 deletions Bitkit/Services/CoreService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
class ActivityService {
private let coreService: CoreService

// Track replacement transactions (RBF) to mark them as boosted
private static var replacementTransactions: Set<String> = []
// Track replacement transactions (RBF): newTxId -> parent/original txIds
private static var replacementTransactions: [String: [String]] = [:]

// Track replaced transactions that should be ignored during sync
private static var replacedTransactions: Set<String> = []
Expand Down Expand Up @@ -71,7 +71,7 @@

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

Check warning on line 74 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 74 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 = timestamp
}
Expand All @@ -83,23 +83,36 @@
confirmedTimestamp = timestamp
}

// Get existing activity to preserve certain flags like isBoosted
// Get existing activity to preserve certain flags like isBoosted and boostTxIds
let existingActivity = try getActivityById(activityId: payment.id)
let preservedIsBoosted =
if case let .onchain(existing) = existingActivity {
existing.isBoosted
} else {
false
let existingOnchain: OnchainActivity? = {
if let existingActivity, case let .onchain(existing) = existingActivity {
return existing
}
return nil
}()
let preservedIsBoosted = existingOnchain?.isBoosted ?? false
let preservedBoostTxIds = existingOnchain?.boostTxIds ?? []

// Check if this is a replacement transaction (RBF) that should be marked as boosted
let isReplacementTransaction = ActivityService.replacementTransactions.contains(txid)
let isReplacementTransaction = ActivityService.replacementTransactions.keys.contains(txid)
let shouldMarkAsBoosted = preservedIsBoosted || isReplacementTransaction

// Capture tracked parents for replacement transactions (RBF) before removing from tracking
let trackedParents: [String] = {
if isReplacementTransaction {
return ActivityService.replacementTransactions[txid] ?? []
}
return []
}()

// Use tracked parents when this is a replacement; otherwise keep preserved
let boostTxIds = isReplacementTransaction ? trackedParents : preservedBoostTxIds

if isReplacementTransaction {
Logger.debug("Found replacement transaction \(txid), marking as boosted", context: "CoreService.syncLdkNodePayments")
// Remove from tracking set since we've processed it
ActivityService.replacementTransactions.remove(txid)
// Remove from tracking map since we've processed it
ActivityService.replacementTransactions.removeValue(forKey: txid)

// Also clean up any old replaced transactions that might be lingering
// This helps prevent the replacedTransactions set from growing indefinitely
Expand All @@ -125,6 +138,7 @@
confirmed: isConfirmed,
timestamp: timestamp,
isBoosted: shouldMarkAsBoosted, // Mark as boosted if it's a replacement transaction
boostTxIds: boostTxIds,
isTransfer: false, // TODO: handle when paying for order
doesExist: true,
confirmTimestamp: confirmedTimestamp,
Expand All @@ -143,7 +157,7 @@
print(payment)
addedCount += 1
}
} else if case let .bolt11(hash, preimage, secret, description, bolt11) = payment.kind {

Check warning on line 160 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 160 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 @@ -282,6 +296,7 @@

// For CPFP, mark the original activity as boosted (parent transaction still exists)
onchainActivity.isBoosted = true
onchainActivity.boostTxIds.append(txid)
try updateActivity(activityId: activityId, activity: .onchain(onchainActivity))
Logger.info("Successfully marked activity \(activityId) as boosted via CPFP", context: "CoreService.boostOnchainTransaction")
} else {
Expand All @@ -296,9 +311,11 @@

Logger.info("RBF transaction created successfully: \(txid)", context: "CoreService.boostOnchainTransaction")

// Track the replacement transaction so we can mark it as boosted when it syncs
ActivityService.replacementTransactions.insert(txid)
Logger.debug("Added replacement transaction \(txid) to tracking list", context: "CoreService.boostOnchainTransaction")
// Track the replacement transaction with its full parent chain
// Include existing boostTxIds (from previous boosts) plus the current txId being replaced
let boostedParentsTxIds = onchainActivity.boostTxIds + [onchainActivity.txId]
ActivityService.replacementTransactions[txid] = boostedParentsTxIds
Logger.debug("Added replacement transaction \(txid) to tracking list with boosted parents txids: \(boostedParentsTxIds)", context: "CoreService.boostOnchainTransaction")

// Track the original transaction ID so we can ignore it during sync
ActivityService.replacedTransactions.insert(onchainActivity.txId)
Expand Down Expand Up @@ -374,6 +391,7 @@
confirmed: template.confirmed ?? false,
timestamp: timestamp,
isBoosted: template.isBoosted ?? false,
boostTxIds: template.boostTxIds,
isTransfer: template.isTransfer ?? false,
doesExist: true,
confirmTimestamp: template.confirmed == true ? timestamp + 3600 : nil,
Expand Down Expand Up @@ -418,6 +436,7 @@
let tags: [String]
let confirmed: Bool?
let isBoosted: Bool?
let boostTxIds: [String]
let isTransfer: Bool?

init(
Expand All @@ -429,7 +448,8 @@
tags: [String] = [],
confirmed: Bool? = nil,
isBoosted: Bool? = nil,
isTransfer: Bool? = nil
isTransfer: Bool? = nil,
boostTxIds: [String] = []
) {
self.type = type
self.txType = txType
Expand All @@ -440,6 +460,7 @@
self.confirmed = confirmed
self.isBoosted = isBoosted
self.isTransfer = isTransfer
self.boostTxIds = boostTxIds
}
}

Expand Down
19 changes: 17 additions & 2 deletions Bitkit/Views/Wallets/Activity/ActivityExplorerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,20 @@ struct ActivityExplorerView: View {
.padding(.bottom, 16)
}

Divider()
.padding(.bottom, 16)
if !onchain.boostTxIds.isEmpty {
Divider()
.padding(.bottom, 16)
ForEach(Array(onchain.boostTxIds.enumerated()), id: \.offset) { index, boostTxId in
let key = onchain.txType == .received
? "wallet__activity_boosted_cpfp"
: "wallet__activity_boosted_rbf"

InfoSection(
title: t(key, variables: ["num": String(index + 1)]),
content: boostTxId
)
}
}
} else if let lightning {
if let preimage = lightning.preimage {
InfoSection(
Expand Down Expand Up @@ -225,6 +237,7 @@ struct ActivityExplorer_Previews: PreviewProvider {
confirmed: true,
timestamp: UInt64(Date().timeIntervalSince1970),
isBoosted: false,
boostTxIds: [],
isTransfer: false,
doesExist: true,
confirmTimestamp: nil,
Expand All @@ -237,6 +250,8 @@ struct ActivityExplorer_Previews: PreviewProvider {
)
.previewDisplayName("Onchain Payment")
}
.environmentObject(AppViewModel())
.environmentObject(SettingsViewModel())
.environmentObject(CurrencyViewModel())
.preferredColorScheme(.dark)
}
Expand Down
1 change: 1 addition & 0 deletions Bitkit/Views/Wallets/Activity/ActivityItemView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ struct ActivityItemView_Previews: PreviewProvider {
confirmed: true,
timestamp: UInt64(Date().timeIntervalSince1970),
isBoosted: false,
boostTxIds: [],
isTransfer: false,
doesExist: true,
confirmTimestamp: nil,
Expand Down
1 change: 1 addition & 0 deletions Bitkit/Views/Wallets/Sheets/BoostSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ struct BoostSheet: View {
confirmed: false,
timestamp: UInt64(Date().timeIntervalSince1970),
isBoosted: false,
boostTxIds: [],
isTransfer: false,
doesExist: true,
confirmTimestamp: nil,
Expand Down
3 changes: 3 additions & 0 deletions BitkitTests/ActivityListTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ final class ActivityTests: XCTestCase {
confirmed: true,
timestamp: timestamp,
isBoosted: false,
boostTxIds: [],
isTransfer: false,
doesExist: true,
confirmTimestamp: nil,
Expand Down Expand Up @@ -235,6 +236,7 @@ final class ActivityTests: XCTestCase {
confirmed: true,
timestamp: timestamp,
isBoosted: false,
boostTxIds: [],
isTransfer: false,
doesExist: true,
confirmTimestamp: nil,
Expand Down Expand Up @@ -401,6 +403,7 @@ final class ActivityTests: XCTestCase {
confirmed: true,
timestamp: timestamp,
isBoosted: false,
boostTxIds: [],
isTransfer: false,
doesExist: true,
confirmTimestamp: nil,
Expand Down
Loading