diff --git a/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index faf57da8..e6b82941 100644 --- a/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -7,7 +7,7 @@ "location" : "https://github.com/synonymdev/bitkit-core", "state" : { "branch" : "master", - "revision" : "0e66950a564871a011dec99d5333e8ecbfdb543b" + "revision" : "539c48daa059bb9bb46bb00f5eb8e227021020d0" } }, { @@ -25,7 +25,7 @@ "location" : "https://github.com/synonymdev/ldk-node", "state" : { "branch" : "main", - "revision" : "2a80f373f469a74ece77096dc30d2cf8093a2a48" + "revision" : "47bfd947e5dee1be7117179c2693d6c8bd1020bb" } }, { diff --git a/Bitkit/Services/CoreService.swift b/Bitkit/Services/CoreService.swift index 6cf0f424..4c910d51 100644 --- a/Bitkit/Services/CoreService.swift +++ b/Bitkit/Services/CoreService.swift @@ -61,6 +61,99 @@ class ActivityService { } } + private func mapToCoreTransactionDetails(txid: String, _ details: LDKNode.TransactionDetails) -> BitkitCore.TransactionDetails { + let inputs = details.inputs.map { input in + BitkitCore.TxInput( + txid: input.txid, + vout: input.vout, + scriptsig: input.scriptsig, + witness: input.witness, + sequence: input.sequence + ) + } + + let outputs = details.outputs.map { output in + BitkitCore.TxOutput( + scriptpubkey: output.scriptpubkey, + scriptpubkeyType: output.scriptpubkeyType, + scriptpubkeyAddress: output.scriptpubkeyAddress, + value: output.value, + n: output.n + ) + } + + return BitkitCore.TransactionDetails( + txId: txid, + amountSats: details.amountSats, + inputs: inputs, + outputs: outputs + ) + } + + private func fetchTransactionDetails(txid: String) async -> BitkitCore.TransactionDetails? { + do { + return try await getTransactionDetails(txid: txid) + } catch { + Logger.warn("Failed to fetch stored transaction details for \(txid): \(error)", context: "ActivityService") + return nil + } + } + + func getTransactionDetails(txid: String) async throws -> BitkitCore.TransactionDetails? { + try await ServiceQueue.background(.core) { + try BitkitCore.getTransactionDetails(txId: txid) + } + } + + // MARK: - Seen Tracking + + func isActivitySeen(id: String) async -> Bool { + do { + if let activity = try await getActivityById(activityId: id) { + switch activity { + case let .onchain(onchain): + return onchain.seenAt != nil + case let .lightning(lightning): + return lightning.seenAt != nil + } + } + } catch { + Logger.error("Failed to check seen status for activity \(id): \(error)", context: "ActivityService") + } + return false + } + + func isOnchainActivitySeen(txid: String) async -> Bool { + if let activity = try? await getOnchainActivityByTxId(txid: txid) { + return activity.seenAt != nil + } + return false + } + + func markActivityAsSeen(id: String, seenAt: UInt64? = nil) async { + let timestamp = seenAt ?? UInt64(Date().timeIntervalSince1970) + + do { + try await ServiceQueue.background(.core) { + try BitkitCore.markActivityAsSeen(activityId: id, seenAt: timestamp) + self.activitiesChangedSubject.send() + } + } catch { + Logger.error("Failed to mark activity \(id) as seen: \(error)", context: "ActivityService") + } + } + + func markOnchainActivityAsSeen(txid: String, seenAt: UInt64? = nil) async { + do { + guard let activity = try await getOnchainActivityByTxId(txid: txid) else { + return + } + await markActivityAsSeen(id: activity.id, seenAt: seenAt) + } catch { + Logger.error("Failed to mark onchain activity for \(txid) as seen: \(error)", context: "ActivityService") + } + } + // MARK: - Transaction Status Checks func wasTransactionReplaced(txid: String) async -> Bool { @@ -84,17 +177,15 @@ class ActivityService { return false } - do { - // Check if this transaction's activity has boostTxIds (meaning it replaced other transactions) - // If any of the replaced transactions have the same value, don't show the sheet - guard let onchain = try? await getOnchainActivityByTxId(txid: txid), - !onchain.boostTxIds.isEmpty - else { - return true - } + let onchainActivity = try? await getOnchainActivityByTxId(txid: txid) + + if let onchainActivity, onchainActivity.seenAt != nil { + return false + } - // This transaction replaced others - check if any have the same value - for replacedTxid in onchain.boostTxIds { + // If this is a replacement transaction with same value as original, skip the sheet + if let boostTxIds = onchainActivity?.boostTxIds, !boostTxIds.isEmpty { + for replacedTxid in boostTxIds { if let replaced = try? await getOnchainActivityByTxId(txid: replacedTxid), replaced.value == value { @@ -105,8 +196,6 @@ class ActivityService { return false } } - } catch { - Logger.error("Failed to check existing activities for replacement: \(error)", context: "CoreService.shouldShowReceivedSheet") } return true @@ -213,7 +302,7 @@ class ActivityService { private func processOnchainPayment( _ payment: PaymentDetails, - transactionDetails: TransactionDetails? = nil + transactionDetails: BitkitCore.TransactionDetails? = nil ) async throws { guard case let .onchain(txid, _) = payment.kind else { return } @@ -258,6 +347,7 @@ class ActivityService { let feeRate = existingOnchain?.feeRate ?? 1 let preservedAddress = existingOnchain?.address ?? "Loading..." let doesExist = existingOnchain?.doesExist ?? true + let seenAt = existingOnchain?.seenAt // Check if this transaction is a channel transfer if channelId == nil || !isTransfer { @@ -309,7 +399,8 @@ class ActivityService { channelId: channelId, transferTxId: transferTxId, createdAt: UInt64(payment.creationTime.timeIntervalSince1970), - updatedAt: paymentTimestamp + updatedAt: paymentTimestamp, + seenAt: seenAt ) if existingActivity != nil { @@ -321,7 +412,7 @@ class ActivityService { // MARK: - Onchain Event Handlers - private func processOnchainTransaction(txid: String, details: TransactionDetails, context: String) async throws { + private func processOnchainTransaction(txid: String, details: BitkitCore.TransactionDetails, context: String) async throws { guard let payments = LightningService.shared.payments else { Logger.warn("No payments available for transaction \(txid)", context: context) return @@ -340,15 +431,21 @@ class ActivityService { try await processOnchainPayment(payment, transactionDetails: details) } - func handleOnchainTransactionReceived(txid: String, details: TransactionDetails) async throws { + func handleOnchainTransactionReceived(txid: String, details: LDKNode.TransactionDetails) async throws { + let coreDetails = mapToCoreTransactionDetails(txid: txid, details) + try await ServiceQueue.background(.core) { - try await self.processOnchainTransaction(txid: txid, details: details, context: "CoreService.handleOnchainTransactionReceived") + try BitkitCore.upsertTransactionDetails(detailsList: [coreDetails]) + try await self.processOnchainTransaction(txid: txid, details: coreDetails, context: "CoreService.handleOnchainTransactionReceived") } } - func handleOnchainTransactionConfirmed(txid: String, details: TransactionDetails) async throws { + func handleOnchainTransactionConfirmed(txid: String, details: LDKNode.TransactionDetails) async throws { + let coreDetails = mapToCoreTransactionDetails(txid: txid, details) + try await ServiceQueue.background(.core) { - try await self.processOnchainTransaction(txid: txid, details: details, context: "CoreService.handleOnchainTransactionConfirmed") + try BitkitCore.upsertTransactionDetails(detailsList: [coreDetails]) + try await self.processOnchainTransaction(txid: txid, details: coreDetails, context: "CoreService.handleOnchainTransactionConfirmed") } } @@ -497,13 +594,11 @@ class ActivityService { let paymentTimestamp = UInt64(payment.latestUpdateTimestamp) let existingActivity = try getActivityById(activityId: payment.id) + let existingLightning: LightningActivity? = if let existingActivity, case let .lightning(ln) = existingActivity { ln } else { nil } // Skip if existing activity has newer timestamp to avoid overwriting local data - if let existingActivity, case let .lightning(existing) = existingActivity { - let existingUpdatedAt = existing.updatedAt ?? 0 - if existingUpdatedAt > paymentTimestamp { - return - } + if let existingUpdatedAt = existingLightning?.updatedAt, existingUpdatedAt > paymentTimestamp { + return } let state: BitkitCore.PaymentState = switch payment.status { @@ -523,7 +618,8 @@ class ActivityService { timestamp: paymentTimestamp, preimage: preimage, createdAt: paymentTimestamp, - updatedAt: paymentTimestamp + updatedAt: paymentTimestamp, + seenAt: existingLightning?.seenAt ) if existingActivity != nil { @@ -598,7 +694,8 @@ class ActivityService { /// Marks replacement transactions (with originalTxId in boostTxIds) as doesExist = false when original confirms /// Finds the channel ID associated with a transaction based on its direction - private func findChannelForTransaction(txid: String, direction: PaymentDirection, transactionDetails: TransactionDetails? = nil) async -> String? + private func findChannelForTransaction(txid: String, direction: PaymentDirection, + transactionDetails: BitkitCore.TransactionDetails? = nil) async -> String? { switch direction { case .inbound: @@ -611,13 +708,13 @@ class ActivityService { } /// Check if a transaction spends a closed channel's funding UTXO - private func findClosedChannelForTransaction(txid: String, transactionDetails: TransactionDetails? = nil) async -> String? { + private func findClosedChannelForTransaction(txid: String, transactionDetails: BitkitCore.TransactionDetails? = nil) async -> String? { do { let closedChannels = try await getAllClosedChannels(sortDirection: .desc) guard !closedChannels.isEmpty else { return nil } - // Use provided transaction details if available, otherwise try node - guard let details = transactionDetails ?? LightningService.shared.getTransactionDetails(txid: txid) else { + let details = if let provided = transactionDetails { provided } else { await fetchTransactionDetails(txid: txid) } + guard let details else { Logger.warn("Transaction details not available for \(txid)", context: "CoreService.findClosedChannelForTransaction") return nil } @@ -686,7 +783,7 @@ class ActivityService { } /// Check pre-activity metadata for addresses in the transaction - private func findAddressInPreActivityMetadata(details: TransactionDetails, value: UInt64) async -> String? { + private func findAddressInPreActivityMetadata(details: BitkitCore.TransactionDetails, value: UInt64) async -> String? { for output in details.outputs { guard let address = output.scriptpubkeyAddress else { continue } if let metadata = try? await getPreActivityMetadata(searchKey: address, searchByAddress: true), @@ -700,9 +797,11 @@ class ActivityService { } /// Find the receiving address for an onchain transaction - private func findReceivingAddress(for txid: String, value: UInt64, transactionDetails: TransactionDetails? = nil) async throws -> String? { - // Use provided transaction details if available, otherwise try node - guard let details = transactionDetails ?? LightningService.shared.getTransactionDetails(txid: txid) else { + private func findReceivingAddress(for txid: String, value: UInt64, + transactionDetails: BitkitCore.TransactionDetails? = nil) async throws -> String? + { + let details = if let provided = transactionDetails { provided } else { await fetchTransactionDetails(txid: txid) } + guard let details else { Logger.warn("Transaction details not available for \(txid)", context: "CoreService.findReceivingAddress") return nil } @@ -1048,7 +1147,8 @@ class ActivityService { timestamp: timestamp, preimage: template.status == .succeeded ? "preimage\(activityId)" : nil, createdAt: timestamp, - updatedAt: timestamp + updatedAt: timestamp, + seenAt: nil ) ) case .onchain: @@ -1071,7 +1171,8 @@ class ActivityService { channelId: nil, transferTxId: nil, createdAt: timestamp, - updatedAt: timestamp + updatedAt: timestamp, + seenAt: nil ) ) } diff --git a/Bitkit/Services/LightningService.swift b/Bitkit/Services/LightningService.swift index 711e5532..ee78eb7d 100644 --- a/Bitkit/Services/LightningService.swift +++ b/Bitkit/Services/LightningService.swift @@ -78,13 +78,12 @@ class LightningService { Logger.debug("Building ldk-node with vssUrl: '\(vssUrl)'") Logger.debug("Building ldk-node with lnurlAuthServerUrl: '\(lnurlAuthServerUrl)'") - // Create NodeEntropy from mnemonic - let nodeEntropy = NodeEntropy.fromBip39Mnemonic(mnemonic: mnemonic, passphrase: passphrase) + // Set entropy from mnemonic on builder + builder.setEntropyBip39Mnemonic(mnemonic: mnemonic, passphrase: passphrase) try await ServiceQueue.background(.ldk) { if !lnurlAuthServerUrl.isEmpty { self.node = try builder.buildWithVssStore( - nodeEntropy: nodeEntropy, vssUrl: vssUrl, storeId: storeId, lnurlAuthServerUrl: lnurlAuthServerUrl, @@ -92,7 +91,6 @@ class LightningService { ) } else { self.node = try builder.buildWithVssStoreAndFixedHeaders( - nodeEntropy: nodeEntropy, vssUrl: vssUrl, storeId: storeId, fixedHeaders: [:] @@ -623,12 +621,6 @@ extension LightningService { var channels: [ChannelDetails]? { node?.listChannels() } var payments: [PaymentDetails]? { node?.listPayments() } - /// Get transaction details from the node for a given transaction ID - /// Returns nil if the transaction is not found in the wallet - func getTransactionDetails(txid: String) -> TransactionDetails? { - return node?.getTransactionDetails(txid: txid) - } - /// Get balance for a specific address in satoshis /// - Parameter address: The Bitcoin address to check /// - Returns: The current balance in satoshis diff --git a/Bitkit/Utilities/Errors.swift b/Bitkit/Utilities/Errors.swift index 698e25b6..3838cc6a 100644 --- a/Bitkit/Utilities/Errors.swift +++ b/Bitkit/Utilities/Errors.swift @@ -122,18 +122,30 @@ struct AppError: LocalizedError { private init(ldkBuildError: BuildError) { switch ldkBuildError as BuildError { + case let .InvalidSeedBytes(message: ldkMessage): + message = "Invalid seed bytes" + debugMessage = ldkMessage + case let .InvalidSeedFile(message: ldkMessage): + message = "Invalid seed file" + debugMessage = ldkMessage case let .InvalidSystemTime(message: ldkMessage): message = "Invalid system time" debugMessage = ldkMessage case let .InvalidChannelMonitor(message: ldkMessage): message = "Invalid channel monitor" debugMessage = ldkMessage - case let .InvalidListeningAddresses(message: ldkMessage): - message = "Invalid system time" - debugMessage = ldkMessage case let .InvalidListeningAddresses(message: ldkMessage): message = "Invalid listening addresses" debugMessage = ldkMessage + case let .InvalidAnnouncementAddresses(message: ldkMessage): + message = "Invalid announcement addresses" + debugMessage = ldkMessage + case let .InvalidNodeAlias(message: ldkMessage): + message = "Invalid node alias" + debugMessage = ldkMessage + case let .RuntimeSetupFailed(message: ldkMessage): + message = "Runtime setup failed" + debugMessage = ldkMessage case let .ReadFailed(message: ldkMessage): message = "Read failed" debugMessage = ldkMessage @@ -152,17 +164,8 @@ struct AppError: LocalizedError { case let .LoggerSetupFailed(message: ldkMessage): message = "Logger setup failed" debugMessage = ldkMessage - case let .InvalidNodeAlias(message: ldkMessage): - message = ldkMessage - debugMessage = nil - case let .InvalidAnnouncementAddresses(message: ldkMessage): - message = ldkMessage - debugMessage = nil case let .NetworkMismatch(message: ldkMessage): - message = ldkMessage - debugMessage = nil - case let .RuntimeSetupFailed(message: ldkMessage): - message = "Runtime setup failed" + message = "Network mismatch" debugMessage = ldkMessage case let .AsyncPaymentsConfigMismatch(message: ldkMessage): message = "Async payments config mismatch" diff --git a/Bitkit/ViewModels/AppViewModel.swift b/Bitkit/ViewModels/AppViewModel.swift index 8a5055b3..d702ce18 100644 --- a/Bitkit/ViewModels/AppViewModel.swift +++ b/Bitkit/ViewModels/AppViewModel.swift @@ -336,7 +336,18 @@ extension AppViewModel { func handleLdkNodeEvent(_ event: Event) { switch event { case let .paymentReceived(paymentId, paymentHash, amountMsat, customRecords): - sheetViewModel.showSheet(.receivedTx, data: ReceivedTxSheetDetails(type: .lightning, sats: amountMsat / 1000)) + Task { + if let paymentId { + if await CoreService.shared.activity.isActivitySeen(id: paymentId) { + return + } + await CoreService.shared.activity.markActivityAsSeen(id: paymentId) + } + + await MainActor.run { + sheetViewModel.showSheet(.receivedTx, data: ReceivedTxSheetDetails(type: .lightning, sats: amountMsat / 1000)) + } + } case .channelPending(channelId: _, userChannelId: _, formerTemporaryChannelId: _, counterpartyNodeId: _, fundingTxo: _): // Only relevant for channels to external nodes break @@ -359,7 +370,8 @@ extension AppViewModel { timestamp: now, preimage: nil, createdAt: now, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) try await CoreService.shared.activity.insert(.lightning(ln)) @@ -407,13 +419,17 @@ extension AppViewModel { Task { // Show sheet for new transactions or replacements with value changes try? await Task.sleep(nanoseconds: 500_000_000) // 500ms delay + + if await CoreService.shared.activity.isOnchainActivitySeen(txid: txid) { + return + } + let shouldShow = await CoreService.shared.activity.shouldShowReceivedSheet(txid: txid, value: sats) + guard shouldShow else { return } - await MainActor.run { - if !shouldShow { - return - } + await CoreService.shared.activity.markOnchainActivityAsSeen(txid: txid) + await MainActor.run { sheetViewModel.showSheet(.receivedTx, data: ReceivedTxSheetDetails(type: .onchain, sats: sats)) } } diff --git a/Bitkit/Views/Gift/GiftLoading.swift b/Bitkit/Views/Gift/GiftLoading.swift index 004c95d2..94bd2fd9 100644 --- a/Bitkit/Views/Gift/GiftLoading.swift +++ b/Bitkit/Views/Gift/GiftLoading.swift @@ -111,12 +111,16 @@ struct GiftLoading: View { timestamp: nowTimestamp, preimage: nil, createdAt: nowTimestamp, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) // Add to activity list try await CoreService.shared.activity.insert(.lightning(lightningActivity)) + // Mark the activity as seen before showing the sheet + await CoreService.shared.activity.markActivityAsSeen(id: lightningActivity.id) + // Trigger haptic feedback Haptics.notify(.success) diff --git a/Bitkit/Views/Wallets/Activity/ActivityExplorerView.swift b/Bitkit/Views/Wallets/Activity/ActivityExplorerView.swift index 1844adac..ac22ed00 100644 --- a/Bitkit/Views/Wallets/Activity/ActivityExplorerView.swift +++ b/Bitkit/Views/Wallets/Activity/ActivityExplorerView.swift @@ -9,7 +9,7 @@ struct ActivityExplorerView: View { @EnvironmentObject var currency: CurrencyViewModel @State private var item: Activity - @State private var txDetails: TransactionDetails? + @State private var txDetails: BitkitCore.TransactionDetails? @State private var boostTxDoesExist: [String: Bool] = [:] // Maps boostTxId -> doesExist init(item: Activity) { @@ -52,13 +52,13 @@ struct ActivityExplorerView: View { private func loadTransactionDetails() async { guard let onchain else { return } - // Try to get transaction details from node - if let nodeDetails = LightningService.shared.getTransactionDetails(txid: onchain.txId) { + do { + let details = try await CoreService.shared.activity.getTransactionDetails(txid: onchain.txId) await MainActor.run { - txDetails = nodeDetails + txDetails = details } - } else { - Logger.warn("Transaction details not available from node for \(onchain.txId)") + } catch { + Logger.error("Failed to load transaction details for \(onchain.txId): \(error)", context: "ActivityExplorerView") } } @@ -277,7 +277,8 @@ struct ActivityExplorer_Previews: PreviewProvider { timestamp: UInt64(Date().timeIntervalSince1970), preimage: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ) ) @@ -303,7 +304,8 @@ struct ActivityExplorer_Previews: PreviewProvider { channelId: nil, transferTxId: nil, createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ) ) diff --git a/Bitkit/Views/Wallets/Activity/ActivityItemView.swift b/Bitkit/Views/Wallets/Activity/ActivityItemView.swift index 4b6234ba..de231203 100644 --- a/Bitkit/Views/Wallets/Activity/ActivityItemView.swift +++ b/Bitkit/Views/Wallets/Activity/ActivityItemView.swift @@ -571,7 +571,8 @@ struct ActivityItemView_Previews: PreviewProvider { timestamp: UInt64(Date().timeIntervalSince1970), preimage: nil, createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ) ) @@ -599,7 +600,8 @@ struct ActivityItemView_Previews: PreviewProvider { channelId: nil, transferTxId: nil, createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ) ) diff --git a/Bitkit/Views/Wallets/Sheets/BoostSheet.swift b/Bitkit/Views/Wallets/Sheets/BoostSheet.swift index ba7d308b..c7496755 100644 --- a/Bitkit/Views/Wallets/Sheets/BoostSheet.swift +++ b/Bitkit/Views/Wallets/Sheets/BoostSheet.swift @@ -442,7 +442,8 @@ struct BoostSheet: View { channelId: nil, transferTxId: nil, createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ) ) diff --git a/BitkitTests/ActivityListTest.swift b/BitkitTests/ActivityListTest.swift index 2b30ae0a..541939ef 100644 --- a/BitkitTests/ActivityListTest.swift +++ b/BitkitTests/ActivityListTest.swift @@ -44,7 +44,8 @@ final class ActivityTests: XCTestCase { timestamp: timestamp, preimage: nil, createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ) @@ -91,7 +92,8 @@ final class ActivityTests: XCTestCase { channelId: nil, transferTxId: nil, createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ) @@ -128,7 +130,8 @@ final class ActivityTests: XCTestCase { timestamp: timestamp, preimage: nil, createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ) @@ -165,7 +168,8 @@ final class ActivityTests: XCTestCase { timestamp: timestamp, preimage: nil, createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ), Activity.lightning( @@ -180,7 +184,8 @@ final class ActivityTests: XCTestCase { timestamp: timestamp, preimage: nil, createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ), ] @@ -221,7 +226,8 @@ final class ActivityTests: XCTestCase { timestamp: timestamp, preimage: nil, createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ), Activity.onchain( @@ -243,7 +249,8 @@ final class ActivityTests: XCTestCase { channelId: nil, transferTxId: nil, createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ), ] @@ -297,7 +304,8 @@ final class ActivityTests: XCTestCase { timestamp: timestamp, preimage: nil, createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ) @@ -316,7 +324,8 @@ final class ActivityTests: XCTestCase { timestamp: timestamp, preimage: "preimage123", createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ) @@ -352,7 +361,8 @@ final class ActivityTests: XCTestCase { timestamp: timestamp, preimage: nil, createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ) @@ -388,7 +398,8 @@ final class ActivityTests: XCTestCase { timestamp: timestamp, preimage: nil, createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ), Activity.onchain( @@ -410,7 +421,8 @@ final class ActivityTests: XCTestCase { channelId: nil, transferTxId: nil, createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ), Activity.lightning( @@ -425,7 +437,8 @@ final class ActivityTests: XCTestCase { timestamp: timestamp, preimage: nil, createdAt: nil, - updatedAt: nil + updatedAt: nil, + seenAt: nil ) ), ]