Skip to content
Open
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.

72 changes: 71 additions & 1 deletion Bitkit/AppScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ struct AppScene: View {
@StateObject private var tagManager = TagManager()
@StateObject private var transferTracking: TransferTrackingManager
@StateObject private var channelDetails = ChannelDetailsViewModel.shared
@StateObject private var migrations = MigrationsService.shared

@State private var hideSplash = false
@State private var removeSplash = false
Expand Down Expand Up @@ -72,6 +73,9 @@ struct AppScene: View {
.onChange(of: wallet.walletExists, perform: handleWalletExistsChange)
.onChange(of: wallet.nodeLifecycleState, perform: handleNodeLifecycleChange)
.onChange(of: scenePhase, perform: handleScenePhaseChange)
.onChange(of: migrations.isShowingMigrationLoading) { isLoading in
if !isLoading { widgets.loadSavedWidgets() }
}
.environmentObject(app)
.environmentObject(navigation)
.environmentObject(network)
Expand Down Expand Up @@ -111,7 +115,9 @@ struct AppScene: View {
@ViewBuilder
private var mainContent: some View {
ZStack {
if showRecoveryScreen {
if migrations.isShowingMigrationLoading {
migrationLoadingContent
} else if showRecoveryScreen {
RecoveryRouter()
.accentColor(.white)
} else if hasCriticalUpdate {
Expand All @@ -127,6 +133,32 @@ struct AppScene: View {
}
}

@ViewBuilder
private var migrationLoadingContent: some View {
VStack(spacing: 24) {
Spacer()

ProgressView()
.scaleEffect(1.5)
.tint(.white)

VStack(spacing: 8) {
Text("Updating Wallet")
.font(.system(size: 24, weight: .semibold))
.foregroundColor(.white)

Text("Please wait while we update the app...")
.font(.system(size: 16))
.foregroundColor(.white.opacity(0.7))
.multilineTextAlignment(.center)
}

Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.black)
}

@ViewBuilder
private var walletContent: some View {
if wallet.walletExists == true {
Expand Down Expand Up @@ -247,6 +279,7 @@ struct AppScene: View {
@Sendable
private func setupTask() async {
do {
await checkAndPerformRNMigration()
try wallet.setWalletExistsState()

// Setup TimedSheetManager with all timed sheets
Expand All @@ -262,6 +295,43 @@ struct AppScene: View {
}
}

private func checkAndPerformRNMigration() async {
let migrations = MigrationsService.shared

guard !migrations.isMigrationChecked else {
Logger.debug("RN migration already checked, skipping", context: "AppScene")
return
}

guard !migrations.hasNativeWalletData() else {
Logger.info("Native wallet data exists, skipping RN migration", context: "AppScene")
migrations.markMigrationChecked()
return
}

guard migrations.hasRNWalletData() else {
Logger.info("No RN wallet data found, skipping migration", context: "AppScene")
migrations.markMigrationChecked()
return
}

await MainActor.run { migrations.isShowingMigrationLoading = true }
Logger.info("RN wallet data found, starting migration...", context: "AppScene")

do {
try await migrations.migrateFromReactNative()
} catch {
Logger.error("RN migration failed: \(error)", context: "AppScene")
migrations.markMigrationChecked()
await MainActor.run { migrations.isShowingMigrationLoading = false }
app.toast(
type: .error,
title: "Migration Failed",
description: "Please restore your wallet manually using your recovery phrase"
)
}
}

private func handleNodeLifecycleChange(_ state: NodeLifecycleState) {
if state == .initializing {
walletIsInitializing = true
Expand Down
36 changes: 36 additions & 0 deletions Bitkit/Services/CoreService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
}
}
await MainActor.run {
self.cachedTxIdsInBoostTxIds = txIds

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

View workflow job for this annotation

GitHub Actions / Run Tests

reference to captured var 'txIds' in concurrently-executing code; this is an error in the Swift 6 language mode
}
} catch {
Logger.error("Failed to refresh boostTxIds cache: \(error)", context: "ActivityService")
Expand Down Expand Up @@ -109,7 +109,7 @@

func isActivitySeen(id: String) async -> Bool {
do {
if let activity = try await getActivityById(activityId: id) {

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

View workflow job for this annotation

GitHub Actions / Run Tests

no 'async' operations occur within 'await' expression
switch activity {
case let .onchain(onchain):
return onchain.seenAt != nil
Expand Down Expand Up @@ -154,6 +154,42 @@
}
}

func markAllUnseenActivitiesAsSeen() async {
let timestamp = UInt64(Date().timeIntervalSince1970)

do {
let activities = try await get()
var didMarkAny = false

for activity in activities {
let id: String
let isSeen: Bool

switch activity {
case let .onchain(onchain):
id = onchain.id
isSeen = onchain.seenAt != nil
case let .lightning(lightning):
id = lightning.id
isSeen = lightning.seenAt != nil
}

if !isSeen {
try await ServiceQueue.background(.core) {
try BitkitCore.markActivityAsSeen(activityId: id, seenAt: timestamp)
}
Comment on lines +178 to +180
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe could apply some parallelization in case the wallet has many activities, but I'm not sure if it would make a big difference

didMarkAny = true
}
}

if didMarkAny {
activitiesChangedSubject.send()
}
} catch {
Logger.error("Failed to mark all activities as seen: \(error)", context: "ActivityService")
}
}

// MARK: - Transaction Status Checks

func wasTransactionReplaced(txid: String) async -> Bool {
Expand Down Expand Up @@ -582,7 +618,7 @@
}

private func processLightningPayment(_ payment: PaymentDetails) async throws {
guard case let .bolt11(hash, preimage, secret, description, bolt11) = payment.kind else { return }

Check warning on line 621 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 621 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 { return }
Expand Down Expand Up @@ -635,7 +671,7 @@

for payment in payments {
do {
let state: BitkitCore.PaymentState = switch payment.status {

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

View workflow job for this annotation

GitHub Actions / Run Tests

immutable value 'state' was never used; consider replacing with '_' or removing it
case .failed:
.failed
case .pending:
Expand Down Expand Up @@ -671,7 +707,7 @@
latestCaughtError = error
}
}
} catch {

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

View workflow job for this annotation

GitHub Actions / Run Tests

'catch' block is unreachable because no errors are thrown in 'do' block
Logger.error("Error syncing LDK payment: \(error)", context: "CoreService")
latestCaughtError = error
}
Expand Down Expand Up @@ -705,7 +741,7 @@
/// Check if a transaction spends a closed channel's funding UTXO
private func findClosedChannelForTransaction(txid: String, transactionDetails: BitkitCore.TransactionDetails? = nil) async -> String? {
do {
let closedChannels = try await getAllClosedChannels(sortDirection: .desc)

Check warning on line 744 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 details = if let provided = transactionDetails { provided } else { await fetchTransactionDetails(txid: txid) }
Expand Down
13 changes: 11 additions & 2 deletions Bitkit/Services/LightningService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@

private init() {}

func setup(walletIndex: Int, electrumServerUrl: String? = nil, rgsServerUrl: String? = nil) async throws {
func setup(
walletIndex: Int,
electrumServerUrl: String? = nil,
rgsServerUrl: String? = nil,
channelMigration: ChannelDataMigration? = nil
) async throws {
Logger.debug("Checking lightning process lock...")
try StateLocker.lock(.lightning, wait: 30) // Wait 30 seconds to lock because maybe extension is still running

Expand Down Expand Up @@ -80,7 +85,11 @@
Logger.debug("Building ldk-node with vssUrl: '\(vssUrl)'")
Logger.debug("Building ldk-node with lnurlAuthServerUrl: '\(lnurlAuthServerUrl)'")

// Set entropy from mnemonic on builder
if let channelMigration {
builder.setChannelDataMigration(migration: channelMigration)
Logger.info("Applied channel migration: \(channelMigration.channelMonitors.count) monitors", context: "Migration")
}

builder.setEntropyBip39Mnemonic(mnemonic: mnemonic, passphrase: passphrase)

try await ServiceQueue.background(.ldk) {
Expand Down Expand Up @@ -488,7 +497,7 @@
}

func closeChannel(_ channel: ChannelDetails, force: Bool = false, forceCloseReason: String? = nil) async throws {
guard let node else {

Check warning on line 500 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

value 'node' was defined but never used; consider replacing with boolean test

Check warning on line 500 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

value 'node' was defined but never used; consider replacing with boolean test
throw AppError(serviceError: .nodeNotStarted)
}

Expand Down Expand Up @@ -706,7 +715,7 @@
onEvent?(event)

switch event {
case let .paymentSuccessful(paymentId, paymentHash, paymentPreimage, feePaidMsat):

Check warning on line 718 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

immutable value 'paymentPreimage' was never used; consider replacing with '_' or removing it

Check warning on line 718 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

immutable value 'paymentPreimage' was never used; consider replacing with '_' or removing it
Logger.info("✅ Payment successful: paymentId: \(paymentId ?? "?") paymentHash: \(paymentHash) feePaidMsat: \(feePaidMsat ?? 0)")
Task {
let hash = paymentId ?? paymentHash
Expand All @@ -731,7 +740,7 @@
Logger.warn("No paymentId or paymentHash available for failed payment", context: "LightningService")
}
}
case let .paymentReceived(paymentId, paymentHash, amountMsat, feePaidMsat):

Check warning on line 743 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

immutable value 'feePaidMsat' was never used; consider replacing with '_' or removing it
Logger.info("🤑 Payment received: paymentId: \(paymentId ?? "?") paymentHash: \(paymentHash) amountMsat: \(amountMsat)")
Task {
let hash = paymentId ?? paymentHash
Expand All @@ -741,7 +750,7 @@
Logger.error("Failed to handle payment received for \(hash): \(error)", context: "LightningService")
}
}
case let .paymentClaimable(paymentId, paymentHash, claimableAmountMsat, claimDeadline, customRecords):

Check warning on line 753 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

immutable value 'customRecords' was never used; consider replacing with '_' or removing it

Check warning on line 753 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

immutable value 'claimDeadline' was never used; consider replacing with '_' or removing it
Logger.info(
"🫰 Payment claimable: paymentId: \(paymentId) paymentHash: \(paymentHash) claimableAmountMsat: \(claimableAmountMsat)"
)
Expand Down Expand Up @@ -770,7 +779,7 @@

if let channel {
await registerClosedChannel(channel: channel, reason: reasonString)
await MainActor.run {

Check warning on line 782 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

result of call to 'run(resultType:body:)' is unused

Check warning on line 782 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

result of call to 'run(resultType:body:)' is unused
channelCache.removeValue(forKey: channelIdString)
}
} else {
Expand All @@ -793,7 +802,7 @@
Logger.error("Failed to handle transaction received for \(txid): \(error)", context: "LightningService")
}
}
case let .onchainTransactionConfirmed(txid, blockHash, blockHeight, confirmationTime, details):

Check warning on line 805 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

immutable value 'confirmationTime' was never used; consider replacing with '_' or removing it

Check warning on line 805 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

immutable value 'blockHash' was never used; consider replacing with '_' or removing it
Logger.info("✅ Onchain transaction confirmed: txid=\(txid) blockHeight=\(blockHeight) amountSats=\(details.amountSats)")
Task {
do {
Expand Down Expand Up @@ -847,7 +856,7 @@

// MARK: Balance Events

case let .balanceChanged(oldSpendableOnchain, newSpendableOnchain, oldTotalOnchain, newTotalOnchain, oldLightning, newLightning):

Check warning on line 859 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

immutable value 'newTotalOnchain' was never used; consider replacing with '_' or removing it

Check warning on line 859 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

immutable value 'oldTotalOnchain' was never used; consider replacing with '_' or removing it
Logger
.info("💰 Balance changed: onchain=\(oldSpendableOnchain)->\(newSpendableOnchain) lightning=\(oldLightning)->\(newLightning)")

Expand Down
Loading
Loading