@@ -25,6 +25,42 @@ class ActivityService {
2525 /// Maximum address index to search when current address exists
2626 private static let maxAddressSearchIndex : UInt32 = 100_000
2727
28+ // MARK: - BoostTxIds Cache
29+
30+ // Cached set of transaction IDs that appear in boostTxIds (for filtering replaced transactions)
31+ private var cachedTxIdsInBoostTxIds : Set < String > = [ ]
32+
33+ /// Get the set of transaction IDs that appear in boostTxIds (cached for performance)
34+ func getTxIdsInBoostTxIds( ) async -> Set < String > {
35+ if cachedTxIdsInBoostTxIds. isEmpty {
36+ await refreshBoostTxIdsCache ( )
37+ }
38+ return cachedTxIdsInBoostTxIds
39+ }
40+
41+ private func updateBoostTxIdsCache( for activity: Activity ) {
42+ if case let . onchain( onchain) = activity {
43+ cachedTxIdsInBoostTxIds. formUnion ( onchain. boostTxIds)
44+ }
45+ }
46+
47+ private func refreshBoostTxIdsCache( ) async {
48+ do {
49+ let allOnchainActivities = try await get ( filter: . onchain)
50+ var txIds : Set < String > = [ ]
51+ for activity in allOnchainActivities {
52+ if case let . onchain( onchain) = activity {
53+ txIds. formUnion ( onchain. boostTxIds)
54+ }
55+ }
56+ await MainActor . run {
57+ self . cachedTxIdsInBoostTxIds = txIds
58+ }
59+ } catch {
60+ Logger . error ( " Failed to refresh boostTxIds cache: \( error) " , context: " ActivityService " )
61+ }
62+ }
63+
2864 // MARK: - Transaction Status Checks
2965
3066 func wasTransactionReplaced( txid: String ) async -> Bool {
@@ -125,20 +161,24 @@ class ActivityService {
125161 _ = try deleteActivityById ( activityId: id)
126162 }
127163
164+ // Clear cache since all activities are deleted
165+ self . cachedTxIdsInBoostTxIds. removeAll ( )
128166 self . activitiesChangedSubject. send ( )
129167 }
130168 }
131169
132170 func insert( _ activity: Activity ) async throws {
133171 try await ServiceQueue . background ( . core) {
134172 try insertActivity ( activity: activity)
173+ self . updateBoostTxIdsCache ( for: activity)
135174 self . activitiesChangedSubject. send ( )
136175 }
137176 }
138177
139178 func upsertList( _ activities: [ Activity ] ) async throws {
140179 try await ServiceQueue . background ( . core) {
141180 try upsertActivities ( activities: activities)
181+ await self . refreshBoostTxIdsCache ( )
142182 }
143183 }
144184
@@ -308,10 +348,7 @@ class ActivityService {
308348 // Find the activity for the replaced transaction
309349 let replacedActivity = try await self . getOnchainActivityByTxId ( txid: txid)
310350
311- let replacedTags : [ String ]
312351 if var existing = replacedActivity {
313- replacedTags = await ( try ? self . tags ( forActivity: existing. id) ) ?? [ ]
314-
315352 Logger . info (
316353 " Transaction \( txid) replaced by \( conflicts. count) conflict(s): \( conflicts. joined ( separator: " , " ) ) " ,
317354 context: " CoreService.handleOnchainTransactionReplaced "
@@ -323,9 +360,8 @@ class ActivityService {
323360 try await self . update ( id: existing. id, activity: . onchain( existing) )
324361 Logger . info ( " Marked transaction \( txid) as replaced " , context: " CoreService.handleOnchainTransactionReplaced " )
325362 } else {
326- replacedTags = [ ]
327363 Logger . info (
328- " Activity not found for replaced transaction \( txid) - was deleted by initiated RBF, tags in pre-activity metadata " ,
364+ " Activity not found for replaced transaction \( txid) - will be created when transaction is processed " ,
329365 context: " CoreService.handleOnchainTransactionReplaced "
330366 )
331367 }
@@ -369,13 +405,16 @@ class ActivityService {
369405 activity. updatedAt = UInt64 ( Date ( ) . timeIntervalSince1970)
370406 try await self . update ( id: activity. id, activity: . onchain( activity) )
371407
372- // Apply tags from the replaced transaction
373- if !replacedTags . isEmpty {
408+ // Move tags from the replaced transaction
409+ if let replacedActivity {
374410 do {
375- try await self . appendTags ( toActivity: activity. id, replacedTags)
411+ let replacedTags = try await self . tags ( forActivity: replacedActivity. id)
412+ if !replacedTags. isEmpty {
413+ try await self . appendTags ( toActivity: activity. id, replacedTags)
414+ }
376415 } catch {
377416 Logger . error (
378- " Failed to apply tags from replaced transaction \( txid) to replacement transaction \( conflictTxid) : \( error) " ,
417+ " Failed to copy tags from replaced transaction \( txid) to replacement transaction \( conflictTxid) : \( error) " ,
379418 context: " CoreService.handleOnchainTransactionReplaced "
380419 )
381420 }
@@ -792,19 +831,27 @@ class ActivityService {
792831 func update( id: String, activity: Activity) async throws {
793832 try await ServiceQueue . background ( . core) {
794833 try updateActivity ( activityId: id, activity: activity)
834+ self . updateBoostTxIdsCache ( for: activity)
795835 self . activitiesChangedSubject. send ( )
796836 }
797837 }
798838
799839 func upsert( _ activity: Activity) async throws {
800840 try await ServiceQueue . background ( . core) {
801841 try upsertActivity ( activity: activity)
842+ self . updateBoostTxIdsCache ( for: activity)
802843 self . activitiesChangedSubject. send ( )
803844 }
804845 }
805846
806847 func delete( id: String) async throws -> Bool {
807848 try await ServiceQueue . background ( . core) {
849+ // Rebuild cache if deleting an onchain activity with boostTxIds
850+ let activity = try ? getActivityById ( activityId: id)
851+ if let activity, case let . onchain( onchain) = activity, !onchain. boostTxIds. isEmpty {
852+ await self . refreshBoostTxIdsCache ( )
853+ }
854+
808855 let result = try deleteActivityById ( activityId: id)
809856 self . activitiesChangedSubject. send ( )
810857 return result
@@ -951,35 +998,12 @@ class ActivityService {
951998
952999 Logger . info ( " RBF transaction created successfully: \( txid) " , context: " CoreService.boostOnchainTransaction " )
9531000
954- // Get tags from the old activity before deleting it
955- let oldTags = await ( try ? self . tags ( forActivity: activityId) ) ?? [ ]
956-
957- // Create pre-activity metadata for the replacement transaction with tags from the old activity
958- if !oldTags. isEmpty {
959- let currentTime = UInt64 ( Date ( ) . timeIntervalSince1970)
960- let preActivityMetadata = BitkitCore . PreActivityMetadata (
961- paymentId: txid,
962- tags: oldTags,
963- paymentHash: nil ,
964- txId: txid,
965- address: onchainActivity. address,
966- isReceive: false ,
967- feeRate: UInt64 ( feeRate) ,
968- isTransfer: onchainActivity. isTransfer,
969- channelId: onchainActivity. channelId,
970- createdAt: currentTime
971- )
972- try ? await self . addPreActivityMetadata ( preActivityMetadata)
973- Logger . info (
974- " Created pre-activity metadata with \( oldTags. count) tag(s) for RBF replacement transaction \( txid) " ,
975- context: " CoreService.boostOnchainTransaction "
976- )
977- }
978-
979- // For RBF we initiated, delete the old activity
980- _ = try await self . delete ( id: activityId)
1001+ // For RBF, mark the original activity as doesExist = false instead of deleting it
1002+ // This allows it to be displayed with the "removed" status
1003+ onchainActivity. doesExist = false
1004+ try await self . update ( id: activityId, activity: . onchain( onchainActivity) )
9811005 Logger . info (
982- " Successfully deleted activity \( activityId) (replaced by RBF transaction \( txid ) ) " ,
1006+ " Successfully marked activity \( activityId) as doesExist = false (replaced by RBF) " ,
9831007 context: " CoreService.boostOnchainTransaction "
9841008 )
9851009 }
0 commit comments