diff --git a/LightningKit.xcodeproj/project.pbxproj b/LightningKit.xcodeproj/project.pbxproj index a60ad87..efe7909 100644 --- a/LightningKit.xcodeproj/project.pbxproj +++ b/LightningKit.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* Begin PBXBuildFile section */ 2FA5DC8A66918200D42EA1EE /* MessageResponseStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D6FACBD18084B3922C1B /* MessageResponseStream.swift */; }; D059940C2403C9DE0096AC17 /* Lndmobile.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D059940B2403C9DE0096AC17 /* Lndmobile.framework */; }; + D09B6F4F240CD100009A883F /* GRPC in Frameworks */ = {isa = PBXBuildFile; productRef = D3A733C923FA80A2005DAC30 /* GRPC */; }; + D09B6F56240D3332009A883F /* RpcReadyCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09B6F55240D3332009A883F /* RpcReadyCallback.swift */; }; D3A7339423FA7D94005DAC30 /* LightningKit.h in Headers */ = {isa = PBXBuildFile; fileRef = D3A7339223FA7D94005DAC30 /* LightningKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; D3A733B123FA7E98005DAC30 /* RemoteLnd.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3A7339B23FA7E98005DAC30 /* RemoteLnd.swift */; }; D3A733B223FA7E98005DAC30 /* WalletUnlocker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3A7339C23FA7E98005DAC30 /* WalletUnlocker.swift */; }; @@ -36,6 +38,8 @@ 2FA5D6FACBD18084B3922C1B /* MessageResponseStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageResponseStream.swift; sourceTree = ""; }; D059940B2403C9DE0096AC17 /* Lndmobile.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Lndmobile.framework; path = Carthage/Build/iOS/Lndmobile.framework; sourceTree = SOURCE_ROOT; }; D05C723F70B6969736B4E735 /* Pods_LightningKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LightningKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D09B6F50240CD306009A883F /* RxBlocking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RxBlocking.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D09B6F55240D3332009A883F /* RpcReadyCallback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RpcReadyCallback.swift; sourceTree = ""; }; D3A7338F23FA7D94005DAC30 /* LightningKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LightningKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D3A7339223FA7D94005DAC30 /* LightningKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LightningKit.h; sourceTree = ""; }; D3A7339323FA7D94005DAC30 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -67,6 +71,7 @@ files = ( D059940C2403C9DE0096AC17 /* Lndmobile.framework in Frameworks */, E989B9E19F21A2518A31A210 /* Pods_LightningKit.framework in Frameworks */, + D09B6F4F240CD100009A883F /* GRPC in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -161,10 +166,11 @@ D3A733AC23FA7E98005DAC30 /* LndMobileCallbacks */ = { isa = PBXGroup; children = ( + D3A733AE23FA7E98005DAC30 /* LndMobileCallbackError.swift */, D3A733AF23FA7E98005DAC30 /* VoidResponseCallback.swift */, D3A733AD23FA7E98005DAC30 /* MessageResponseCallback.swift */, 2FA5D6FACBD18084B3922C1B /* MessageResponseStream.swift */, - D3A733AE23FA7E98005DAC30 /* LndMobileCallbackError.swift */, + D09B6F55240D3332009A883F /* RpcReadyCallback.swift */, ); path = LndMobileCallbacks; sourceTree = ""; @@ -172,6 +178,7 @@ D3A733C823FA80A2005DAC30 /* Frameworks */ = { isa = PBXGroup; children = ( + D09B6F50240CD306009A883F /* RxBlocking.framework */, D05C723F70B6969736B4E735 /* Pods_LightningKit.framework */, ); name = Frameworks; @@ -207,7 +214,7 @@ ); name = LightningKit; packageProductDependencies = ( - D3A733C923FA80A2005DAC30 /* XCSwiftPackageProductDependency */, + D3A733C923FA80A2005DAC30 /* GRPC */, ); productName = LightningKit; productReference = D3A7338F23FA7D94005DAC30 /* LightningKit.framework */; @@ -238,7 +245,7 @@ ); mainGroup = D3A7338523FA7D94005DAC30; packageReferences = ( - D3A733C523FA7FD1005DAC30 /* XCRemoteSwiftPackageReference */, + D3A733C523FA7FD1005DAC30 /* XCRemoteSwiftPackageReference "grpc-swift" */, ); productRefGroup = D3A7339023FA7D94005DAC30 /* Products */; projectDirPath = ""; @@ -293,6 +300,7 @@ D3A733B723FA7E98005DAC30 /* rpc.pb.swift in Sources */, D3A733B423FA7E98005DAC30 /* ConnectivityManager.swift in Sources */, D3A733BC23FA7E98005DAC30 /* FileManager.swift in Sources */, + D09B6F56240D3332009A883F /* RpcReadyCallback.swift in Sources */, D3A733C023FA7E98005DAC30 /* LndMobileCallbackError.swift in Sources */, D3A733B923FA7E98005DAC30 /* RpcCredentials.swift in Sources */, D3A733BB23FA7E98005DAC30 /* Data.swift in Sources */, @@ -451,7 +459,6 @@ "$(PROJECT_DIR)/Carthage/Build/iOS", ); INFOPLIST_FILE = LightningKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -484,7 +491,6 @@ "$(PROJECT_DIR)/Carthage/Build/iOS", ); INFOPLIST_FILE = LightningKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -523,7 +529,7 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - D3A733C523FA7FD1005DAC30 /* XCRemoteSwiftPackageReference */ = { + D3A733C523FA7FD1005DAC30 /* XCRemoteSwiftPackageReference "grpc-swift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "git@github.com:grpc/grpc-swift.git"; requirement = { @@ -534,9 +540,9 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - D3A733C923FA80A2005DAC30 /* XCSwiftPackageProductDependency */ = { + D3A733C923FA80A2005DAC30 /* GRPC */ = { isa = XCSwiftPackageProductDependency; - package = D3A733C523FA7FD1005DAC30 /* XCRemoteSwiftPackageReference */; + package = D3A733C523FA7FD1005DAC30 /* XCRemoteSwiftPackageReference "grpc-swift" */; productName = GRPC; }; /* End XCSwiftPackageProductDependency section */ diff --git a/LightningKit/ILndNode.swift b/LightningKit/ILndNode.swift index f25ab8c..2b8b137 100644 --- a/LightningKit/ILndNode.swift +++ b/LightningKit/ILndNode.swift @@ -18,6 +18,7 @@ protocol ILndNode { var pendingChannelsSingle: Single { get } var paymentsSingle: Single { get } var transactionsSingle: Single { get } + var newSeedSingle: Single { get } func invoicesSingle(request: Lnrpc_ListInvoiceRequest) -> Single func paySingle(request: Lnrpc_SendRequest) -> Single @@ -27,4 +28,6 @@ protocol ILndNode { func openChannelSingle(request: Lnrpc_OpenChannelRequest) -> Observable func closeChannelSingle(request: Lnrpc_CloseChannelRequest) throws -> Observable func connectSingle(request: Lnrpc_ConnectPeerRequest) -> Single + + func initWalletSingle(request: Lnrpc_InitWalletRequest) -> Single } diff --git a/LightningKit/Kit.swift b/LightningKit/Kit.swift index 12b757d..bb71edf 100644 --- a/LightningKit/Kit.swift +++ b/LightningKit/Kit.swift @@ -82,6 +82,10 @@ public class Kit { lndNode.transactionsSingle } + public var newSeedSingle: Single { + lndNode.newSeedSingle + } + public func invoicesSingle(pendingOnly: Bool = false, offset: UInt64 = 0, limit: UInt64 = 1000, reversed: Bool = false) -> Single { var request = Lnrpc_ListInvoiceRequest() request.pendingOnly = pendingOnly @@ -112,13 +116,20 @@ public class Kit { return lndNode.addInvoiceSingle(invoice: invoice) } - public func unlockWalletSingle(password: Data) -> Single { + public func unlockWalletSingle(password: String) -> Single { var request = Lnrpc_UnlockWalletRequest() - request.walletPassword = password - + request.walletPassword = Data(Array(password.utf8)) + return lndNode.unlockWalletSingle(request: request) } - + + public func unlockWallet(password: String) throws { + var request = Lnrpc_UnlockWalletRequest() + request.walletPassword = Data(Array(password.utf8)) + + _ = try lndNode.unlockWalletSingle(request: request).toBlocking().first() + } + public func decodeSingle(paymentRequest: String) -> Single { var request = Lnrpc_PayReqString() request.payReq = paymentRequest @@ -180,34 +191,13 @@ public class Kit { return try lndNode.closeChannelSingle(request: request) } - // LocalLnd methods - - public func start(password: String) { - guard let localNode = lndNode as? LocalLnd else { - return - } - - localNode.startAndUnlock(password: password) - } - - public func create(password: String) -> Single<[String]> { - guard let localNode = lndNode as? LocalLnd else { - return Single.error(KitErrors.cannotInitRemoteNode) - } + public func initWalletSingle(words: [String], password: String, recoveryWindow: Int32 = 100) -> Single { + var request = Lnrpc_InitWalletRequest() + request.cipherSeedMnemonic = words + request.walletPassword = Data(Array(password.utf8)) + request.recoveryWindow = recoveryWindow - return localNode.start().flatMap { - localNode.createWalletSingle(password: password) - } - } - - public func restore(words: [String], password: String) -> Single { - guard let localNode = lndNode as? LocalLnd else { - return Single.error(KitErrors.cannotInitRemoteNode) - } - - return localNode.start().flatMap { - localNode.restoreWalletSingle(words: words, password: password) - } + return lndNode.initWalletSingle(request: request) } // Private methods @@ -244,8 +234,15 @@ public extension Kit { } static func local() throws -> Kit { - let localLnd = LocalLnd(filesDir: try FileManager.default.walletDirectory().path) + let localLnd = try LocalLnd(filesDir: try FileManager.default.walletDirectory().path) return Kit(lndNode: localLnd) } + + static func clearLocalNodeData() throws { + let fileManager = FileManager.default + let lndDirectoryPath = try fileManager.walletDirectory().path + + try fileManager.removeItem(atPath: lndDirectoryPath) + } } diff --git a/LightningKit/Local/LndMobileCallbacks/RpcReadyCallback.swift b/LightningKit/Local/LndMobileCallbacks/RpcReadyCallback.swift new file mode 100644 index 0000000..bfffd6e --- /dev/null +++ b/LightningKit/Local/LndMobileCallbacks/RpcReadyCallback.swift @@ -0,0 +1,22 @@ +import Lndmobile + +class RpcReadyCallback: NSObject, LndmobileCallbackProtocol { + private weak var delegate: RpcReadyCallbackDelegate? + + init(_ delegate: RpcReadyCallbackDelegate?) { + self.delegate = delegate + } + + func onError(_ error: Error?) { + delegate?.RpcServerStartFailed(error: error ?? LndMobileCallbackError.unknownError) + } + + func onResponse(_ response: Data?) { + delegate?.rpcReady() + } +} + +protocol RpcReadyCallbackDelegate: AnyObject { + func RpcServerStartFailed(error: Error) + func rpcReady() +} diff --git a/LightningKit/Local/LocalLnd.swift b/LightningKit/Local/LocalLnd.swift index 95c5195..78606e2 100644 --- a/LightningKit/Local/LocalLnd.swift +++ b/LightningKit/Local/LocalLnd.swift @@ -1,5 +1,6 @@ import Lndmobile import RxSwift +import RxBlocking import SwiftProtobuf class LocalLnd: ILndNode { @@ -116,16 +117,12 @@ class LocalLnd: ILndNode { var transactionsSingle: Single { Single.create { emitter in LndmobileGetTransactions(try! Lnrpc_GetTransactionsRequest().serializedData(), MessageResponseCallback(emitter: emitter)) - + return Disposables.create() } } - init(filesDir: String) { - self.filesDir = filesDir - } - - private func genSeedSingle() -> Single { + var newSeedSingle: Single { Single.create { emitter in LndmobileGenSeed(try! Lnrpc_GenSeedRequest().serializedData(), MessageResponseCallback(emitter: emitter)) @@ -133,6 +130,19 @@ class LocalLnd: ILndNode { } } + init(filesDir: String) throws { + self.filesDir = filesDir + + // start Lndmobile daemon + let args = "--lnddir=\(filesDir) --bitcoin.active --bitcoin.mainnet --debuglevel=warn --no-macaroons --nolisten --norest --bitcoin.node=neutrino --routing.assumechanvalid --debuglevel=info" + + _ = try Single.create { [weak self] emitter in + LndmobileStart(args, VoidResponseCallback(emitter: emitter), RpcReadyCallback(self)) + + return Disposables.create() + }.toBlocking().first() + } + func scheduleStatusUpdates() { Observable.interval(.seconds(3), scheduler: SerialDispatchQueueScheduler(qos: .background)) .flatMap { [weak self] _ -> Observable in @@ -197,7 +207,7 @@ class LocalLnd: ILndNode { func closeChannelSingle(request: Lnrpc_CloseChannelRequest) throws -> Observable { Observable.create { emitter in LndmobileCloseChannel(try! request.serializedData(), MessageResponseStream(emitter: emitter)) - + return Disposables.create() } } @@ -210,65 +220,9 @@ class LocalLnd: ILndNode { } } - func start() -> Single { - let args = "--lnddir=\(filesDir) --bitcoin.active --bitcoin.mainnet --debuglevel=warn --no-macaroons --nolisten --norest --bitcoin.node=neutrino --routing.assumechanvalid --debuglevel=info" - - return Single.create { emitter in - LndmobileStart(args, VoidResponseCallback(emitter: emitter), VoidResponseCallback(emitter: nil)) - - return Disposables.create() - } - } - - func startAndUnlock(password: String) { - start() - .flatMap { [weak self] _ in - var request = Lnrpc_UnlockWalletRequest() - request.walletPassword = Data(Array(password.utf8)) - - return self?.unlockWalletSingle(request: request) ?? Single.error(NodeNotRetained()) - } - .subscribe( - onSuccess: { [weak self] in self?.scheduleStatusUpdates() }, - onError: { [weak self] in self?.status = .error($0) } - ) - .disposed(by: disposeBag) - } - - func createWalletSingle(password: String) -> Single<[String]> { - genSeedSingle() - .flatMap { [weak self] genSeedResponse in - guard let node = self else { - return Single<[String]>.error(NodeNotRetained()) - } - - return node.initWalletSingle(mnemonicWords: genSeedResponse.cipherSeedMnemonic, password: password, recoveryWindow: 0) - .do( - onSuccess: { [weak self] _ in self?.scheduleStatusUpdates() }, - onError: { [weak self] in self?.status = .error($0) } - ) - .map { _ in - genSeedResponse.cipherSeedMnemonic - } - } - } - - func restoreWalletSingle(words: [String], password: String) -> Single { - initWalletSingle(mnemonicWords: words, password: password) - .do( - onSuccess: { [weak self] _ in self?.scheduleStatusUpdates() }, - onError: { [weak self] in self?.status = .error($0) } - ) - } - - func initWalletSingle(mnemonicWords: [String], password: String, recoveryWindow: Int32 = 100) -> Single { + func initWalletSingle(request: Lnrpc_InitWalletRequest) -> Single { Single.create { emitter in - var msg = Lnrpc_InitWalletRequest() - msg.cipherSeedMnemonic = mnemonicWords - msg.walletPassword = Data(Array(password.utf8)) - msg.recoveryWindow = recoveryWindow - - LndmobileInitWallet(try! msg.serializedData(), VoidResponseCallback(emitter: emitter)) + LndmobileInitWallet(try! request.serializedData(), VoidResponseCallback(emitter: emitter)) return Disposables.create() } @@ -284,3 +238,13 @@ class LocalLnd: ILndNode { } } } + +extension LocalLnd: RpcReadyCallbackDelegate { + func RpcServerStartFailed(error: Error) { + status = .error(error) + } + + func rpcReady() { + scheduleStatusUpdates() + } +} diff --git a/LightningKit/Remote/RemoteLnd.swift b/LightningKit/Remote/RemoteLnd.swift index edaa874..902070e 100644 --- a/LightningKit/Remote/RemoteLnd.swift +++ b/LightningKit/Remote/RemoteLnd.swift @@ -96,6 +96,12 @@ class RemoteLnd: ILndNode { } } + var newSeedSingle: Single { + connection.walletUnlockerUnaryCall() { + $0.genSeed(Lnrpc_GenSeedRequest()).response + } + } + init(rpcCredentials: RpcCredentials) throws { connection = try LndNioConnection(rpcCredentials: rpcCredentials) walletUnlocker = WalletUnlocker(connection: connection) @@ -164,6 +170,12 @@ class RemoteLnd: ILndNode { } } + func initWalletSingle(request: Lnrpc_InitWalletRequest) -> Single { + connection.walletUnlockerUnaryCall() { + $0.initWallet(request).response + }.map { _ in Void() } + } + func validateAsync() -> Single { fetchStatusSingle() .flatMap { diff --git a/LightningWallet/Modules/UnlockRemoteWallet/UnlockRemoteWalletInteractor.swift b/LightningWallet/Modules/UnlockRemoteWallet/UnlockRemoteWalletInteractor.swift index 9065227..e008e59 100644 --- a/LightningWallet/Modules/UnlockRemoteWallet/UnlockRemoteWalletInteractor.swift +++ b/LightningWallet/Modules/UnlockRemoteWallet/UnlockRemoteWalletInteractor.swift @@ -17,7 +17,7 @@ class UnlockRemoteWalletInteractor { extension UnlockRemoteWalletInteractor: IUnlockRemoteWalletInteractor { func unlockWallet(password: String) { - lightningKit.unlockWalletSingle(password: Data(Array(password.utf8))) + lightningKit.unlockWalletSingle(password: password) .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) .observeOn(MainScheduler.instance) .subscribe(onSuccess: { [weak self] in diff --git a/Podfile b/Podfile index 3189535..19aecf7 100644 --- a/Podfile +++ b/Podfile @@ -32,6 +32,7 @@ target 'LightningWallet' do pod 'KeychainAccess' pod 'RxSwift' + pod 'RxBlocking' pod 'SnapKit' end @@ -39,4 +40,5 @@ target 'LightningKit' do project 'LightningKit' pod 'RxSwift' + pod 'RxBlocking' end diff --git a/Podfile.lock b/Podfile.lock index 15dc858..a2a185e 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -24,6 +24,8 @@ PODS: - StorageKit.swift (~> 1.0) - ThemeKit.swift (~> 1.0) - UIExtensions.swift (~> 1.1) + - RxBlocking (5.0.1): + - RxSwift (~> 5) - RxSwift (5.0.1) - SectionsTableView.swift (1.1): - SnapKit (~> 5.0) @@ -48,6 +50,7 @@ DEPENDENCIES: - LanguageKit.swift (from `https://github.com/horizontalsystems/component-kit-ios/`) - ObjectMapper - PinKit.swift (from `https://github.com/horizontalsystems/component-kit-ios/`) + - RxBlocking - RxSwift - SectionsTableView.swift - SnapKit @@ -61,6 +64,7 @@ SPEC REPOS: - GRDB.swift - KeychainAccess - ObjectMapper + - RxBlocking - RxSwift - SectionsTableView.swift - SnapKit @@ -113,6 +117,7 @@ SPEC CHECKSUMS: LanguageKit.swift: f1105c75fe9d04f482d2ab55e48eb91ed008abef ObjectMapper: 70187b8941977c62ccfb423caf6b50be405cabf0 PinKit.swift: 1acbf82ce5114972f1eb9a5a0c1b808dc08ad12e + RxBlocking: c5e090f42d046df8b0e7752742510f349963710f RxSwift: e2dc62b366a3adf6a0be44ba9f405efd4c94e0c4 SectionsTableView.swift: 91f2235b63405f938621b7df040149d7c65b355d SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb @@ -120,6 +125,6 @@ SPEC CHECKSUMS: ThemeKit.swift: ab5c081b10f6a80149d37751f3ee98f1292bf497 UIExtensions.swift: b3852a339a81c730d98b3342ae0275e807703782 -PODFILE CHECKSUM: d9058190292fc1f957185e649c4ed6b9a6b608b7 +PODFILE CHECKSUM: b8c6d90bca746a75637a0c1a99a437a459094bba COCOAPODS: 1.8.4