diff --git a/Bitkit/AppScene.swift b/Bitkit/AppScene.swift index 83df9fe3..add6f520 100644 --- a/Bitkit/AppScene.swift +++ b/Bitkit/AppScene.swift @@ -239,7 +239,7 @@ struct AppScene: View { // Schedule full backup after wallet create/restore to prevent epoch dates in backup status await BackupService.shared.scheduleFullBackup() } catch { - Logger.error("Failed to start wallet") + Logger.error(error, context: "Failed to start wallet") Haptics.notify(.error) } } diff --git a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 768f28e5..99397463 100644 Binary files a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index b56481cb..06e11b50 100644 Binary files a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index a60ef332..dcbd8c6f 100644 Binary files a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index b4030353..1759c8a5 100644 Binary files a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index e4035573..00513e61 100644 Binary files a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index ae52a803..4aa0ef92 100644 Binary files a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index b56481cb..06e11b50 100644 Binary files a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 18caa44a..21dfd9cb 100644 Binary files a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index c927d6b8..79675fa7 100644 Binary files a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index c927d6b8..79675fa7 100644 Binary files a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 4967bf33..07460a3f 100644 Binary files a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 7d8062d8..186fb70b 100644 Binary files a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index eb8dbee2..1bb7a143 100644 Binary files a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 6cd57d24..b34a1240 100644 Binary files a/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/Bitkit/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/Bitkit/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png b/Bitkit/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png index 81fb9f14..ad3973e5 100644 Binary files a/Bitkit/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png and b/Bitkit/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png differ diff --git a/Bitkit/Bitkit.entitlements b/Bitkit/Bitkit.entitlements index d2a6d546..0898d896 100644 --- a/Bitkit/Bitkit.entitlements +++ b/Bitkit/Bitkit.entitlements @@ -17,6 +17,9 @@ keychain-access-groups $(AppIdentifierPrefix)to.bitkit + $(AppIdentifierPrefix)to.bitkit.signet + $(AppIdentifierPrefix)to.bitkit.testnet + $(AppIdentifierPrefix)to.bitkit.regtest diff --git a/Bitkit/Constants/Env.swift b/Bitkit/Constants/Env.swift index b2d3db66..a821ff29 100644 --- a/Bitkit/Constants/Env.swift +++ b/Bitkit/Constants/Env.swift @@ -9,56 +9,67 @@ enum Env { static let isPreview = ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" static let isTestFlight = Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt" static let isUnitTest = ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil - #if E2E_BUILD - static let isE2E = true - #else - static let isE2E = ProcessInfo.processInfo.environment["E2E"] == "true" - #endif - static let dustLimit = 547 - static let msatsPerSat: UInt64 = 1000 - #if CHECK_GEOBLOCK - static let isGeoblockingEnabled = true + #if DEBUG + static let isDebug = true #else - static let isGeoblockingEnabled = ProcessInfo.processInfo.environment["GEO"] == "true" + static let isDebug = false #endif + static var isE2E: Bool { + #if E2E_BUILD + return true + #else + return ProcessInfo.processInfo.environment["E2E"] == "true" + #endif + } + + static var isGeoblockingEnabled: Bool { + #if CHECK_GEOBLOCK + return true + #else + return ProcessInfo.processInfo.environment["GEO"] == "true" + #endif + } + /// The current execution context of the app static var currentExecutionContext: ExecutionContext { - return Bundle.main.bundleIdentifier?.lowercased().contains("notification") == true ? .pushNotificationExtension : .foregroundApp + let isNotificationExtension = Bundle.main.bundleIdentifier?.lowercased().contains("notification") == true + return isNotificationExtension ? .pushNotificationExtension : .foregroundApp } // {Team ID}.{Keychain Group} - static let keychainGroup = "KYH47R284B.to.bitkit" // TODO: needs to change for regtest/mainnet so we don't use same group - - #if targetEnvironment(simulator) - static let isSim = true - #else - static let isSim = false - #endif - - #if DEBUG - static let isDebug = true - #else - static let isDebug = false - #endif + /// Returns the keychain access group based on the current network + static var keychainGroup: String { + let base = "KYH47R284B.to.bitkit" + let networkSuffix = networkName(network) + return networkSuffix == "bitcoin" ? base : "\(base).\(networkSuffix)" + } // MARK: wallet services - static let network: LDKNode.Network = .regtest + static let network: LDKNode.Network = (isE2E || isUnitTest) ? .regtest : .bitcoin + static let ldkLogLevel = LDKNode.LogLevel.trace + static let walletSyncIntervalSecs: UInt64 = 10 // TODO: play around with this /// Converts the LDKNode.Network to BitkitCore.Network for use with bitkitcore functions static var bitkitCoreNetwork: BitkitCore.Network { switch network { - case .bitcoin: - return .bitcoin - case .testnet: - return .testnet - case .signet: - return .signet - case .regtest: - return .regtest + case .bitcoin: .bitcoin + case .testnet: .testnet + case .signet: .signet + case .regtest: .regtest + } + } + + /// Returns the lowercase name of the network (e.g., "bitcoin", "testnet", "signet", "regtest") + private static func networkName(_ network: LDKNode.Network) -> String { + switch network { + case .bitcoin: "bitcoin" + case .testnet: "testnet" + case .signet: "signet" + case .regtest: "regtest" } } @@ -74,27 +85,10 @@ enum Env { } switch network { - case .bitcoin: - return "ssl://35.187.18.233:8900" - case .signet: - fatalError("Signet network not implemented") - case .testnet: - return "ssl://electrum.blockstream.info:60002" - case .regtest: - return "tcp://34.65.252.32:18483" - } - } - - static var esploraServerUrl: String { - switch network { - case .regtest: - return "https://bitkit.stag0.blocktank.to/electrs" - case .bitcoin: - fatalError("Bitcoin network not implemented") - case .testnet: - fatalError("Testnet network not implemented") - case .signet: - fatalError("Signet network not implemented") + case .bitcoin: return "ssl://fulcrum.bitkit.blocktank.to:8900" + case .signet: return "ssl://mempool.space:60602" + case .testnet: return "ssl://electrum.blockstream.info:60002" + case .regtest: return "ssl://fulcrum.bitkit.stag0.blocktank.to:18484" } } @@ -112,83 +106,50 @@ enum Env { } static func ldkStorage(walletIndex: Int) -> URL { - switch network { - case .regtest: - return - appStorageUrl - .appendingPathComponent("regtest") - .appendingPathComponent("wallet\(walletIndex)/ldk") - case .bitcoin: - return - appStorageUrl - .appendingPathComponent("bitcoin") - .appendingPathComponent("wallet\(walletIndex)/ldk") - case .testnet: - fatalError("Testnet network not implemented") - case .signet: - fatalError("Signet network not implemented") - } + appStorageUrl + .appendingPathComponent(networkName(network)) + .appendingPathComponent("wallet\(walletIndex)/ldk") } static func bitkitCoreStorage(walletIndex: Int) -> URL { - switch network { - case .regtest: - return - appStorageUrl - .appendingPathComponent("regtest") - .appendingPathComponent("wallet\(walletIndex)/core") - case .bitcoin: - return - appStorageUrl - .appendingPathComponent("bitcoin") - .appendingPathComponent("wallet\(walletIndex)/core") - case .testnet: - fatalError("Testnet network not implemented") - case .signet: - fatalError("Signet network not implemented") - } + appStorageUrl + .appendingPathComponent(networkName(network)) + .appendingPathComponent("wallet\(walletIndex)/core") } static var ldkRgsServerUrl: String? { switch network { - case .regtest: - return "https://bitkit.stag0.blocktank.to/rgs/snapshot" - case .bitcoin: - return "https://rgs.blocktank.to/snapshot" - case .testnet: - return "https://rapidsync.lightningdevkit.org/testnet/snapshot" - case .signet: - return nil + case .bitcoin: "https://rgs.blocktank.to/snapshot" + case .signet: "https://rapidsync.lightningdevkit.org/signet/snapshot" + case .testnet: "https://rapidsync.lightningdevkit.org/testnet/snapshot" + case .regtest: "https://bitkit.stag0.blocktank.to/rgs/snapshot" } } // TODO: remove this to load from BT API instead static var trustedLnPeers: [LnPeer] { switch network { - case .regtest: + case .bitcoin: return [ - // Staging Blocktank node - .init(nodeId: "028a8910b0048630d4eb17af25668cdd7ea6f2d8ae20956e7a06e2ae46ebcb69fc", host: "34.65.86.104", port: 9400), + .init(nodeId: "039b8b4dd1d88c2c5db374290cda397a8f5d79f312d6ea5d5bfdfc7c6ff363eae3", host: "34.65.111.104", port: 9735), + .init(nodeId: "03816141f1dce7782ec32b66a300783b1d436b19777e7c686ed00115bd4b88ff4b", host: "34.65.191.64", port: 9735), + .init(nodeId: "02a371038863605300d0b3fc9de0cf5ccb57728b7f8906535709a831b16e311187", host: "34.65.186.40", port: 9735), ] - case .bitcoin: + case .signet: return [] case .testnet: return [] - case .signet: - return [] + case .regtest: + return [ + .init(nodeId: "028a8910b0048630d4eb17af25668cdd7ea6f2d8ae20956e7a06e2ae46ebcb69fc", host: "34.65.86.104", port: 9400), + ] } } static var blocktankBaseUrl: String { switch network { - case .regtest: - return "https://api.stag0.blocktank.to" - case .bitcoin: - fatalError("Bitcoin network not implemented") - case .testnet: - fatalError("Testnet network not implemented") - case .signet: - fatalError("Signet network not implemented") + case .bitcoin: "https://api1.blocktank.to/api" + default: "https://api.stag0.blocktank.to/" } } @@ -197,11 +158,19 @@ enum Env { } static var blocktankClientServer: String { - "\(blocktankBaseUrl)/blocktank/api/v2" + switch network { + case .bitcoin: "\(blocktankBaseUrl)" + default: "\(blocktankBaseUrl)/blocktank/api/v2" + } } static var btcRatesServer: String { - "https://bitkit.stag0.blocktank.to/fx/rates/btc" // TODO: switch to prod when available + switch network { + case .bitcoin: "https://blocktank.synonym.to/fx/rates/btc" + case .signet: "https://bitkit.stag0.blocktank.to/fx/rates/btc" + case .testnet: "https://bitkit.stag0.blocktank.to/fx/rates/btc" + case .regtest: "https://bitkit.stag0.blocktank.to/fx/rates/btc" + } } static let fxRateRefreshInterval: TimeInterval = 2 * 60 // 2 minutes @@ -217,45 +186,39 @@ enum Env { .wakeToTimeout, ] + static var vssStoreIdPrefix: String { + "bitkit_v1_\(networkName(network))" + } + static var vssServerUrl: String { switch network { - case .bitcoin: - fatalError("Bitcoin network not implemented") - default: - return "https://bitkit.stag0.blocktank.to/vss_rs_auth" + case .bitcoin: "https://bitkit.to/vss_rs_auth" + default: "https://bitkit.stag0.blocktank.to/vss_rs_auth" } } - static var vssStoreIdPrefix: String { + static var lnurlAuthServerUrl: String { switch network { - case .bitcoin: - fatalError("Bitcoin network not implemented") - case .regtest: - return "bitkit_v1_regtest" - case .testnet: - return "bitkit_v1_testnet" - case .signet: - return "bitkit_v1_signet" + case .bitcoin: "https://bitkit.to/lnurl_auth/auth" + default: "https://bitkit.stag0.blocktank.to/lnurl_auth/auth" } } - static var lnurlAuthServerUrl: String { + static var blockExplorerUrl: String { switch network { - case .bitcoin: - fatalError("LNURL-auth server not implemented for mainnet") - default: - return "https://bitkit.stag0.blocktank.to/lnurl_auth/auth" + case .bitcoin: "https://mempool.space" + case .signet: "https://mutinynet.com" + case .testnet: "https://mempool.space/testnet" + case .regtest: "https://mempool.bitkit.stag0.blocktank.to" } } static var logDirectory: String { - return appStorageUrl.appendingPathComponent("logs").path - } - - static var ldkLogLevel: LDKNode.LogLevel { - return .trace + appStorageUrl.appendingPathComponent("logs").path } + static let dustLimit = 547 + static let msatsPerSat: UInt64 = 1000 static let appStoreUrl = "https://apps.apple.com/app/bitkit-wallet/id6502440655" static let playStoreUrl = "https://play.google.com/store/apps/details?id=to.bitkit" static let githubUrl = "https://www.github.com/synonymdev/bitkit" diff --git a/Bitkit/MainNavView.swift b/Bitkit/MainNavView.swift index cf522206..5320ecf9 100644 --- a/Bitkit/MainNavView.swift +++ b/Bitkit/MainNavView.swift @@ -389,6 +389,7 @@ struct MainNavView: View { // Dev settings case .blocktankRegtest: BlocktankRegtestView() + case .ldkDebug: LdkDebugScreen() case .orders: ChannelOrders() case .logs: LogView() } diff --git a/Bitkit/Services/BackupService.swift b/Bitkit/Services/BackupService.swift index d4949d1f..a229b8ba 100644 --- a/Bitkit/Services/BackupService.swift +++ b/Bitkit/Services/BackupService.swift @@ -85,7 +85,6 @@ class BackupService { guard shouldStart == true else { return } - try? await vssBackupClient.setup() Logger.debug("Start observing backup statuses and data store changes", context: "BackupService") startBackupStatusObservers() startDataStoreListeners() @@ -130,8 +129,6 @@ class BackupService { } do { - try await vssBackupClient.setup() - let data = try await getBackupDataBytes(category: category) let _ = try await vssBackupClient.putObject(key: category.rawValue, data: data) diff --git a/Bitkit/Services/CoreService.swift b/Bitkit/Services/CoreService.swift index 4c910d51..ce9c627f 100644 --- a/Bitkit/Services/CoreService.swift +++ b/Bitkit/Services/CoreService.swift @@ -241,11 +241,6 @@ class ActivityService { func removeAll() async throws { try await ServiceQueue.background(.core) { - // Only allow removing on regtest for now - guard Env.network == .regtest else { - throw AppError(message: "Regtest only", debugMessage: nil) - } - // Get all activities and delete them one by one let activities = try getActivities( filter: .all, txType: nil, tags: nil, search: nil, minDate: nil, maxDate: nil, limit: nil, sortDirection: nil diff --git a/Bitkit/Services/LightningService.swift b/Bitkit/Services/LightningService.swift index ee78eb7d..bf9fdd93 100644 --- a/Bitkit/Services/LightningService.swift +++ b/Bitkit/Services/LightningService.swift @@ -41,9 +41,11 @@ class LightningService { Logger.debug("Using LDK storage path: \(ldkStoragePath)") - config.trustedPeers0conf = Env.trustedLnPeers.map(\.nodeId) + let trustedPeersIds = Env.trustedLnPeers.map(\.nodeId) + + config.trustedPeers0conf = trustedPeersIds config.anchorChannelsConfig = .init( - trustedPeersNoReserve: Env.trustedLnPeers.map(\.nodeId), + trustedPeersNoReserve: trustedPeersIds, perChannelReserveSats: 1 ) config.includeUntrustedPendingInSpendable = true @@ -571,6 +573,27 @@ class LightningService { } } + func logNetworkGraphInfo() async throws -> String { + guard let node else { + throw AppError(serviceError: .nodeNotSetup) + } + + let nodeStatus = node.status() + let networkGraph = node.networkGraph() + let allNodes = networkGraph.listNodes() + let lastRgsSync = nodeStatus.latestRgsSnapshotTimestamp + + var lastRgsSyncString = "Never" + if let lastRgsSync { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + dateFormatter.timeZone = TimeZone(abbreviation: "UTC") + lastRgsSyncString = dateFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(lastRgsSync))) + } + + return "Nodes: \(allNodes.count), Last Synced: \(lastRgsSyncString)" + } + // MARK: Logging helpers private func generateLogFilePath() -> String { diff --git a/Bitkit/Utilities/AppReset.swift b/Bitkit/Utilities/AppReset.swift index b4b1d5a2..021122a9 100644 --- a/Bitkit/Utilities/AppReset.swift +++ b/Bitkit/Utilities/AppReset.swift @@ -35,7 +35,9 @@ enum AppReset { } // Wipe logs - try wipeLogs() + if Env.network == .regtest { + try wipeLogs() + } // Recreate the entire app state tree to guarantee clean defaults for all @StateObject VMs. // Avoid showing splash during when app is reset diff --git a/Bitkit/ViewModels/NavigationViewModel.swift b/Bitkit/ViewModels/NavigationViewModel.swift index ff70e4fc..37fdf469 100644 --- a/Bitkit/ViewModels/NavigationViewModel.swift +++ b/Bitkit/ViewModels/NavigationViewModel.swift @@ -90,6 +90,7 @@ enum Route: Hashable { // Dev settings case blocktankRegtest + case ldkDebug case orders case logs } diff --git a/Bitkit/Views/Settings/DevSettingsView.swift b/Bitkit/Views/Settings/DevSettingsView.swift index a5859269..d6780b46 100644 --- a/Bitkit/Views/Settings/DevSettingsView.swift +++ b/Bitkit/Views/Settings/DevSettingsView.swift @@ -20,6 +20,10 @@ struct DevSettingsView: View { } } + NavigationLink(value: Route.ldkDebug) { + SettingsListLabel(title: "LDK") + } + NavigationLink(value: Route.orders) { SettingsListLabel(title: "Orders") } @@ -56,23 +60,6 @@ struct DevSettingsView: View { SettingsListLabel(title: "Show Logs") } - Button { - Task { - do { - try await notificationManager.sendTestNotification() - } catch { - Logger.error(error, context: "failed to test push notification") - app.toast( - type: .error, - title: "Error", - description: "Failed to send test notification: \(error)" - ) - } - } - } label: { - SettingsListLabel(title: "Test Push Notification", rightIcon: nil) - } - Button { Task { guard let zipURL = LogService.shared.zipLogs() else { @@ -98,6 +85,23 @@ struct DevSettingsView: View { SettingsListLabel(title: "Export Logs", rightIcon: nil) } + Button { + Task { + do { + try await notificationManager.sendTestNotification() + } catch { + Logger.error(error, context: "failed to test push notification") + app.toast( + type: .error, + title: "Error", + description: "Failed to send test notification: \(error)" + ) + } + } + } label: { + SettingsListLabel(title: "Test Push Notification", rightIcon: nil) + } + Button { Task { do { diff --git a/Bitkit/Views/Settings/LdkDebugScreen.swift b/Bitkit/Views/Settings/LdkDebugScreen.swift new file mode 100644 index 00000000..c8e144d2 --- /dev/null +++ b/Bitkit/Views/Settings/LdkDebugScreen.swift @@ -0,0 +1,114 @@ +import SwiftUI + +struct LdkDebugScreen: View { + @EnvironmentObject var app: AppViewModel + @EnvironmentObject var wallet: WalletViewModel + + @State private var nodeUri: String = "" + @State private var showDeleteConfirmation = false + @State private var isRestartingNode = false + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + NavigationBar(title: "LDK Debug") + .padding(.bottom, 16) + + ScrollView(showsIndicators: false) { + VStack(alignment: .leading, spacing: 32) { + // Add Peer + VStack(alignment: .leading, spacing: 8) { + CaptionMText("Add Peer") + + TextField("039b8d4d...a8f3eae3@127.0.0.1:9735", text: $nodeUri) + + HStack(spacing: 8) { + CustomButton(title: "Add Peer", size: .small) { + Task { + try await addPeer() + } + } + CustomButton(title: "Paste & Add", size: .small) { + Task { + try await pasteAndAddPeer() + } + } + } + } + + // Network Graph Storage + VStack(alignment: .leading, spacing: 8) { + CaptionMText("Network Graph Storage") + + HStack(spacing: 8) { + CustomButton(title: "Log Graph Info", size: .small) { + Task { + await logNetworkGraphInfo() + } + } + } + } + + // Node + VStack(alignment: .leading, spacing: 8) { + CaptionMText("Node") + + HStack(spacing: 8) { + CustomButton(title: "Restart", size: .small, isLoading: isRestartingNode) { + Task { + await restartNode() + } + } + } + } + } + } + } + .navigationBarHidden(true) + .padding(.horizontal, 16) + .bottomSafeAreaPadding() + } + + func addPeer() async throws { + do { + let lnPeer = try LnPeer(connection: nodeUri) + try await wallet.connectPeer(lnPeer) + app.toast(type: .success, title: "Peer added", description: "Peer added successfully") + } catch { + Logger.error(error, context: "LdkDebugScreen") + app.toast(type: .error, title: "Error", description: "Failed to add peer: \(error.localizedDescription)") + } + } + + func pasteAndAddPeer() async throws { + guard let pastedText = UIPasteboard.general.string?.trimmingCharacters(in: .whitespacesAndNewlines) else { + app.toast(type: .error, title: "Error", description: "Failed to paste text") + return + } + nodeUri = pastedText + try await addPeer() + } + + func logNetworkGraphInfo() async { + do { + let lightningService = LightningService.shared + let info = try await lightningService.logNetworkGraphInfo() + app.toast(type: .info, title: "Network Graph Info", description: info) + } catch { + Logger.error("Failed to log network graph info: \(error)") + } + } + + func restartNode() async { + do { + isRestartingNode = true + let lightningService = LightningService.shared + try await lightningService.restart() + app.toast(type: .success, title: "Node Restarted", description: "Node restarted successfully") + } catch { + Logger.error("Failed to restart node: \(error)") + app.toast(type: .error, title: "Error", description: "Failed to restart node: \(error.localizedDescription)") + } + + isRestartingNode = false + } +} diff --git a/Bitkit/Views/Wallets/Activity/ActivityExplorerView.swift b/Bitkit/Views/Wallets/Activity/ActivityExplorerView.swift index ac22ed00..c429ca81 100644 --- a/Bitkit/Views/Wallets/Activity/ActivityExplorerView.swift +++ b/Bitkit/Views/Wallets/Activity/ActivityExplorerView.swift @@ -41,12 +41,7 @@ struct ActivityExplorerView: View { } private func getBlockExplorerUrl(txId: String) -> URL? { - let baseUrl = - switch Env.network { - case .testnet: "https://mempool.space/testnet" - case .bitcoin, .regtest, .signet: "https://mempool.space" - } - return URL(string: "\(baseUrl)/tx/\(txId)") + return URL(string: "\(Env.blockExplorerUrl)/tx/\(txId)") } private func loadTransactionDetails() async { diff --git a/BitkitNotification/BitkitNotification.entitlements b/BitkitNotification/BitkitNotification.entitlements index a9b567fb..e52eec14 100644 --- a/BitkitNotification/BitkitNotification.entitlements +++ b/BitkitNotification/BitkitNotification.entitlements @@ -9,6 +9,9 @@ keychain-access-groups $(AppIdentifierPrefix)to.bitkit + $(AppIdentifierPrefix)to.bitkit.signet + $(AppIdentifierPrefix)to.bitkit.testnet + $(AppIdentifierPrefix)to.bitkit.regtest