From c06bca615100ae61e9ce1601d05a714628822633 Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 1 Jul 2024 13:44:21 +0200 Subject: [PATCH 1/3] feat: ldk-node test --- Bitkit.xcodeproj/project.pbxproj | 51 ++++++++++++ .../xcshareddata/swiftpm/Package.resolved | 15 ++++ .../UserInterfaceState.xcuserstate | Bin 20196 -> 31739 bytes Bitkit/ContentView.swift | 16 +++- Bitkit/Services/LightningService.swift | 78 ++++++++++++++++++ Bitkit/ViewModels/LightningViewModel.swift | 31 +++++++ CONTRIBUTING.md | 0 README.md | 5 ++ 8 files changed, 192 insertions(+), 4 deletions(-) create mode 100644 Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 Bitkit/Services/LightningService.swift create mode 100644 Bitkit/ViewModels/LightningViewModel.swift create mode 100644 CONTRIBUTING.md create mode 100644 README.md diff --git a/Bitkit.xcodeproj/project.pbxproj b/Bitkit.xcodeproj/project.pbxproj index 5559d45a..17ec85ff 100644 --- a/Bitkit.xcodeproj/project.pbxproj +++ b/Bitkit.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 96B129FD2C2EC05D00DD07B0 /* LDKNode in Frameworks */ = {isa = PBXBuildFile; productRef = 96B129FC2C2EC05D00DD07B0 /* LDKNode */; }; + 96B12A002C2EC37B00DD07B0 /* LightningViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96B129FF2C2EC37B00DD07B0 /* LightningViewModel.swift */; }; + 96B12A032C2EC65000DD07B0 /* LightningService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96B12A022C2EC65000DD07B0 /* LightningService.swift */; }; 96FE1F652C2DE6AA006D0C8B /* BitkitApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96FE1F642C2DE6AA006D0C8B /* BitkitApp.swift */; }; 96FE1F672C2DE6AA006D0C8B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96FE1F662C2DE6AA006D0C8B /* ContentView.swift */; }; 96FE1F692C2DE6AC006D0C8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 96FE1F682C2DE6AC006D0C8B /* Assets.xcassets */; }; @@ -34,6 +37,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 96B129FF2C2EC37B00DD07B0 /* LightningViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightningViewModel.swift; sourceTree = ""; }; + 96B12A022C2EC65000DD07B0 /* LightningService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightningService.swift; sourceTree = ""; }; 96FE1F612C2DE6AA006D0C8B /* Bitkit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Bitkit.app; sourceTree = BUILT_PRODUCTS_DIR; }; 96FE1F642C2DE6AA006D0C8B /* BitkitApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitkitApp.swift; sourceTree = ""; }; 96FE1F662C2DE6AA006D0C8B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -52,6 +57,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 96B129FD2C2EC05D00DD07B0 /* LDKNode in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -72,6 +78,22 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 96B129FE2C2EC0ED00DD07B0 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 96B129FF2C2EC37B00DD07B0 /* LightningViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 96B12A012C2EC61500DD07B0 /* Services */ = { + isa = PBXGroup; + children = ( + 96B12A022C2EC65000DD07B0 /* LightningService.swift */, + ); + path = Services; + sourceTree = ""; + }; 96FE1F582C2DE6AA006D0C8B = { isa = PBXGroup; children = ( @@ -97,6 +119,8 @@ children = ( 96FE1F642C2DE6AA006D0C8B /* BitkitApp.swift */, 96FE1F662C2DE6AA006D0C8B /* ContentView.swift */, + 96B129FE2C2EC0ED00DD07B0 /* ViewModels */, + 96B12A012C2EC61500DD07B0 /* Services */, 96FE1F682C2DE6AC006D0C8B /* Assets.xcassets */, 96FE1F6A2C2DE6AC006D0C8B /* Bitkit.entitlements */, 96FE1F6B2C2DE6AC006D0C8B /* Preview Content */, @@ -145,6 +169,9 @@ dependencies = ( ); name = Bitkit; + packageProductDependencies = ( + 96B129FC2C2EC05D00DD07B0 /* LDKNode */, + ); productName = Bitkit; productReference = 96FE1F612C2DE6AA006D0C8B /* Bitkit.app */; productType = "com.apple.product-type.application"; @@ -217,6 +244,9 @@ Base, ); mainGroup = 96FE1F582C2DE6AA006D0C8B; + packageReferences = ( + 96B129FB2C2EC05D00DD07B0 /* XCRemoteSwiftPackageReference "ldk-node" */, + ); productRefGroup = 96FE1F622C2DE6AA006D0C8B /* Products */; projectDirPath = ""; projectRoot = ""; @@ -259,7 +289,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 96B12A002C2EC37B00DD07B0 /* LightningViewModel.swift in Sources */, 96FE1F672C2DE6AA006D0C8B /* ContentView.swift in Sources */, + 96B12A032C2EC65000DD07B0 /* LightningService.swift in Sources */, 96FE1F652C2DE6AA006D0C8B /* BitkitApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -621,6 +653,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 96B129FB2C2EC05D00DD07B0 /* XCRemoteSwiftPackageReference "ldk-node" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/lightningdevkit/ldk-node"; + requirement = { + branch = main; + kind = branch; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 96B129FC2C2EC05D00DD07B0 /* LDKNode */ = { + isa = XCSwiftPackageProductDependency; + package = 96B129FB2C2EC05D00DD07B0 /* XCRemoteSwiftPackageReference "ldk-node" */; + productName = LDKNode; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 96FE1F592C2DE6AA006D0C8B /* Project object */; } diff --git a/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..8a1f0693 --- /dev/null +++ b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "247ed477c6daed345df4c4f5bf67b0aa7ce2c2cee2632d2a5c6ca866bf1412e7", + "pins" : [ + { + "identity" : "ldk-node", + "kind" : "remoteSourceControl", + "location" : "https://github.com/lightningdevkit/ldk-node", + "state" : { + "branch" : "main", + "revision" : "66fec694f2b1de81dcb3d40073698a03dfe25522" + } + } + ], + "version" : 3 +} diff --git a/Bitkit.xcodeproj/project.xcworkspace/xcuserdata/jason.xcuserdatad/UserInterfaceState.xcuserstate b/Bitkit.xcodeproj/project.xcworkspace/xcuserdata/jason.xcuserdatad/UserInterfaceState.xcuserstate index e56afd0a02a7714d0f124156e4aefab2b619f1fa..badc29a0b35efbadc006cc6576b7caf88c243016 100644 GIT binary patch literal 31739 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 YOQEHt{@fp7?7Px4zAI|daBZplfAr2OkpKVy diff --git a/Bitkit/ContentView.swift b/Bitkit/ContentView.swift index 40c89e7d..6c7e51a9 100644 --- a/Bitkit/ContentView.swift +++ b/Bitkit/ContentView.swift @@ -8,14 +8,22 @@ import SwiftUI struct ContentView: View { + @StateObject var lnViewModel = LightningViewModel() + var body: some View { VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, Bitcoin!") + Text("LDK-Node running: \(lnViewModel.status?.isRunning == true ? "✅" : "❌")") } .padding() + .onAppear { + Task { + do { + try await lnViewModel.start() + } catch { + print("Error: \(error)") + } + } + } } } diff --git a/Bitkit/Services/LightningService.swift b/Bitkit/Services/LightningService.swift new file mode 100644 index 00000000..405f58c0 --- /dev/null +++ b/Bitkit/Services/LightningService.swift @@ -0,0 +1,78 @@ +// +// LightningService.swift +// Bitkit +// +// Created by Jason van den Berg on 2024/06/28. +// + +import Foundation +import LDKNode + +//TODO catch all errors and pass a readable error message to the UI + +class LightningService { + private var node: Node? + + static var shared: LightningService = LightningService() + + private init() { + + } + + func setup() throws { + //TODO share with app group for background extension + guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { + return //TODO throw error + } + + print(documentsDirectory) + + let storageDirPath = documentsDirectory.appendingPathComponent("ldk").path //TODO add network and maybe hash mnemonic + + var config = defaultConfig() + config.storageDirPath = storageDirPath + config.logDirPath = storageDirPath + config.network = .regtest + config.logLevel = .trace + + let nodeBuilder = Builder.fromConfig(config: config) + + //cargo run --release --bin electrs -- -vvv --jsonrpc-import --daemon-rpc-addr 127.0.0.1:18443 --cookie polaruser:polarpass + nodeBuilder.setEsploraServer(esploraServerUrl: "https://jaybird-logical-sadly.ngrok-free.app") //TODO get from ENV + +// nodeBuilder.setEsploraServer(esploraServerUrl: "http://localhost:3000") //TODO get from ENV +// nodeBuilder.setGossipSourceRgs(rgsServerUrl: "https://rapidsync.lightningdevkit.org/snapshot/") //TODO get from ENV + + let mnemonic = "science fatigue phone inner pipe solve acquire nothing birth slow armor flip debate gorilla select settle talk badge uphold firm video vibrant banner casual" // = generateEntropyMnemonic() + + nodeBuilder.setEntropyBip39Mnemonic(mnemonic: mnemonic, passphrase: nil) + + node = try nodeBuilder.build() + } + + func start() throws { + guard let node else { + //TODO throw custom error + return + } + try node.start() + } + + func sync() throws { + guard let node else { + //TODO throw custom error + return + } + try node.syncWallets() + } +} + +//MARK: UI Helpers (Published via LightningViewModel) +extension LightningService { + var nodeId: String? { node?.nodeId() } + var balances: BalanceDetails? { node?.listBalances() } + var status: NodeStatus? { node?.status() } + var listPeers: [PeerDetails]? { node?.listPeers() } + var listChannels: [ChannelDetails]? { node?.listChannels() } + var listPayments: [PaymentDetails]? { node?.listPayments() } +} diff --git a/Bitkit/ViewModels/LightningViewModel.swift b/Bitkit/ViewModels/LightningViewModel.swift new file mode 100644 index 00000000..46cc9531 --- /dev/null +++ b/Bitkit/ViewModels/LightningViewModel.swift @@ -0,0 +1,31 @@ +// +// LightningViewModel.swift +// Bitkit +// +// Created by Jason van den Berg on 2024/06/28. +// + +import SwiftUI +import LDKNode + +@MainActor +class LightningViewModel: ObservableObject { + @Published var status: NodeStatus? + + func start() async throws { + try LightningService.shared.setup() + try LightningService.shared.start() + await sync() + } + + func sync() async { + do { + try LightningService.shared.sync() + status = LightningService.shared.status + + //TODO sync everything else for the UI + } catch { + print("Error: \(error)") + } + } +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..e69de29b diff --git a/README.md b/README.md new file mode 100644 index 00000000..99f2b839 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +## Error handling + +- Don't pass exceptions up to the UI +- Translations +- Known lightning errors From 99cd70f9d96f696d870422279f7cfee7e5b272fe Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 1 Jul 2024 14:18:37 +0200 Subject: [PATCH 2/3] chore: move server urls to env vars --- Bitkit.xcodeproj/project.pbxproj | 12 +++ .../UserInterfaceState.xcuserstate | Bin 31739 -> 34371 bytes Bitkit/Constants/Env.swift | 81 ++++++++++++++++++ Bitkit/Services/LightningService.swift | 36 +++----- Bitkit/ViewModels/LightningViewModel.swift | 5 +- 5 files changed, 108 insertions(+), 26 deletions(-) create mode 100644 Bitkit/Constants/Env.swift diff --git a/Bitkit.xcodeproj/project.pbxproj b/Bitkit.xcodeproj/project.pbxproj index 17ec85ff..f69d3240 100644 --- a/Bitkit.xcodeproj/project.pbxproj +++ b/Bitkit.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 9637E6D32C32CE79004A92FC /* Env.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9637E6D22C32CE79004A92FC /* Env.swift */; }; 96B129FD2C2EC05D00DD07B0 /* LDKNode in Frameworks */ = {isa = PBXBuildFile; productRef = 96B129FC2C2EC05D00DD07B0 /* LDKNode */; }; 96B12A002C2EC37B00DD07B0 /* LightningViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96B129FF2C2EC37B00DD07B0 /* LightningViewModel.swift */; }; 96B12A032C2EC65000DD07B0 /* LightningService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96B12A022C2EC65000DD07B0 /* LightningService.swift */; }; @@ -37,6 +38,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 9637E6D22C32CE79004A92FC /* Env.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Env.swift; sourceTree = ""; }; 96B129FF2C2EC37B00DD07B0 /* LightningViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightningViewModel.swift; sourceTree = ""; }; 96B12A022C2EC65000DD07B0 /* LightningService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightningService.swift; sourceTree = ""; }; 96FE1F612C2DE6AA006D0C8B /* Bitkit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Bitkit.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -78,6 +80,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 9637E6D12C32CE65004A92FC /* Constants */ = { + isa = PBXGroup; + children = ( + 9637E6D22C32CE79004A92FC /* Env.swift */, + ); + path = Constants; + sourceTree = ""; + }; 96B129FE2C2EC0ED00DD07B0 /* ViewModels */ = { isa = PBXGroup; children = ( @@ -121,6 +131,7 @@ 96FE1F662C2DE6AA006D0C8B /* ContentView.swift */, 96B129FE2C2EC0ED00DD07B0 /* ViewModels */, 96B12A012C2EC61500DD07B0 /* Services */, + 9637E6D12C32CE65004A92FC /* Constants */, 96FE1F682C2DE6AC006D0C8B /* Assets.xcassets */, 96FE1F6A2C2DE6AC006D0C8B /* Bitkit.entitlements */, 96FE1F6B2C2DE6AC006D0C8B /* Preview Content */, @@ -289,6 +300,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9637E6D32C32CE79004A92FC /* Env.swift in Sources */, 96B12A002C2EC37B00DD07B0 /* LightningViewModel.swift in Sources */, 96FE1F672C2DE6AA006D0C8B /* ContentView.swift in Sources */, 96B12A032C2EC65000DD07B0 /* LightningService.swift in Sources */, diff --git a/Bitkit.xcodeproj/project.xcworkspace/xcuserdata/jason.xcuserdatad/UserInterfaceState.xcuserstate b/Bitkit.xcodeproj/project.xcworkspace/xcuserdata/jason.xcuserdatad/UserInterfaceState.xcuserstate index badc29a0b35efbadc006cc6576b7caf88c243016..9fa3cb60694a2fb599d67aa1fba8926982fee7e3 100644 GIT binary patch delta 15992 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 delta 14051 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 L`Lp%CdinnWr6kcS diff --git a/Bitkit/Constants/Env.swift b/Bitkit/Constants/Env.swift new file mode 100644 index 00000000..ee73c05d --- /dev/null +++ b/Bitkit/Constants/Env.swift @@ -0,0 +1,81 @@ +// +// Env.swift +// Bitkit +// +// Created by Jason van den Berg on 2024/07/01. +// + +import Foundation +import LDKNode + +struct 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 targetEnvironment(simulator) + static let isSim = true +#else + static let isSim = false +#endif + +#if DEBUG + static let isDebug = true +#else + static let isDebug = false +#endif + + //MARK: wallet services + static let network: Network = .regtest + static var esploraServerUrl: String { + switch network { + case .regtest: + //cargo run --release --bin electrs -- -vvv --jsonrpc-import --daemon-rpc-addr 127.0.0.1:18443 --cookie polaruser:polarpass +// return "https://jaybird-logical-sadly.ngrok-free.app" + return "http://localhost:3000" + case .bitcoin: + fatalError("Bitcoin network not implemented") + case .testnet: + fatalError("Testnet network not implemented") + case .signet: + fatalError("Signet network not implemented") + } + } + + static var appStorageUrl: URL { + //TODO move to app group so files can be shared with extensions + guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { + fatalError("Could not find documents directory") + } + + return documentsDirectory + } + + static var ldkStorage: URL { + let storageDirPath = appStorageUrl.appendingPathComponent("ldk") + + switch network { + case .regtest: + return storageDirPath.appendingPathComponent("regtest") + case .bitcoin: + fatalError("Bitcoin network not implemented") + case .testnet: + fatalError("Testnet network not implemented") + case .signet: + fatalError("Signet network not implemented") + } + } + + static var ldkRgsServerUrl: String? { + switch network { + case .regtest: + return nil + case .bitcoin: + return "https://rapidsync.lightningdevkit.org/snapshot/" + case .testnet: + return nil + case .signet: + return nil + } + } +} diff --git a/Bitkit/Services/LightningService.swift b/Bitkit/Services/LightningService.swift index 405f58c0..4d8efc2d 100644 --- a/Bitkit/Services/LightningService.swift +++ b/Bitkit/Services/LightningService.swift @@ -12,39 +12,25 @@ import LDKNode class LightningService { private var node: Node? - + static var shared: LightningService = LightningService() - private init() { - - } + private init() {} - func setup() throws { - //TODO share with app group for background extension - guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { - return //TODO throw error - } - - print(documentsDirectory) - - let storageDirPath = documentsDirectory.appendingPathComponent("ldk").path //TODO add network and maybe hash mnemonic - + func setup(mnemonic: String, passphrase: String?) throws { var config = defaultConfig() - config.storageDirPath = storageDirPath - config.logDirPath = storageDirPath + config.storageDirPath = Env.ldkStorage.path + config.logDirPath = Env.ldkStorage.path config.network = .regtest config.logLevel = .trace - + let nodeBuilder = Builder.fromConfig(config: config) - - //cargo run --release --bin electrs -- -vvv --jsonrpc-import --daemon-rpc-addr 127.0.0.1:18443 --cookie polaruser:polarpass - nodeBuilder.setEsploraServer(esploraServerUrl: "https://jaybird-logical-sadly.ngrok-free.app") //TODO get from ENV - -// nodeBuilder.setEsploraServer(esploraServerUrl: "http://localhost:3000") //TODO get from ENV -// nodeBuilder.setGossipSourceRgs(rgsServerUrl: "https://rapidsync.lightningdevkit.org/snapshot/") //TODO get from ENV + nodeBuilder.setEsploraServer(esploraServerUrl: Env.esploraServerUrl) + + if let rgsServerUrl = Env.ldkRgsServerUrl { + nodeBuilder.setGossipSourceRgs(rgsServerUrl: rgsServerUrl) + } - let mnemonic = "science fatigue phone inner pipe solve acquire nothing birth slow armor flip debate gorilla select settle talk badge uphold firm video vibrant banner casual" // = generateEntropyMnemonic() - nodeBuilder.setEntropyBip39Mnemonic(mnemonic: mnemonic, passphrase: nil) node = try nodeBuilder.build() diff --git a/Bitkit/ViewModels/LightningViewModel.swift b/Bitkit/ViewModels/LightningViewModel.swift index 46cc9531..a1ba5e23 100644 --- a/Bitkit/ViewModels/LightningViewModel.swift +++ b/Bitkit/ViewModels/LightningViewModel.swift @@ -13,7 +13,10 @@ class LightningViewModel: ObservableObject { @Published var status: NodeStatus? func start() async throws { - try LightningService.shared.setup() + let mnemonic = "science fatigue phone inner pipe solve acquire nothing birth slow armor flip debate gorilla select settle talk badge uphold firm video vibrant banner casual" // = generateEntropyMnemonic() + let passphrase: String? = nil + + try LightningService.shared.setup(mnemonic: mnemonic, passphrase: passphrase) try LightningService.shared.start() await sync() } From 097554566f25f78f6a07652accb41bb34afad5cb Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 1 Jul 2024 16:54:13 +0200 Subject: [PATCH 3/3] feat: bdk setup --- Bitkit.xcodeproj/project.pbxproj | 41 +++++++++ .../xcshareddata/swiftpm/Package.resolved | 11 ++- .../UserInterfaceState.xcuserstate | Bin 34371 -> 50137 bytes Bitkit/Constants/Env.swift | 18 +++- Bitkit/ContentView.swift | 50 ++++++++++- Bitkit/Models/LnPeer.swift | 30 +++++++ Bitkit/Models/WalletNetwork.swift | 56 ++++++++++++ Bitkit/Services/LightningService.swift | 16 +++- Bitkit/Services/OnChainService.swift | 83 ++++++++++++++++++ Bitkit/ViewModels/LightningViewModel.swift | 8 +- Bitkit/ViewModels/OnChainViewModel.swift | 37 ++++++++ 11 files changed, 339 insertions(+), 11 deletions(-) create mode 100644 Bitkit/Models/LnPeer.swift create mode 100644 Bitkit/Models/WalletNetwork.swift create mode 100644 Bitkit/Services/OnChainService.swift create mode 100644 Bitkit/ViewModels/OnChainViewModel.swift diff --git a/Bitkit.xcodeproj/project.pbxproj b/Bitkit.xcodeproj/project.pbxproj index f69d3240..d04c6dc1 100644 --- a/Bitkit.xcodeproj/project.pbxproj +++ b/Bitkit.xcodeproj/project.pbxproj @@ -8,6 +8,11 @@ /* Begin PBXBuildFile section */ 9637E6D32C32CE79004A92FC /* Env.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9637E6D22C32CE79004A92FC /* Env.swift */; }; + 9637E6D52C32D811004A92FC /* OnChainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9637E6D42C32D811004A92FC /* OnChainService.swift */; }; + 9637E6D82C32D8A7004A92FC /* BitcoinDevKit in Frameworks */ = {isa = PBXBuildFile; productRef = 9637E6D72C32D8A7004A92FC /* BitcoinDevKit */; }; + 9637E6DA2C32E573004A92FC /* OnChainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9637E6D92C32E573004A92FC /* OnChainViewModel.swift */; }; + 9637E6DD2C32EAA8004A92FC /* WalletNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9637E6DC2C32EAA8004A92FC /* WalletNetwork.swift */; }; + 9637E6DF2C32ED7B004A92FC /* LnPeer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9637E6DE2C32ED7B004A92FC /* LnPeer.swift */; }; 96B129FD2C2EC05D00DD07B0 /* LDKNode in Frameworks */ = {isa = PBXBuildFile; productRef = 96B129FC2C2EC05D00DD07B0 /* LDKNode */; }; 96B12A002C2EC37B00DD07B0 /* LightningViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96B129FF2C2EC37B00DD07B0 /* LightningViewModel.swift */; }; 96B12A032C2EC65000DD07B0 /* LightningService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96B12A022C2EC65000DD07B0 /* LightningService.swift */; }; @@ -39,6 +44,10 @@ /* Begin PBXFileReference section */ 9637E6D22C32CE79004A92FC /* Env.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Env.swift; sourceTree = ""; }; + 9637E6D42C32D811004A92FC /* OnChainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnChainService.swift; sourceTree = ""; }; + 9637E6D92C32E573004A92FC /* OnChainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnChainViewModel.swift; sourceTree = ""; }; + 9637E6DC2C32EAA8004A92FC /* WalletNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletNetwork.swift; sourceTree = ""; }; + 9637E6DE2C32ED7B004A92FC /* LnPeer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LnPeer.swift; sourceTree = ""; }; 96B129FF2C2EC37B00DD07B0 /* LightningViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightningViewModel.swift; sourceTree = ""; }; 96B12A022C2EC65000DD07B0 /* LightningService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightningService.swift; sourceTree = ""; }; 96FE1F612C2DE6AA006D0C8B /* Bitkit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Bitkit.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -59,6 +68,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 9637E6D82C32D8A7004A92FC /* BitcoinDevKit in Frameworks */, 96B129FD2C2EC05D00DD07B0 /* LDKNode in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -88,10 +98,20 @@ path = Constants; sourceTree = ""; }; + 9637E6DB2C32EA84004A92FC /* Models */ = { + isa = PBXGroup; + children = ( + 9637E6DC2C32EAA8004A92FC /* WalletNetwork.swift */, + 9637E6DE2C32ED7B004A92FC /* LnPeer.swift */, + ); + path = Models; + sourceTree = ""; + }; 96B129FE2C2EC0ED00DD07B0 /* ViewModels */ = { isa = PBXGroup; children = ( 96B129FF2C2EC37B00DD07B0 /* LightningViewModel.swift */, + 9637E6D92C32E573004A92FC /* OnChainViewModel.swift */, ); path = ViewModels; sourceTree = ""; @@ -100,6 +120,7 @@ isa = PBXGroup; children = ( 96B12A022C2EC65000DD07B0 /* LightningService.swift */, + 9637E6D42C32D811004A92FC /* OnChainService.swift */, ); path = Services; sourceTree = ""; @@ -131,6 +152,7 @@ 96FE1F662C2DE6AA006D0C8B /* ContentView.swift */, 96B129FE2C2EC0ED00DD07B0 /* ViewModels */, 96B12A012C2EC61500DD07B0 /* Services */, + 9637E6DB2C32EA84004A92FC /* Models */, 9637E6D12C32CE65004A92FC /* Constants */, 96FE1F682C2DE6AC006D0C8B /* Assets.xcassets */, 96FE1F6A2C2DE6AC006D0C8B /* Bitkit.entitlements */, @@ -182,6 +204,7 @@ name = Bitkit; packageProductDependencies = ( 96B129FC2C2EC05D00DD07B0 /* LDKNode */, + 9637E6D72C32D8A7004A92FC /* BitcoinDevKit */, ); productName = Bitkit; productReference = 96FE1F612C2DE6AA006D0C8B /* Bitkit.app */; @@ -257,6 +280,7 @@ mainGroup = 96FE1F582C2DE6AA006D0C8B; packageReferences = ( 96B129FB2C2EC05D00DD07B0 /* XCRemoteSwiftPackageReference "ldk-node" */, + 9637E6D62C32D8A7004A92FC /* XCRemoteSwiftPackageReference "bdk-swift" */, ); productRefGroup = 96FE1F622C2DE6AA006D0C8B /* Products */; projectDirPath = ""; @@ -302,8 +326,12 @@ files = ( 9637E6D32C32CE79004A92FC /* Env.swift in Sources */, 96B12A002C2EC37B00DD07B0 /* LightningViewModel.swift in Sources */, + 9637E6DD2C32EAA8004A92FC /* WalletNetwork.swift in Sources */, + 9637E6DA2C32E573004A92FC /* OnChainViewModel.swift in Sources */, + 9637E6DF2C32ED7B004A92FC /* LnPeer.swift in Sources */, 96FE1F672C2DE6AA006D0C8B /* ContentView.swift in Sources */, 96B12A032C2EC65000DD07B0 /* LightningService.swift in Sources */, + 9637E6D52C32D811004A92FC /* OnChainService.swift in Sources */, 96FE1F652C2DE6AA006D0C8B /* BitkitApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -667,6 +695,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 9637E6D62C32D8A7004A92FC /* XCRemoteSwiftPackageReference "bdk-swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/bitcoindevkit/bdk-swift"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.31.1; + }; + }; 96B129FB2C2EC05D00DD07B0 /* XCRemoteSwiftPackageReference "ldk-node" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lightningdevkit/ldk-node"; @@ -678,6 +714,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 9637E6D72C32D8A7004A92FC /* BitcoinDevKit */ = { + isa = XCSwiftPackageProductDependency; + package = 9637E6D62C32D8A7004A92FC /* XCRemoteSwiftPackageReference "bdk-swift" */; + productName = BitcoinDevKit; + }; 96B129FC2C2EC05D00DD07B0 /* LDKNode */ = { isa = XCSwiftPackageProductDependency; package = 96B129FB2C2EC05D00DD07B0 /* XCRemoteSwiftPackageReference "ldk-node" */; diff --git a/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8a1f0693..6090409f 100644 --- a/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,6 +1,15 @@ { - "originHash" : "247ed477c6daed345df4c4f5bf67b0aa7ce2c2cee2632d2a5c6ca866bf1412e7", + "originHash" : "957b506793f070febdbc6a14c01a0fcfc0c18f7f437da5a1d36c9627c119b759", "pins" : [ + { + "identity" : "bdk-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/bitcoindevkit/bdk-swift", + "state" : { + "revision" : "f2398109ebd8759322f601d9938779f3cdf99a12", + "version" : "0.31.1" + } + }, { "identity" : "ldk-node", "kind" : "remoteSourceControl", diff --git a/Bitkit.xcodeproj/project.xcworkspace/xcuserdata/jason.xcuserdatad/UserInterfaceState.xcuserstate b/Bitkit.xcodeproj/project.xcworkspace/xcuserdata/jason.xcuserdatad/UserInterfaceState.xcuserstate index 9fa3cb60694a2fb599d67aa1fba8926982fee7e3..9a82fa0fdff5a882fc440b0cdc9d612be79443b9 100644 GIT binary patch literal 50137 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 diff --git a/Bitkit/Constants/Env.swift b/Bitkit/Constants/Env.swift index ee73c05d..95b18b4c 100644 --- a/Bitkit/Constants/Env.swift +++ b/Bitkit/Constants/Env.swift @@ -6,7 +6,6 @@ // import Foundation -import LDKNode struct Env { static let isPreview = ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" @@ -26,12 +25,12 @@ struct Env { #endif //MARK: wallet services - static let network: Network = .regtest + static let network: WalletNetwork = .regtest static var esploraServerUrl: String { switch network { case .regtest: //cargo run --release --bin electrs -- -vvv --jsonrpc-import --daemon-rpc-addr 127.0.0.1:18443 --cookie polaruser:polarpass -// return "https://jaybird-logical-sadly.ngrok-free.app" + // return "https://jaybird-logical-sadly.ngrok-free.app" return "http://localhost:3000" case .bitcoin: fatalError("Bitcoin network not implemented") @@ -78,4 +77,17 @@ struct Env { return nil } } + + static var trustedLnPeers: [LnPeer] { + switch network { + case .regtest: + return [.init(nodeId: "021de6ad59a78caf8f376cbd022e8c6ede2a1ef0a4fa035174e8b9c25ad5866584", address: "127.0.0.1:9736")] + case .bitcoin: + return [] + case .testnet: + return [] + case .signet: + return [] + } + } } diff --git a/Bitkit/ContentView.swift b/Bitkit/ContentView.swift index 6c7e51a9..22b4ac04 100644 --- a/Bitkit/ContentView.swift +++ b/Bitkit/ContentView.swift @@ -9,16 +9,62 @@ import SwiftUI struct ContentView: View { @StateObject var lnViewModel = LightningViewModel() + @StateObject var onChainViewModel = OnChainViewModel() var body: some View { VStack { - Text("LDK-Node running: \(lnViewModel.status?.isRunning == true ? "✅" : "❌")") + Group { + Text("LDK-Node running: \(lnViewModel.status?.isRunning == true ? "✅" : "❌")") + + + if let nodeId = lnViewModel.nodeId { + Text("LN Node ID: \(nodeId)") + .onTapGesture { + UIPasteboard.general.string = nodeId + } + } + + if let peers = lnViewModel.peers { + ForEach(peers, id: \.nodeId) { peer in + Text("Peer: \(peer.nodeId) \(peer.address) \(peer.isConnected ? "✅" : "❌")") + } + } + + if let lnBalance = lnViewModel.balance { + Text("Lightning \(lnBalance.totalLightningBalanceSats)") + } + + if let onchainBalance = onChainViewModel.balance { + Text("On Chain \(onchainBalance.total)") + } + + if let receiveAddress = onChainViewModel.address { + Text("Receive Address: \(receiveAddress)") + .onTapGesture { + UIPasteboard.general.string = receiveAddress + } + } + + Button("New Receive Address") { + try! onChainViewModel.newReceiveAddress() + } + + Button("Sync") { + Task { + await lnViewModel.sync() + await onChainViewModel.sync() + } + } + } + .multilineTextAlignment(.center) + .padding() } - .padding() + .padding(4) .onAppear { Task { do { try await lnViewModel.start() + try await onChainViewModel.start() } catch { print("Error: \(error)") } diff --git a/Bitkit/Models/LnPeer.swift b/Bitkit/Models/LnPeer.swift new file mode 100644 index 00000000..c39f592e --- /dev/null +++ b/Bitkit/Models/LnPeer.swift @@ -0,0 +1,30 @@ +// +// LnPeer.swift +// Bitkit +// +// Created by Jason van den Berg on 2024/07/01. +// + +import Foundation + +struct LnPeer { + let nodeId: String + let address: String + + init(nodeId: String, address: String) { + self.nodeId = nodeId + self.address = address + } + + init(connection: String) throws { + let parts = connection.split(separator: "@") + guard parts.count == 2 else { +// throw LnPeerError.invalidConnection + //TODO throw custom error + fatalError("Invalid connection") + } + + nodeId = String(parts[0]) + address = String(parts[1]) + } +} diff --git a/Bitkit/Models/WalletNetwork.swift b/Bitkit/Models/WalletNetwork.swift new file mode 100644 index 00000000..9332a9a8 --- /dev/null +++ b/Bitkit/Models/WalletNetwork.swift @@ -0,0 +1,56 @@ +// +// Network.swift +// Bitkit +// +// Created by Jason van den Berg on 2024/07/01. +// + +import Foundation +import LDKNode +import BitcoinDevKit + +enum WalletNetwork { + case regtest + case bitcoin + case testnet + case signet + + var displayName: String { + switch self { + case .regtest: + return "Regtest" + case .bitcoin: + return "Bitcoin" + case .testnet: + return "Testnet" + case .signet: + return "Signet" + } + } + + var ldkNetwork: LDKNode.Network { + switch self { + case .regtest: + return .regtest + case .bitcoin: + return .bitcoin + case .testnet: + return .testnet + case .signet: + return .signet + } + } + + var bdkNetwork: BitcoinDevKit.Network { + switch self { + case .regtest: + return .regtest + case .bitcoin: + return .bitcoin + case .testnet: + return .testnet + case .signet: + return .signet + } + } +} diff --git a/Bitkit/Services/LightningService.swift b/Bitkit/Services/LightningService.swift index 4d8efc2d..15724d67 100644 --- a/Bitkit/Services/LightningService.swift +++ b/Bitkit/Services/LightningService.swift @@ -21,8 +21,12 @@ class LightningService { var config = defaultConfig() config.storageDirPath = Env.ldkStorage.path config.logDirPath = Env.ldkStorage.path - config.network = .regtest + config.network = Env.network.ldkNetwork config.logLevel = .trace + config.anchorChannelsConfig = .init( + trustedPeersNoReserve: Env.trustedLnPeers.map({ $0.nodeId }), + perChannelReserveSats: 2000 //TODO set correctly + ) let nodeBuilder = Builder.fromConfig(config: config) nodeBuilder.setEsploraServer(esploraServerUrl: Env.esploraServerUrl) @@ -41,7 +45,11 @@ class LightningService { //TODO throw custom error return } + try node.start() + for peer in Env.trustedLnPeers { + try node.connect(nodeId: peer.nodeId, address: peer.address, persist: true) + } } func sync() throws { @@ -58,7 +66,7 @@ extension LightningService { var nodeId: String? { node?.nodeId() } var balances: BalanceDetails? { node?.listBalances() } var status: NodeStatus? { node?.status() } - var listPeers: [PeerDetails]? { node?.listPeers() } - var listChannels: [ChannelDetails]? { node?.listChannels() } - var listPayments: [PaymentDetails]? { node?.listPayments() } + var peers: [PeerDetails]? { node?.listPeers() } + var Channels: [ChannelDetails]? { node?.listChannels() } + var payments: [PaymentDetails]? { node?.listPayments() } } diff --git a/Bitkit/Services/OnChainService.swift b/Bitkit/Services/OnChainService.swift new file mode 100644 index 00000000..835d994b --- /dev/null +++ b/Bitkit/Services/OnChainService.swift @@ -0,0 +1,83 @@ +// +// OnChainService.swift +// Bitkit +// +// Created by Jason van den Berg on 2024/07/01. +// + +import Foundation +import BitcoinDevKit + +class OnChainService { + private var wallet: Wallet? + private var blockchainConfig: BlockchainConfig? + + static var shared: OnChainService = OnChainService() + + private init() {} + + func setup() throws { + //TODO maybe better as a lazy var + let esploraConfig = EsploraConfig( + baseUrl: Env.esploraServerUrl, + proxy: nil, + concurrency: nil, + stopGap: UInt64(20), + timeout: nil + ) + + blockchainConfig = BlockchainConfig.esplora(config: esploraConfig) + } + + func createWallet(mnemonic: String, passphrase: String?) throws { + let mnemonic = try Mnemonic.fromString(mnemonic: mnemonic) + let secretKey = DescriptorSecretKey( + network: Env.network.bdkNetwork, + mnemonic: mnemonic, + password: passphrase + ) + let descriptor = Descriptor.newBip86( + secretKey: secretKey, + keychain: .external, + network: Env.network.bdkNetwork + ) + let changeDescriptor = Descriptor.newBip86( + secretKey: secretKey, + keychain: .internal, + network: Env.network.bdkNetwork + ) + + //TODO save to keychain + + wallet = try Wallet( + descriptor: descriptor, + changeDescriptor: changeDescriptor, + network: Env.network.bdkNetwork, + databaseConfig: .memory //TODO use sqlite + ) + } + + func getAddress() throws -> String { + guard let wallet else { + //TODO throw custom error + return "error" + } + let addressInfo = try wallet.getAddress(addressIndex: .new) + return addressInfo.address.asString() + } + + func sync() throws { + guard let wallet, let blockchainConfig else { + //TODO throw custom error + return + } + let blockchain = try Blockchain(config: blockchainConfig) + try wallet.sync(blockchain: blockchain, progress: nil) + } +} + +//MARK: UI Helpers (Published via OnChainViewModel) +extension OnChainService { + //TODO catch errors? + var balance: Balance? { try? wallet?.getBalance() } +} diff --git a/Bitkit/ViewModels/LightningViewModel.swift b/Bitkit/ViewModels/LightningViewModel.swift index a1ba5e23..6e168f6c 100644 --- a/Bitkit/ViewModels/LightningViewModel.swift +++ b/Bitkit/ViewModels/LightningViewModel.swift @@ -11,7 +11,10 @@ import LDKNode @MainActor class LightningViewModel: ObservableObject { @Published var status: NodeStatus? - + @Published var nodeId: String? + @Published var balance: BalanceDetails? + @Published var peers: [PeerDetails]? + func start() async throws { let mnemonic = "science fatigue phone inner pipe solve acquire nothing birth slow armor flip debate gorilla select settle talk badge uphold firm video vibrant banner casual" // = generateEntropyMnemonic() let passphrase: String? = nil @@ -25,6 +28,9 @@ class LightningViewModel: ObservableObject { do { try LightningService.shared.sync() status = LightningService.shared.status + nodeId = LightningService.shared.nodeId + balance = LightningService.shared.balances + peers = LightningService.shared.peers //TODO sync everything else for the UI } catch { diff --git a/Bitkit/ViewModels/OnChainViewModel.swift b/Bitkit/ViewModels/OnChainViewModel.swift new file mode 100644 index 00000000..f5e98573 --- /dev/null +++ b/Bitkit/ViewModels/OnChainViewModel.swift @@ -0,0 +1,37 @@ +// +// OnChainViewModel.swift +// Bitkit +// +// Created by Jason van den Berg on 2024/07/01. +// + +import SwiftUI +import BitcoinDevKit + +@MainActor +class OnChainViewModel: ObservableObject { + @Published var balance: Balance? + @Published var address: String? + + func start() async throws { + let mnemonic = "science fatigue phone inner pipe solve acquire nothing birth slow armor flip debate gorilla select settle talk badge uphold firm video vibrant banner casual" // = generateEntropyMnemonic() + let passphrase: String? = nil + + try OnChainService.shared.setup() + try OnChainService.shared.createWallet(mnemonic: mnemonic, passphrase: passphrase) + await sync() + } + + func newReceiveAddress() throws { + address = try OnChainService.shared.getAddress() + } + + func sync() async { + do { + try OnChainService.shared.sync() + balance = OnChainService.shared.balance + } catch { + print("Error: \(error)") + } + } +}