1+ import BitkitCore
12import LDKNode
23import os. log
34import UserNotifications
@@ -17,7 +18,7 @@ class NotificationService: UNNotificationServiceExtension {
1718 var notificationPayload : [ String : Any ] ?
1819
1920 private lazy var notificationLogger : OSLog = {
20- let bundleID = Bundle . main. bundleIdentifier ?? " to.bitkit-regtest .notification "
21+ let bundleID = Bundle . main. bundleIdentifier ?? " to.bitkit.notification "
2122 return OSLog ( subsystem: bundleID, category: " NotificationService " )
2223 } ( )
2324
@@ -57,10 +58,6 @@ class NotificationService: UNNotificationServiceExtension {
5758 }
5859
5960 do {
60- // TODO: switch to electrum after syncing issues are fixed
61- // For notification extension, use default Electrum server URL for now
62- // try await LightningService.shared.setup(walletIndex: self.walletIndex, electrumServerUrl: Env.electrumServerUrl)
63-
6461 try await LightningService . shared. setup ( walletIndex: self . walletIndex)
6562 try await LightningService . shared. start { event in
6663 self . lightningEventTime = CFAbsoluteTimeGetCurrent ( )
@@ -90,16 +87,31 @@ class NotificationService: UNNotificationServiceExtension {
9087 return
9188 }
9289
93- os_log ( " 🔔 NotificationService: Open channel request for order %{public}@ " , log: notificationLogger, type: . error, orderId)
90+ guard let lspId = notificationPayload ? [ " lspId " ] as? String else {
91+ os_log ( " 🔔 NotificationService: Missing lspId " , log: notificationLogger, type: . error)
92+ return
93+ }
94+
95+ os_log (
96+ " 🔔 NotificationService: Open channel request for order %{public}@ with LSP %{public}@ " ,
97+ log: notificationLogger,
98+ type: . error,
99+ orderId,
100+ lspId
101+ )
94102
95103 do {
104+ // First, ensure we're connected to the LSP peer
105+ try await self . ensurePeerConnected ( lspId: lspId)
106+
107+ // Now open the channel
96108 let order = try await CoreService . shared. blocktank. open ( orderId: orderId)
97109 os_log ( " 🔔 NotificationService: Channel opened for order %{public}@ " , log: notificationLogger, type: . error, order. id)
98110 } catch {
99111 logError ( error, context: " Failed to open channel " )
100112
101- self . bestAttemptContent? . title = " Spending Balance Setup Failed "
102- self . bestAttemptContent? . body = error . localizedDescription
113+ self . bestAttemptContent? . title = " Spending Balance Setup Ready "
114+ self . bestAttemptContent? . body = " Tap to finalize transfer to spending "
103115
104116 self . deliver ( )
105117 }
@@ -164,11 +176,30 @@ class NotificationService: UNNotificationServiceExtension {
164176 os_log ( " 🔔 New LDK event: %{public}@ " , log: notificationLogger, type: . error, String ( describing: event) )
165177
166178 switch event {
167- case let . paymentReceived( _, _, amountMsat, _) :
179+ case let . paymentReceived( _, paymentHash, amountMsat, _) :
180+ // For incomingHtlc notifications, only show notification if payment hash matches
181+ if notificationType == . incomingHtlc {
182+ guard let expectedPaymentHash = notificationPayload ? [ " paymentHash " ] as? String else {
183+ os_log ( " 🔔 NotificationService: Missing paymentHash in notification payload " , log: notificationLogger, type: . error)
184+ return
185+ }
186+
187+ // Only process if this is the payment we're waiting for
188+ guard paymentHash == expectedPaymentHash else {
189+ os_log (
190+ " 🔔 NotificationService: Payment hash mismatch. Expected: %{public}@, Got: %{public}@ " ,
191+ log: notificationLogger,
192+ type: . error,
193+ expectedPaymentHash,
194+ paymentHash
195+ )
196+ return
197+ }
198+ }
199+
168200 let sats = amountMsat / 1000
169201 bestAttemptContent? . title = " Payment Received "
170202 bestAttemptContent? . body = " ₿ \( sats) "
171- ReceivedTxSheetDetails ( type: . lightning, sats: sats) . save ( ) // Save for UI to pick up
172203
173204 if notificationType == . incomingHtlc {
174205 deliver ( )
@@ -190,6 +221,41 @@ class NotificationService: UNNotificationServiceExtension {
190221 bestAttemptContent? . title = " Payment Received "
191222 bestAttemptContent? . body = " ₿ \( sats) "
192223 ReceivedTxSheetDetails ( type: . lightning, sats: sats) . save ( ) // Save for UI to pick up
224+
225+ // Add activity item for CJIT payment
226+ Task {
227+ do {
228+ let cjitOrder = await CoreService . shared. blocktank. getCjit ( channel: channel)
229+ if let cjitOrder {
230+ let now = UInt64 ( Date ( ) . timeIntervalSince1970)
231+
232+ let ln = LightningActivity (
233+ id: channel. fundingTxo? . txid. description ?? " " ,
234+ txType: . received,
235+ status: . succeeded,
236+ value: sats,
237+ fee: 0 ,
238+ invoice: cjitOrder. invoice. request,
239+ message: " " ,
240+ timestamp: now,
241+ preimage: nil ,
242+ createdAt: now,
243+ updatedAt: nil ,
244+ seenAt: nil
245+ )
246+
247+ try await CoreService . shared. activity. insert ( . lightning( ln) )
248+ os_log ( " 🔔 NotificationService: Added CJIT activity item " , log: notificationLogger, type: . error)
249+ }
250+ } catch {
251+ os_log (
252+ " 🔔 NotificationService: Failed to add CJIT activity: %{public}@ " ,
253+ log: notificationLogger,
254+ type: . error,
255+ error. localizedDescription
256+ )
257+ }
258+ }
193259 }
194260
195261 deliver ( )
@@ -224,7 +290,7 @@ class NotificationService: UNNotificationServiceExtension {
224290
225291 // MARK: New Onchain Transaction Events
226292
227- case let . onchainTransactionReceived( txid , details) :
293+ case let . onchainTransactionReceived( _ , details) :
228294 // Show notification for incoming onchain transactions
229295 if details. amountSats > 0 {
230296 let sats = UInt64 ( abs ( Int64 ( details. amountSats) ) )
@@ -233,14 +299,15 @@ class NotificationService: UNNotificationServiceExtension {
233299 ReceivedTxSheetDetails ( type: . onchain, sats: sats) . save ( ) // Save for UI to pick up
234300 deliver ( )
235301 }
236- case let . onchainTransactionConfirmed( txid, blockHash, blockHeight, confirmationTime, details) :
237- // Transaction confirmed - could show notification if it was previously unconfirmed
238- if details. amountSats > 0 {
239- let sats = UInt64 ( abs ( Int64 ( details. amountSats) ) )
240- bestAttemptContent? . title = " Payment Confirmed "
241- bestAttemptContent? . body = " ₿ \( sats) confirmed at block \( blockHeight) "
242- deliver ( )
243- }
302+ case . onchainTransactionConfirmed:
303+ // // Transaction confirmed - could show notification if it was previously unconfirmed
304+ // if details.amountSats > 0 {
305+ // let sats = UInt64(abs(Int64(details.amountSats)))
306+ // bestAttemptContent?.title = "Payment Confirmed"
307+ // bestAttemptContent?.body = "₿ \(sats) confirmed at block \(blockHeight)"
308+ // deliver()
309+ // }
310+ break
244311 case . onchainTransactionReplaced, . onchainTransactionReorged, . onchainTransactionEvicted:
245312 // These events are less critical for notifications, but could be logged
246313 os_log ( " 🔔 Onchain transaction state changed: %{public}@ " , log: notificationLogger, type: . error, String ( describing: event) )
@@ -331,6 +398,54 @@ class NotificationService: UNNotificationServiceExtension {
331398 }
332399 }
333400
401+ /// Ensures the peer is connected before attempting to open a channel
402+ /// - Parameter lspId: The LSP node ID to connect to
403+ private func ensurePeerConnected( lspId: String ) async throws {
404+ // Find the peer in trusted peers list
405+ guard let peer = Env . trustedLnPeers. first ( where: { $0. nodeId == lspId } ) else {
406+ os_log ( " 🔔 NotificationService: LSP %{public}@ not found in trusted peers " , log: notificationLogger, type: . error, lspId)
407+ throw AppError ( message: " LSP not found in trusted peers " , debugMessage: " LSP ID: \( lspId) " )
408+ }
409+
410+ // Check if already connected
411+ if let peers = LightningService . shared. peers, peers. contains ( where: { $0. nodeId == lspId } ) {
412+ os_log ( " 🔔 NotificationService: Already connected to LSP %{public}@ " , log: notificationLogger, type: . error, lspId)
413+ return
414+ }
415+
416+ // Connect to the peer
417+ os_log ( " 🔔 NotificationService: Connecting to LSP %{public}@ at %{public}@ " , log: notificationLogger, type: . error, lspId, peer. address)
418+ try await LightningService . shared. connectPeer ( peer: peer)
419+
420+ // Wait for connection to be established (with timeout)
421+ let maxWaitTime : TimeInterval = 10.0
422+ let pollInterval : TimeInterval = 0.5
423+ let startTime = Date ( )
424+
425+ while Date ( ) . timeIntervalSince ( startTime) < maxWaitTime {
426+ if let peers = LightningService . shared. peers, peers. contains ( where: { $0. nodeId == lspId } ) {
427+ os_log ( " 🔔 NotificationService: Successfully connected to LSP %{public}@ " , log: notificationLogger, type: . error, lspId)
428+ return
429+ }
430+
431+ try await Task . sleep ( nanoseconds: UInt64 ( pollInterval * 1_000_000_000 ) )
432+ }
433+
434+ // Timeout - check one more time
435+ if let peers = LightningService . shared. peers, peers. contains ( where: { $0. nodeId == lspId } ) {
436+ os_log (
437+ " 🔔 NotificationService: Successfully connected to LSP %{public}@ (after timeout check) " ,
438+ log: notificationLogger,
439+ type: . error,
440+ lspId
441+ )
442+ return
443+ }
444+
445+ os_log ( " 🔔 NotificationService: Timeout waiting for LSP connection %{public}@ " , log: notificationLogger, type: . error, lspId)
446+ throw AppError ( message: " Failed to connect to LSP " , debugMessage: " Timeout after \( maxWaitTime) s waiting for peer \( lspId) " )
447+ }
448+
334449 /// Logs comprehensive error details
335450 private func logError( _ error: Error , context: String ) {
336451 os_log (
0 commit comments