diff --git a/FullyNoded.xcodeproj/project.pbxproj b/FullyNoded.xcodeproj/project.pbxproj index 1c041b3a..0f42fa0a 100644 --- a/FullyNoded.xcodeproj/project.pbxproj +++ b/FullyNoded.xcodeproj/project.pbxproj @@ -25,6 +25,8 @@ 0A1F110926A2783000BE2707 /* Asset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A1F110426A2783000BE2707 /* Asset.swift */; }; 0A1F110C26A278C100BE2707 /* ModelObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A1F110B26A278C100BE2707 /* ModelObject.swift */; }; 0A1F111026A2791B00BE2707 /* ModelObjectType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A1F110F26A2791B00BE2707 /* ModelObjectType.swift */; }; + 0A212D8B26C66A9600BF6FD4 /* OnchainUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A212D8A26C66A9600BF6FD4 /* OnchainUtils.swift */; }; + 0A212D8D26C66B1800BF6FD4 /* DescriptorInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A212D8C26C66B1800BF6FD4 /* DescriptorInfo.swift */; }; 0A21442C26B4282300123EAC /* PromptForAuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21442B26B4282300123EAC /* PromptForAuthViewController.swift */; }; 0A3013D8266B361E004DA35C /* LndRpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3013D7266B361E004DA35C /* LndRpc.swift */; }; 0A590357269875CD00510626 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A590356269875CB00510626 /* Extensions.swift */; }; @@ -137,7 +139,6 @@ D09A330724A982F3009FA0B2 /* FullyNodedWalletsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A330624A982F2009FA0B2 /* FullyNodedWalletsViewController.swift */; }; D09A330924A98EE1009FA0B2 /* WalletDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A330824A98EE1009FA0B2 /* WalletDetailViewController.swift */; }; D09A330D24A9B10E009FA0B2 /* BIP39WordList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A330C24A9B10E009FA0B2 /* BIP39WordList.swift */; }; - D09A330F24A9B853009FA0B2 /* RecoveryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A330E24A9B853009FA0B2 /* RecoveryViewController.swift */; }; D09A331124AC4BF6009FA0B2 /* DescriptorParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A331024AC4BF6009FA0B2 /* DescriptorParser.swift */; }; D09A331324AC4C93009FA0B2 /* Descriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A331224AC4C93009FA0B2 /* Descriptor.swift */; }; D09F9E4C24B0673C006B588C /* SignersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09F9E4B24B0673C006B588C /* SignersViewController.swift */; }; @@ -227,6 +228,8 @@ 0A1F110426A2783000BE2707 /* Asset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Asset.swift; sourceTree = ""; }; 0A1F110B26A278C100BE2707 /* ModelObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelObject.swift; sourceTree = ""; }; 0A1F110F26A2791B00BE2707 /* ModelObjectType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelObjectType.swift; sourceTree = ""; }; + 0A212D8A26C66A9600BF6FD4 /* OnchainUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnchainUtils.swift; sourceTree = ""; }; + 0A212D8C26C66B1800BF6FD4 /* DescriptorInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DescriptorInfo.swift; sourceTree = ""; }; 0A21442B26B4282300123EAC /* PromptForAuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromptForAuthViewController.swift; sourceTree = ""; }; 0A3013D7266B361E004DA35C /* LndRpc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LndRpc.swift; sourceTree = ""; }; 0A590356269875CB00510626 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; @@ -342,7 +345,6 @@ D09A330624A982F2009FA0B2 /* FullyNodedWalletsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullyNodedWalletsViewController.swift; sourceTree = ""; }; D09A330824A98EE1009FA0B2 /* WalletDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletDetailViewController.swift; sourceTree = ""; }; D09A330C24A9B10E009FA0B2 /* BIP39WordList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BIP39WordList.swift; sourceTree = ""; }; - D09A330E24A9B853009FA0B2 /* RecoveryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryViewController.swift; sourceTree = ""; }; D09A331024AC4BF6009FA0B2 /* DescriptorParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DescriptorParser.swift; sourceTree = ""; }; D09A331224AC4C93009FA0B2 /* Descriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Descriptor.swift; sourceTree = ""; }; D09F9E4B24B0673C006B588C /* SignersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignersViewController.swift; sourceTree = ""; }; @@ -642,6 +644,7 @@ 0A3013D7266B361E004DA35C /* LndRpc.swift */, 0A9834BE26BBC93F00BAB74E /* CoreDataiCloud.swift */, 0A9834C326BBCCB300BAB74E /* Backup.swift */, + 0A212D8A26C66A9600BF6FD4 /* OnchainUtils.swift */, ); path = Helpers; sourceTree = ""; @@ -715,6 +718,7 @@ D093E2A625A59B9C0038B59A /* TransactionStruct.swift */, 0A59035C269B0CA700510626 /* InvoiceStruct.swift */, 0A77138526AE5E85005CC23D /* WalletInfo.swift */, + 0A212D8C26C66B1800BF6FD4 /* DescriptorInfo.swift */, ); path = Structs; sourceTree = ""; @@ -919,7 +923,6 @@ D09A330024A848DA009FA0B2 /* SeedDisplayerViewController.swift */, D09A330624A982F2009FA0B2 /* FullyNodedWalletsViewController.swift */, D09A330824A98EE1009FA0B2 /* WalletDetailViewController.swift */, - D09A330E24A9B853009FA0B2 /* RecoveryViewController.swift */, D09F9E4B24B0673C006B588C /* SignersViewController.swift */, D09F9E4F24B1BAC3006B588C /* SignerDetailViewController.swift */, D09F9E5124B1F8DE006B588C /* AddSignerViewController.swift */, @@ -1190,6 +1193,7 @@ D0DBC7062501CEFE00F6787C /* VerifyTransactionViewController.swift in Sources */, D0855FCA22B0CEA800DBBFC2 /* CreatePSBT.swift in Sources */, 0A1F110726A2783000BE2707 /* KeyType.swift in Sources */, + 0A212D8B26C66A9600BF6FD4 /* OnchainUtils.swift in Sources */, D02094F3225936D2002E8E7A /* ConnectingView.swift in Sources */, D09F9E5224B1F8DE006B588C /* AddSignerViewController.swift in Sources */, D0196DDB21B5AC5F0081E0AB /* KeyPad.swift in Sources */, @@ -1277,7 +1281,6 @@ D0560EE92277F75900C3E909 /* UTXOViewController.swift in Sources */, D0257E6524D6C5F5000EB854 /* LightningRPC.swift in Sources */, D00F851D2494A616008D7CD3 /* TorAuthViewController.swift in Sources */, - D09A330F24A9B853009FA0B2 /* RecoveryViewController.swift in Sources */, D0C20EDA24A6E46E00C62AA4 /* Uptime.swift in Sources */, D09A330124A848DA009FA0B2 /* SeedDisplayerViewController.swift in Sources */, D06D788F25B817E100769157 /* Lifehash.swift in Sources */, @@ -1292,6 +1295,7 @@ D0EDDF1124972CEF00657931 /* InvoiceSettingsViewController.swift in Sources */, D069051F24EA6C1F007F939D /* LightningPeersViewController.swift in Sources */, 0A1F111026A2791B00BE2707 /* ModelObjectType.swift in Sources */, + 0A212D8D26C66B1800BF6FD4 /* DescriptorInfo.swift in Sources */, D084960422EBCD3B003FE8EB /* GetInfoViewController.swift in Sources */, D073E16E22558CDE0012A28C /* CoreDataService.swift in Sources */, D0E54DD525207A1C00BD859E /* UTXOs.swift in Sources */, @@ -1457,7 +1461,7 @@ D00B9A3A2111427200E8B95A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-4"; + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-6"; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = FullyNoded/FullyNoded.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; @@ -1494,7 +1498,7 @@ D00B9A3B2111427200E8B95A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-4"; + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-6"; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = FullyNoded/FullyNodedRelease.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/1024-1.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/1024-1.png deleted file mode 100644 index 27782062..00000000 Binary files a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/1024-1.png and /dev/null differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/1024_fully noded logo.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/1024_fully noded logo.png new file mode 100644 index 00000000..f9f27fb4 Binary files /dev/null and b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/1024_fully noded logo.png differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/128.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/128.png deleted file mode 100644 index 08a6152c..00000000 Binary files a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/128.png and /dev/null differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/128_fully noded logo.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/128_fully noded logo.png new file mode 100644 index 00000000..3d824de9 Binary files /dev/null and b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/128_fully noded logo.png differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/16.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/16.png deleted file mode 100644 index 417f612d..00000000 Binary files a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/16.png and /dev/null differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/16_fully noded logo copy.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/16_fully noded logo copy.png new file mode 100644 index 00000000..a85b69d5 Binary files /dev/null and b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/16_fully noded logo copy.png differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/256-1.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/256-1.png deleted file mode 100644 index a4edde42..00000000 Binary files a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/256-1.png and /dev/null differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/256.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/256.png deleted file mode 100644 index a4edde42..00000000 Binary files a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/256.png and /dev/null differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/256_fully noded logo copy 5-1.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/256_fully noded logo copy 5-1.png new file mode 100644 index 00000000..324fae5b Binary files /dev/null and b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/256_fully noded logo copy 5-1.png differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/256_fully noded logo copy 5.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/256_fully noded logo copy 5.png new file mode 100644 index 00000000..324fae5b Binary files /dev/null and b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/256_fully noded logo copy 5.png differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/32-1.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/32-1.png deleted file mode 100644 index 594e020f..00000000 Binary files a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/32-1.png and /dev/null differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/32.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/32.png deleted file mode 100644 index 594e020f..00000000 Binary files a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/32.png and /dev/null differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/32_fully noded logo copy-1.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/32_fully noded logo copy-1.png new file mode 100644 index 00000000..efbd2964 Binary files /dev/null and b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/32_fully noded logo copy-1.png differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/32_fully noded logo copy.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/32_fully noded logo copy.png new file mode 100644 index 00000000..efbd2964 Binary files /dev/null and b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/32_fully noded logo copy.png differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/512-1.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/512-1.png deleted file mode 100644 index 5eb0981f..00000000 Binary files a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/512-1.png and /dev/null differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/512.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/512.png deleted file mode 100644 index 5eb0981f..00000000 Binary files a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/512.png and /dev/null differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/512_fully noded logo-1.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/512_fully noded logo-1.png new file mode 100644 index 00000000..79364f11 Binary files /dev/null and b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/512_fully noded logo-1.png differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/512_fully noded logo.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/512_fully noded logo.png new file mode 100644 index 00000000..79364f11 Binary files /dev/null and b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/512_fully noded logo.png differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/64.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/64.png deleted file mode 100644 index 9b919a84..00000000 Binary files a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/64.png and /dev/null differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/64_fully noded logo.png b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/64_fully noded logo.png new file mode 100644 index 00000000..aac0306c Binary files /dev/null and b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/64_fully noded logo.png differ diff --git a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/Contents.json b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/Contents.json index d82a0c9f..7facd999 100644 --- a/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/Contents.json +++ b/FullyNoded/Assets.xcassets/AppIcon-6.appiconset/Contents.json @@ -151,61 +151,61 @@ "size" : "1024x1024" }, { - "filename" : "16.png", + "filename" : "16_fully noded logo copy.png", "idiom" : "mac", "scale" : "1x", "size" : "16x16" }, { - "filename" : "32.png", + "filename" : "32_fully noded logo copy.png", "idiom" : "mac", "scale" : "2x", "size" : "16x16" }, { - "filename" : "32-1.png", + "filename" : "32_fully noded logo copy-1.png", "idiom" : "mac", "scale" : "1x", "size" : "32x32" }, { - "filename" : "64.png", + "filename" : "64_fully noded logo.png", "idiom" : "mac", "scale" : "2x", "size" : "32x32" }, { - "filename" : "128.png", + "filename" : "128_fully noded logo.png", "idiom" : "mac", "scale" : "1x", "size" : "128x128" }, { - "filename" : "256-1.png", + "filename" : "256_fully noded logo copy 5.png", "idiom" : "mac", "scale" : "2x", "size" : "128x128" }, { - "filename" : "256.png", + "filename" : "256_fully noded logo copy 5-1.png", "idiom" : "mac", "scale" : "1x", "size" : "256x256" }, { - "filename" : "512-1.png", + "filename" : "512_fully noded logo.png", "idiom" : "mac", "scale" : "2x", "size" : "256x256" }, { - "filename" : "512.png", + "filename" : "512_fully noded logo-1.png", "idiom" : "mac", "scale" : "1x", "size" : "512x512" }, { - "filename" : "1024-1.png", + "filename" : "1024_fully noded logo.png", "idiom" : "mac", "scale" : "2x", "size" : "512x512" diff --git a/FullyNoded/Helpers/OnchainUtils.swift b/FullyNoded/Helpers/OnchainUtils.swift new file mode 100644 index 00000000..c7d83d5b --- /dev/null +++ b/FullyNoded/Helpers/OnchainUtils.swift @@ -0,0 +1,111 @@ +// +// OnchainUtils.swift +// FullyNoded +// +// Created by Peter Denton on 8/13/21. +// Copyright © 2021 Fontaine. All rights reserved. +// + +import Foundation + +class OnchainUtils { + static func getDescriptorInfo(_ desc: String, completion: @escaping ((descriptorInfo: DescriptorInfo?, message: String?)) -> Void) { + Reducer.makeCommand(command: .getdescriptorinfo, param: "\"\(desc)\"") { (response, message) in + guard let response = response as? [String:Any] else { + completion((nil, message ?? "Unknown error.")) + return + } + + completion((DescriptorInfo(response), nil)) + } + } + + static func importDescriptors(_ param: String, completion: @escaping ((imported: Bool, message: String?)) -> Void) { + Reducer.makeCommand(command: .importdescriptors, param: param) { (response, message) in + guard let responseArray = response as? [[String:Any]] else { + completion((false, "Error importing descriptors: \(message ?? "unknown error")")) + return + } + + var warnings:String? + + for (i, response) in responseArray.enumerated() { + var errorMessage = "" + + guard let success = response["success"] as? Bool, success else { + if let error = response["error"] as? [String:Any], let messageCheck = error["message"] as? String { + errorMessage = "Error importing descriptors: \(messageCheck)" + } + + completion((false, errorMessage)) + return + } + + if let warningsCheck = response["warnings"] as? [String] { + warnings = warningsCheck.description + } + + if i + 1 == responseArray.count { + completion((true, warnings)) + } + } + } + } + + static func importMulti(_ param: String, completion: @escaping ((imported: Bool, message: String?)) -> Void) { + Reducer.makeCommand(command: .importmulti, param: param) { (response, errorDescription) in + guard let result = response as? NSArray, result.count > 0, + let dict = result[0] as? NSDictionary, + let success = dict["success"] as? Bool, + success else { + completion((false, errorDescription ?? "unknown error importing your keys")) + return + } + + completion((success, nil)) + } + } + + static func rescan(completion: @escaping ((started: Bool, message: String?)) -> Void) { + OnchainUtils.getBlockchainInfo { (blockchainInfo, message) in + guard let blockchainInfo = blockchainInfo else { + completion((false, message)) + return + } + + guard blockchainInfo.pruned else { + OnchainUtils.rescanNow(from: "0") { (started, message) in + completion((started, message)) + } + + return + } + + OnchainUtils.rescanNow(from: "\(blockchainInfo.pruneheight)") { (started, message) in + completion((started, message)) + } + } + } + + static func getBlockchainInfo(completion: @escaping ((blockchainInfo: BlockchainInfo?, message: String?)) -> Void) { + Reducer.makeCommand(command: .getblockchaininfo, param: "") { (response, errorMessage) in + guard let dict = response as? [String:Any] else { + completion((nil, errorMessage)) + return + } + + completion((BlockchainInfo(dict), errorMessage)) + } + } + + static func rescanNow(from: String, completion: @escaping ((started: Bool, message: String?)) -> Void) { + Reducer.makeCommand(command: .rescanblockchain, param: "0") { (_, message) in + if let message = message, message.contains("Wallet is currently rescanning. Abort existing rescan or wait.") { + completion((true, nil)) + } else { + completion((true, message)) + } + } + } + +} diff --git a/FullyNoded/Structs/BlockchainInfo.swift b/FullyNoded/Structs/BlockchainInfo.swift index e8f8c6c7..dcb8b5fc 100644 --- a/FullyNoded/Structs/BlockchainInfo.swift +++ b/FullyNoded/Structs/BlockchainInfo.swift @@ -17,8 +17,9 @@ public struct BlockchainInfo: CustomStringConvertible { let progress:String let pruned:Bool let actualProgress:Double - init(dictionary: [String: Any]) { - + let pruneheight:Int + + init(_ dictionary: [String: Any]) { self.network = dictionary["chain"] as? String ?? "" self.blockheight = dictionary["blocks"] as? Int ?? 0 self.difficulty = dictionary["difficulty"] as? String ?? "" @@ -26,7 +27,7 @@ public struct BlockchainInfo: CustomStringConvertible { self.progress = dictionary["progress"] as? String ?? "" self.pruned = dictionary["pruned"] as? Bool ?? false self.actualProgress = dictionary["actualProgress"] as? Double ?? 0.0 - + self.pruneheight = dictionary["pruneheight"] as? Int ?? 0 } public var description: String { diff --git a/FullyNoded/Structs/DescriptorInfo.swift b/FullyNoded/Structs/DescriptorInfo.swift new file mode 100644 index 00000000..6720f11e --- /dev/null +++ b/FullyNoded/Structs/DescriptorInfo.swift @@ -0,0 +1,46 @@ +// +// DescriptorInfo.swift +// FullyNoded +// +// Created by Peter Denton on 8/13/21. +// Copyright © 2021 Fontaine. All rights reserved. +// + +import Foundation + +public struct DescriptorInfo: CustomStringConvertible { + + /* + { (json object) + "descriptor" : "str", (string) The descriptor in canonical form, without private keys + "checksum" : "str", (string) The checksum for the input descriptor + "isrange" : true|false, (boolean) Whether the descriptor is ranged + "issolvable" : true|false, (boolean) Whether the descriptor is solvable + "hasprivatekeys" : true|false (boolean) Whether the input descriptor contained at least one private key + } + */ + + let checksum: String + let hasprivatekeys: Bool + let issolvable: Bool + let isrange: Bool + let descriptor: String + + init(_ dictionary: [String: Any]) { + hasprivatekeys = dictionary["hasprivatekeys"] as! Bool + checksum = dictionary["checksum"] as! String + + if hasprivatekeys { + descriptor = (dictionary["descriptor"] as! String) + "#" + checksum + } else { + descriptor = (dictionary["descriptor"] as! String) + } + + issolvable = dictionary["issolvable"] as! Bool + isrange = dictionary["isrange"] as! Bool + } + + public var description: String { + return "" + } +} diff --git a/FullyNoded/View Controllers/Home/Home Screen/MainMenuViewController.swift b/FullyNoded/View Controllers/Home/Home Screen/MainMenuViewController.swift index 7975eb45..267b6deb 100644 --- a/FullyNoded/View Controllers/Home/Home Screen/MainMenuViewController.swift +++ b/FullyNoded/View Controllers/Home/Home Screen/MainMenuViewController.swift @@ -526,7 +526,7 @@ class MainMenuViewController: UIViewController { DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.blockchainInfo = BlockchainInfo(dictionary: response) + self.blockchainInfo = BlockchainInfo(response) self.mainMenu.reloadSections(IndexSet(arrayLiteral: 0, 3, 5, 7, 8, 9), with: .fade) self.getPeerInfo() } diff --git a/FullyNoded/View Controllers/Home/Incoming/Import/Import a Key/ImportPrivKeyViewController.swift b/FullyNoded/View Controllers/Home/Incoming/Import/Import a Key/ImportPrivKeyViewController.swift index 595a0979..67f31d8c 100644 --- a/FullyNoded/View Controllers/Home/Incoming/Import/Import a Key/ImportPrivKeyViewController.swift +++ b/FullyNoded/View Controllers/Home/Incoming/Import/Import a Key/ImportPrivKeyViewController.swift @@ -65,7 +65,6 @@ class ImportPrivKeyViewController: UIViewController, UITextFieldDelegate { } func getValues() { - //To do: create struct for import dict let str = ImportStruct(dictionary: dict) label = str.label } @@ -79,7 +78,7 @@ class ImportPrivKeyViewController: UIViewController, UITextFieldDelegate { self.connectingView.removeConnectingView() displayAlert(viewController: self, isError: true, - message: "Invalid key!") + message: "Invalid key.") } } @@ -100,8 +99,7 @@ class ImportPrivKeyViewController: UIViewController, UITextFieldDelegate { } let param = "\"\(key)\", \"\(label)\", false" - - self.executeNodeCommand(method: .importprivkey, param: param) + self.importPrivKey(param: param) case _ where prefix.hasPrefix("1"), _ where prefix.hasPrefix("3"), @@ -116,12 +114,19 @@ class ImportPrivKeyViewController: UIViewController, UITextFieldDelegate { self.connectingView.addConnectingView(vc: self, description: "Importing Address") } - let param = "[{ \"scriptPubKey\": { \"address\": \"\(key)\" }, \"label\": \"\(label)\", \"timestamp\": \"now\", \"watchonly\": true, \"keypool\": false, \"internal\": false }], ''{\"rescan\": false}''" - - isAddress = true - - self.executeNodeCommand(method: .importmulti, param: param) - + activeWallet { wallet in + guard let wallet = wallet else { + showAlert(vc: self, title: "", message: "Only available for FN wallets.") + return + } + + if wallet.type == WalletType.descriptor.stringValue { + self.importDescriptor(key: key) + } else { + self.importDescriptor(key: key) + } + } + default: showError() } @@ -131,76 +136,66 @@ class ImportPrivKeyViewController: UIViewController, UITextFieldDelegate { } } - private func triggerRescan() { - connectingView.addConnectingView(vc: self, description: "starting rescan...") + private func importmulti(key: String) { + let param = "[{ \"scriptPubKey\": { \"address\": \"\(key)\" }, \"label\": \"\(self.label)\", \"timestamp\": \"now\", \"watchonly\": true, \"keypool\": false, \"internal\": false }], ''{\"rescan\": false}''" - Reducer.makeCommand(command: .getblockchaininfo, param: "") { [weak self] (response, errorMessage) in - guard let self = self else { return } - - guard let dict = response as? NSDictionary, let pruned = dict["pruned"] as? Bool else { + OnchainUtils.importMulti(param) { (imported, message) in + if imported { + self.triggerRescan() + } else { self.connectingView.removeConnectingView() - displayAlert(viewController: self, isError: true, message: "Error checking pruned status: \(errorMessage ?? "unknown")") - return + showAlert(vc: self, title: "", message: message ?? "unknown error importmulti") } - - guard pruned else { - self.rescanFrom(0) - return + } + } + + private func importDescriptor(key: String) { + OnchainUtils.getDescriptorInfo("addr(\(key))") { (descriptorInfo, message) in + if let message = message, message != "" { + self.connectingView.removeConnectingView() + showAlert(vc: self, title: "", message: message) } - guard let pruneheight = dict["pruneheight"] as? Int else { + guard let descriptorInfo = descriptorInfo else { self.connectingView.removeConnectingView() - displayAlert(viewController: self, isError: true, message: "Error checking prune height: \(errorMessage ?? "unknown")") + showAlert(vc: self, title: "", message: "Missing values.") + return } - self.rescanFrom(pruneheight) + let param = "[{\"desc\": \"\(descriptorInfo.descriptor)\", \"active\": false, \"timestamp\": \"now\", \"internal\": false, \"label\": \"\(self.label)\"}]" + + OnchainUtils.importDescriptors(param) { (imported, message) in + self.connectingView.removeConnectingView() + + if imported { + showAlert(vc: self, title: "Imported ✓", message: message ?? "") + } else { + showAlert(vc: self, title: "Import failed.", message: message ?? "") + } + } } } - private func rescanFrom(_ height: Int) { - Reducer.makeCommand(command: .rescanblockchain, param: "\(height)") { (_, _) in } + private func triggerRescan() { + connectingView.addConnectingView(vc: self, description: "starting rescan...") - DispatchQueue.main.async { - self.navigationController?.popToRootViewController(animated: true) + OnchainUtils.rescan { (started, message) in + self.connectingView.removeConnectingView() + + if started { + showAlert(vc: self, title: "", message: message ?? "Rescanning blockchain to look up historic transactions and balances.") + } else { + showAlert(vc: self, title: "", message: message ?? "Rescan failed...") + } } - - showAlert(vc: self, title: "Key swept!", message: "Your node is rescanning the blockchain to detect historic transactions, your balance will not show until this process completes. It can take up to an hour for a non pruned node.") } - func executeNodeCommand(method: BTC_CLI_COMMAND, param: String) { - Reducer.makeCommand(command: method, param: param) { [unowned vc = self] (response, errorMessage) in - vc.connectingView.removeConnectingView() + private func importPrivKey(param: String) { + Reducer.makeCommand(command: .importprivkey, param: param) { (response, errorMessage) in + self.connectingView.removeConnectingView() if errorMessage == nil { - switch method { - case .importprivkey: - self.triggerRescan() - - case .importmulti: - if let result = response as? NSArray { - let success = (result[0] as! NSDictionary)["success"] as! Bool - if success { - self.triggerRescan() - } else { - let error = ((result[0] as! NSDictionary)["error"] as! NSDictionary)["message"] as! String - displayAlert(viewController: self, isError: true, message: error) - } - if let warnings = (result[0] as! NSDictionary)["warnings"] as? NSArray { - if warnings.count > 0 { - for warning in warnings { - let warn = warning as! String - DispatchQueue.main.async { [unowned vc = self] in - let alert = UIAlertController(title: "Warning", message: warn, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - vc.present(alert, animated: true, completion: nil) - } - } - } - } - } - default: - break - } + self.triggerRescan() } else { DispatchQueue.main.async { guard var errorMess = errorMessage else { return } diff --git a/FullyNoded/View Controllers/Home/Outgoing/UTXO's/UTXOViewController.swift b/FullyNoded/View Controllers/Home/Outgoing/UTXO's/UTXOViewController.swift index a352703d..df6430a0 100644 --- a/FullyNoded/View Controllers/Home/Outgoing/UTXO's/UTXOViewController.swift +++ b/FullyNoded/View Controllers/Home/Outgoing/UTXO's/UTXOViewController.swift @@ -242,19 +242,13 @@ class UTXOViewController: UIViewController, UITextFieldDelegate, UINavigationCon } private func importmulti(param: String, utxo: UtxosStruct, label: String) { - Reducer.makeCommand(command: .importmulti, param: param) { [weak self] (response, errorMessage) in - guard let self = self else { return } - - guard let result = response as? NSArray, - let dict = result[0] as? NSDictionary, - let success = dict["success"] as? Bool, - success else { + OnchainUtils.importMulti(param) { (imported, message) in + if imported { + self.updateLocally(utxo: utxo, label: label) + } else { self.spinner.removeConnectingView() - showAlert(vc: self, title: "Something went wrong...", message: "error: \(errorMessage ?? "unknown error")") - return + showAlert(vc: self, title: "Something went wrong...", message: "error: \(message ?? "unknown error")") } - - self.updateLocally(utxo: utxo, label: label) } } diff --git a/FullyNoded/View Controllers/Home/Utilities/Menu/UtilitieMenuViewController.swift b/FullyNoded/View Controllers/Home/Utilities/Menu/UtilitieMenuViewController.swift index beca32f8..1c400936 100644 --- a/FullyNoded/View Controllers/Home/Utilities/Menu/UtilitieMenuViewController.swift +++ b/FullyNoded/View Controllers/Home/Utilities/Menu/UtilitieMenuViewController.swift @@ -339,45 +339,16 @@ class UtilitieMenuViewController: UIViewController, UITableViewDelegate, UITable private func rescan() { connectingView.addConnectingView(vc: self, description: "starting rescan...") - Reducer.makeCommand(command: .getblockchaininfo, param: "") { [weak self] (response, errorMessage) in - guard let self = self else { return } - + OnchainUtils.rescan { (started, message) in self.connectingView.removeConnectingView() - guard let dict = response as? NSDictionary, let pruned = dict["pruned"] as? Bool else { - displayAlert(viewController: self, isError: true, message: "Error checking pruned status: \(errorMessage ?? "unknown")") - return - } - - guard pruned else { - self.rescanFrom(0) - return - } - - guard let pruneheight = dict["pruneheight"] as? Int else { - displayAlert(viewController: self, isError: true, message: "Error checking prune height: \(errorMessage ?? "unknown")") + guard started else { + showAlert(vc: self, title: "", message: message ?? "Unknown error rescanning.") + return } - self.rescanFrom(pruneheight) - } - } - - private func rescanFrom(_ height: Int) { - Reducer.makeCommand(command: .rescanblockchain, param: "\(height)") { [weak self] (response, errorMessage) in - guard let self = self else { return } - - self.connectingView.removeConnectingView() - - if errorMessage != nil { - if errorMessage!.contains("Wallet is currently rescanning. Abort existing rescan or wait.") { - showAlert(vc: self, title: "", message: "Rescan started ✓") - } else { - displayAlert(viewController: self, isError: true, message: "Error rescanning: \(errorMessage!)") - } - } else if response != nil { - showAlert(vc: self, title: "", message: "Rescan started ✓") - } + showAlert(vc: self, title: "", message: "Rescan started ✓") } } diff --git a/FullyNoded/View Controllers/Wallets/RecoveryViewController.swift b/FullyNoded/View Controllers/Wallets/RecoveryViewController.swift deleted file mode 100644 index a474356e..00000000 --- a/FullyNoded/View Controllers/Wallets/RecoveryViewController.swift +++ /dev/null @@ -1,1094 +0,0 @@ -// -// RecoveryViewController.swift -// BitSense -// -// Created by Peter on 29/06/20. -// Copyright © 2020 Fontaine. All rights reserved. -// - -import UIKit -import LibWally - -class RecoveryViewController: UIViewController, UITextFieldDelegate, UINavigationControllerDelegate { - - var coinType = "0" - var recoverSamourai = Bool() - var descriptorsToImport = [String]() - var accountNumber = "0" - var primDesc = "" - var changeDesc = "" - var name = "" - var spinner = ConnectingView() - var addedWords = [String]() - var justWords = [String]() - var bip39Words = [String]() - var autoCompleteCharacterCount = 0 - var timer = Timer() - var blockheight:Int64! - - @IBOutlet weak var wordView: UITextView! - @IBOutlet weak var textField: UITextField! - @IBOutlet weak var recoverOutlet: UIButton! - @IBOutlet weak var accountField: UITextField! - @IBOutlet weak var passphraseField: UITextField! - - override func viewDidLoad() { - super.viewDidLoad() - - navigationController?.delegate = self - passphraseField.delegate = self - accountField.delegate = self - textField.delegate = self - wordView.layer.cornerRadius = 8 - wordView.layer.borderColor = UIColor.lightGray.cgColor - wordView.layer.borderWidth = 0.5 - recoverOutlet.clipsToBounds = true - recoverOutlet.layer.cornerRadius = 8 - recoverOutlet.isEnabled = false - bip39Words = Words.valid - updatePlaceHolder(wordNumber: 1) - accountField.text = "0" - - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard(_:))) - tapGesture.numberOfTapsRequired = 1 - self.view.addGestureRecognizer(tapGesture) - textField.removeGestureRecognizer(tapGesture) - - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) - - setCoinType() - } - - @IBAction func showHelp(_ sender: Any) { - let message = "This tool allows you to import a BIP39 recovery phrase along with an optional passphrase and customizable account number. The recovery tool can take around 1 minute to complete. This is because it constructs most of the popular wallet derivation paths BIP44/49/84 and imports all address types for each derivation. It accounts for change and receive keys importing 2500 keys for each. Optionally you may opt to recover Samourai wallet derivations too (Ricochet, BadBank, Deposit, BIP47, PreMix, PostMix), this will add another minute or so. Once the recovery wallet is created it will rescan your blockchain. Your seed words are encrypted and saved locally, NOT on your node, your node will only hold the public keys" - showAlert(vc: self, title: "Recovery", message: message) - } - - @objc func dismissKeyboard(_ sender: UITapGestureRecognizer) { - hideKeyboards() - } - - private func hideKeyboards() { - DispatchQueue.main.async { [unowned vc = self] in - vc.accountField.resignFirstResponder() - vc.textField.resignFirstResponder() - vc.passphraseField.resignFirstResponder() - } - } - - @objc func keyboardWillShow(notification: NSNotification) { - if passphraseField.isEditing { - if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue { - if self.view.frame.origin.y == 0 { - self.view.frame.origin.y -= keyboardSize.height - } - } - } - } - - @objc func keyboardWillHide(notification: NSNotification) { - if passphraseField.isEditing { - if self.view.frame.origin.y != 0 { - self.view.frame.origin.y = 0 - } - } - } - - private func setCoinType() { - spinner.addConnectingView(vc: self, description: "fetching chain type...") - Reducer.makeCommand(command: .getblockchaininfo, param: "") { [unowned vc = self] (response, errorMessage) in - if let dict = response as? NSDictionary { - if let chain = dict["chain"] as? String { - if chain == "test" { - vc.coinType = "1" - } - if let blocks = dict["blocks"] as? Int { - vc.blockheight = Int64(blocks) - } - vc.spinner.removeConnectingView() - } - } else { - vc.showError(error: "Error getting blockchain info, please chack your connection to your node.") - DispatchQueue.main.async { - vc.navigationController?.popToRootViewController(animated: true) - } - } - } - } - - private func promptRecoveryOptions() { - DispatchQueue.main.async { [unowned vc = self] in - var alertStyle = UIAlertController.Style.actionSheet - if (UIDevice.current.userInterfaceIdiom == .pad) { - alertStyle = UIAlertController.Style.alert - } - let alert = UIAlertController(title: "Would you like to include Samourai Wallet/Mixing derivation paths?", message: "Selecting yes will ensure all Samourai paths are recovered however will double the amount of time it takes to create the Recovery Wallet.", preferredStyle: alertStyle) - alert.addAction(UIAlertAction(title: "Yes, include", style: .default, handler: { [unowned vc = self] action in - vc.recoverSamourai = true - vc.recoverNow() - })) - alert.addAction(UIAlertAction(title: "No, do not", style: .default, handler: { [unowned vc = self] action in - vc.recoverSamourai = false - vc.recoverNow() - })) - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { action in })) - alert.popoverPresentationController?.sourceView = vc.view - vc.present(alert, animated: true, completion: nil) - } - } - - private func showError(error:String) { - DispatchQueue.main.async { [unowned vc = self] in - UserDefaults.standard.removeObject(forKey: "walletName") - vc.spinner.removeConnectingView() - showAlert(vc: vc, title: "Error", message: error) - } - } - - @objc func handleTap() { - DispatchQueue.main.async { [unowned vc = self] in - vc.textField.resignFirstResponder() - vc.accountField.resignFirstResponder() - } - } - - private func updatePlaceHolder(wordNumber: Int) { - DispatchQueue.main.async { [unowned vc = self] in - vc.textField.attributedPlaceholder = NSAttributedString(string: "add word #\(wordNumber)", attributes: [NSAttributedString.Key.foregroundColor: UIColor.lightGray]) - } - } - - private func updateSpinnerText(text: String) { - DispatchQueue.main.async { [unowned vc = self] in - vc.spinner.label.text = text - } - } - - private func recoverNow() { - spinner.addConnectingView(vc: self, description: "creating your recovery wallet...") - - accountNumber = accountField.text ?? "0" - let passphrase = passphraseField.text ?? "" - let seed = justWords.joined(separator: " ") - - if let mk = Keys.masterKey(words: seed, coinType: coinType, passphrase: passphrase) { - createWallet(mk: mk) { [unowned vc = self] success in - if success { - if vc.recoverSamourai { - vc.updateSpinnerText(text: "getting Samourai Bad Bank primary descriptor...") - vc.getDescriptorInfo(desc: vc.samouraiBadBankPrim(mk)) { [unowned vc = self] sam2DescPrim in - if sam2DescPrim != nil { - vc.descriptorsToImport.append(sam2DescPrim!) - vc.updateSpinnerText(text: "getting Samourai Bad Bank change descriptor...") - vc.getDescriptorInfo(desc: vc.samouraiBadBankChange(mk)) { [unowned vc = self] sam2DescChange in - if sam2DescChange != nil { - vc.descriptorsToImport.append(sam2DescChange!) - vc.updateSpinnerText(text: "getting Samourai Pre Mix primary descriptor...") - vc.getDescriptorInfo(desc: vc.samouraiPreMixPrim(mk)) { [unowned vc = self] samDesc3Prim in - if samDesc3Prim != nil { - vc.descriptorsToImport.append(samDesc3Prim!) - vc.updateSpinnerText(text: "getting Samourai Pre Mix change descriptor...") - vc.getDescriptorInfo(desc: vc.samouraiPreMixChange(mk)) { [unowned vc = self] samDesc3Change in - if samDesc3Change != nil { - vc.descriptorsToImport.append(samDesc3Change!) - vc.updateSpinnerText(text: "getting Samourai Post Mix primary descriptor...") - vc.getDescriptorInfo(desc: vc.samouraiPostMixPrim(mk)) { [unowned vc = self] samDesc4Prim in - if samDesc4Prim != nil { - vc.descriptorsToImport.append(samDesc4Prim!) - vc.updateSpinnerText(text: "getting Samourai Post Mix change descriptor...") - vc.getDescriptorInfo(desc: vc.samouraiPostMixChange(mk)) { [unowned vc = self] samDesc4Change in - if samDesc4Change != nil { - vc.descriptorsToImport.append(samDesc4Change!) - vc.updateSpinnerText(text: "getting Samourai Ricochet 84 primary descriptor...") - vc.getDescriptorInfo(desc: vc.samouraiRicochet84Prim(mk)) { [unowned vc = self] samDesc5Prim in - if samDesc5Prim != nil { - vc.descriptorsToImport.append(samDesc5Prim!) - vc.updateSpinnerText(text: "getting Samourai Samourai Ricochet 84 change descriptor...") - vc.getDescriptorInfo(desc: vc.samouraiRicochet84Change(mk)) { [unowned vc = self] samDesc5Change in - if samDesc5Change != nil { - vc.descriptorsToImport.append(samDesc5Change!) - vc.updateSpinnerText(text: "getting Samourai Ricochet 44 primary descriptor...") - vc.getDescriptorInfo(desc: vc.samouraiRicochet44Prim(mk)) { [unowned vc = self] samDesc6Prim in - if samDesc6Prim != nil { - vc.descriptorsToImport.append(samDesc6Prim!) - vc.updateSpinnerText(text: "getting Samourai Ricochet 44 change descriptor...") - vc.getDescriptorInfo(desc: vc.samouraiRicochet44Change(mk)) { [unowned vc = self] samDesc6Change in - if samDesc6Change != nil { - vc.descriptorsToImport.append(samDesc6Prim!) - vc.updateSpinnerText(text: "getting Samourai Ricochet 49 primary descriptor...") - vc.getDescriptorInfo(desc: vc.samouraiRicochet49Prim(mk)) { [unowned vc = self] samDesc7Prim in - if samDesc7Prim != nil { - vc.descriptorsToImport.append(samDesc7Prim!) - vc.updateSpinnerText(text: "getting Samourai Ricochet 49 change descriptor...") - vc.getDescriptorInfo(desc: vc.samouraiRicochet49Change(mk)) { samDesc7Change in - if samDesc7Change != nil { - vc.descriptorsToImport.append(samDesc7Change!) - vc.getNonSamouraiDescriptors(masterKey: mk) - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } else { - vc.getNonSamouraiDescriptors(masterKey: mk) - } - } - } - } else { - - } - } - - @IBAction func recoverAction(_ sender: Any) { - promptRecoveryOptions() - } - - private func getNonSamouraiDescriptors(masterKey: String) { - updateSpinnerText(text: "getting BIP84 primary descriptor...") - getDescriptorInfo(desc: bip84PrimDesc(masterKey)) { [unowned vc = self] bip84Prim in - if bip84Prim != nil { - vc.descriptorsToImport.append(bip84Prim!) - vc.updateSpinnerText(text: "getting BIP84 change descriptor...") - vc.getDescriptorInfo(desc: vc.bip84ChangeDesc(masterKey)) { [unowned vc = self] bip84Change in - if bip84Change != nil { - vc.descriptorsToImport.append(bip84Change!) - vc.updateSpinnerText(text: "getting BIP49 primary descriptor...") - vc.getDescriptorInfo(desc: vc.bip49PrimDesc(masterKey)) { [unowned vc = self] bip49Prim in - if bip49Prim != nil { - vc.descriptorsToImport.append(bip49Prim!) - vc.updateSpinnerText(text: "getting BIP49 change descriptor...") - vc.getDescriptorInfo(desc: vc.bip49ChangeDesc(masterKey)) { [unowned vc = self] bip49Change in - if bip49Change != nil { - vc.descriptorsToImport.append(bip49Change!) - vc.updateSpinnerText(text: "getting BIP44 primary descriptor...") - vc.getDescriptorInfo(desc: vc.bip44PrimDesc(masterKey)) { [unowned vc = self] bip44Prim in - if bip44Prim != nil { - vc.descriptorsToImport.append(bip44Prim!) - vc.updateSpinnerText(text: "getting BIP44 change descriptor...") - vc.getDescriptorInfo(desc: vc.bip44ChangeDesc(masterKey)) { [unowned vc = self] bip44Change in - if bip44Change != nil { - vc.descriptorsToImport.append(bip44Change!) - vc.updateSpinnerText(text: "getting BRD wallet primary descriptor...") - vc.getDescriptorInfo(desc: vc.brdPrimDesc(masterKey)) { [unowned vc = self] (bdrPrimDesd) in - if bdrPrimDesd != nil { - vc.descriptorsToImport.append(bdrPrimDesd!) - vc.updateSpinnerText(text: "getting BRD wallet change descriptor...") - vc.getDescriptorInfo(desc: vc.brdChangeDesc(masterKey)) { (brdChangeDesc) in - if brdChangeDesc != nil { - vc.descriptorsToImport.append(brdChangeDesc!) - vc.importDescriptors(index: 0) - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - - private func importDescriptors(index: Int) { - if index < descriptorsToImport.count { - updateSpinnerText(text: "importing descriptor #\(index + 1) out of \(descriptorsToImport.count)...") - let descriptor = descriptorsToImport[index] - var params = "[{ \"desc\": \"\(descriptor)\", \"timestamp\": \"now\", \"range\": [0,2500], \"watchonly\": true, \"label\": \"Fully Noded Recovery\", \"keypool\": false, \"internal\": false }], {\"rescan\": false}" - if descriptor.contains("84'/\(coinType)'/\(accountNumber)'") { - if descriptor.contains("/0/*") { - params = "[{ \"desc\": \"\(descriptor)\", \"timestamp\": \"now\", \"range\": [0,2500], \"watchonly\": true, \"label\": \"Fully Noded Recovery\", \"keypool\": true, \"internal\": false }], {\"rescan\": false}" - primDesc = descriptor - } else if descriptor.contains("/1/*") { - params = "[{ \"desc\": \"\(descriptor)\", \"timestamp\": \"now\", \"range\": [0,2500], \"watchonly\": true, \"keypool\": true, \"internal\": true }], {\"rescan\": false}" - changeDesc = descriptor - } - } - importMulti(params: params) { [unowned vc = self] success in - if success { - vc.importDescriptors(index: index + 1) - } else { - vc.showError(error: "Error importing a recovery descriptor.") - } - } - } else { - updateSpinnerText(text: "starting a rescan...") - Reducer.makeCommand(command: .getblockchaininfo, param: "") { [unowned vc = self] (response, errorMessage) in - if let dict = response as? NSDictionary { - if let pruned = dict["pruned"] as? Bool { - if pruned { - if let pruneHeight = dict["pruneheight"] as? Int { - Reducer.makeCommand(command: .rescanblockchain, param: "\(pruneHeight)") { [unowned vc = self] (response, errorMessage) in - vc.saveLocally() - } - } - } else { - Reducer.makeCommand(command: .rescanblockchain, param: "") { [unowned vc = self] (response, errorMessage) in - vc.saveLocally() - } - } - } - } else { - vc.showError(error: "Error starting a rescan, your wallet has not been saved. Please check your connection to your node and try again.") - } - } - } - } - - private func samouraiBadBankPrim(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/84h/\(coinType)h/2147483644h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/84h/\(coinType)h/2147483644h]\(xpub)/0/*)" - } - } - return desc - } - - private func samouraiBadBankChange(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/84h/\(coinType)h/2147483644h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/84h/\(coinType)h/2147483644h]\(xpub)/1/*)" - } - } - return desc - } - - private func samouraiPreMixPrim(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/84h/\(coinType)h/2147483645h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/84h/\(coinType)h/2147483645h]\(xpub)/0/*)" - } - } - return desc - } - - private func samouraiPreMixChange(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/84h/\(coinType)h/2147483645h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/84h/\(coinType)h/2147483645h]\(xpub)/1/*)" - } - } - return desc - } - - private func samouraiPostMixPrim(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/84h/\(coinType)h/2147483646h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/84h/\(coinType)h/2147483646h]\(xpub)/0/*)" - } - } - return desc - } - - private func samouraiPostMixChange(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/84h/\(coinType)h/2147483646h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/84h/\(coinType)h/2147483646h]\(xpub)/1/*)" - } - } - return desc - } - - private func samouraiRicochet84Prim(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/84h/\(coinType)h/2147483647h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/84h/\(coinType)h/2147483647h]\(xpub)/0/*)" - } - } - return desc - } - - private func samouraiRicochet84Change(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/84h/\(coinType)h/2147483647h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/84h/\(coinType)h/2147483647h]\(xpub)/1/*)" - } - } - return desc - } - - private func samouraiRicochet44Prim(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/44h/\(coinType)h/2147483647h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/44h/\(coinType)h/2147483647h]\(xpub)/0/*)" - } - } - return desc - } - - private func samouraiRicochet44Change(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/44h/\(coinType)h/2147483647h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/44h/\(coinType)h/2147483647h]\(xpub)/1/*)" - } - } - return desc - } - - private func samouraiRicochet49Prim(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/49h/\(coinType)h/2147483647h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/49h/\(coinType)h/2147483647h]\(xpub)/0/*)" - } - } - return desc - } - - private func samouraiRicochet49Change(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/49h/\(coinType)h/2147483647h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/49h/\(coinType)h/2147483647h]\(xpub)/1/*)" - } - } - return desc - } - - private func bip84PrimDesc(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/84h/\(coinType)h/\(accountNumber)h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/84h/\(coinType)h/\(accountNumber)h]\(xpub)/0/*)" - } - } - return desc - } - - private func bip84ChangeDesc(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/84h/\(coinType)h/\(accountNumber)h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/84h/\(coinType)h/\(accountNumber)h]\(xpub)/1/*)" - } - } - return desc - } - - private func bip44PrimDesc(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/44h/\(coinType)h/\(accountNumber)h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/44h/\(coinType)h/\(accountNumber)h]\(xpub)/0/*)" - } - } - return desc - } - - private func bip44ChangeDesc(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/44h/\(coinType)h/\(accountNumber)h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/44h/\(coinType)h/\(accountNumber)h]\(xpub)/1/*)" - } - } - return desc - } - - private func bip49PrimDesc(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/49h/\(coinType)h/\(accountNumber)h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/49h/\(coinType)h/\(accountNumber)h]\(xpub)/0/*)" - } - } - return desc - } - - private func bip49ChangeDesc(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/49h/\(coinType)h/\(accountNumber)h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/49h/\(coinType)h/\(accountNumber)h]\(xpub)/1/*)" - } - } - return desc - } - - private func brdPrimDesc(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/0h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/0h]\(xpub)/0/*)" - } - } - return desc - } - - private func brdChangeDesc(_ masterKey: String) -> String { - var desc = "" - if let xpub = Keys.xpub(path: "m/0h", masterKey: masterKey) { - if let fingerprint = Keys.fingerprint(masterKey: masterKey) { - desc = "combo([\(fingerprint)/0h]\(xpub)/1/*)" - } - } - return desc - } - - private func walletExistsOnNode(_ hash: String, completion: @escaping ((String?)) -> Void) { - Reducer.makeCommand(command: .listwalletdir, param: "") { [weak self] (response, errorMessage) in - guard let self = self else { return } - - if let wallets = response as? NSDictionary { - self.parseWallets(wallets, hash, completion: completion) - } else { - completion(nil) - } - } - } - - private func parseWallets(_ wallets: NSDictionary, _ hash: String, completion: @escaping ((String?)) -> Void) { - guard let walletArr = wallets["wallets"] as? NSArray, walletArr.count > 0 else { - completion(nil) - return - } - - var existingWallet: String? - - for (i, wallet) in walletArr.enumerated() { - let walletDict = wallet as! NSDictionary - let walletName = walletDict["name"] as! String - - if walletName.contains(hash) { - existingWallet = walletName - } - - if i + 1 == walletArr.count { - completion(existingWallet) - } - } - } - - private func createWallet(mk: String, completion: @escaping ((Bool)) -> Void) { - let walletName = "Recovery-\(Crypto.sha256hash(bip84PrimDesc(mk)))" - - walletExistsOnNode(walletName) { [weak self] existingWallet in - guard let self = self else { return } - - guard existingWallet != nil else { - self.createWalletNow(walletName: walletName) { success in - guard success else { - self.showError(error: "Error creating your wallet.") - completion(false) - return - } - - completion(true) - } - - return - } - - self.name = walletName - UserDefaults.standard.set(walletName, forKey: "walletName") - completion(true) - } - } - - private func createWalletNow(walletName: String, completion: @escaping ((Bool)) -> Void) { - let param = "\"\(walletName)\", true, true, \"\", true" - Reducer.makeCommand(command: .createwallet, param: param) { [unowned vc = self] (response, errorMessage) in - if let dict = response as? NSDictionary { - if let name = dict["name"] as? String { - vc.name = name - UserDefaults.standard.set(name, forKey: "walletName") - completion(true) - } else { - vc.showError(error: "Error creating wallet on your node") - completion(false) - } - } else { - vc.showError(error: "Error creating wallet on your node: \(errorMessage ?? "unknown")") - completion(false) - } - } - } - - private func getDescriptorInfo(desc: String, completion: @escaping ((String?)) -> Void) { - Reducer.makeCommand(command: .getdescriptorinfo, param: "\"\(desc)\"") { (response, errorMessage) in - if let dict = response as? NSDictionary { - if let updatedDescriptor = dict["descriptor"] as? String { - completion((updatedDescriptor)) - } - } - } - } - - private func importMulti(params: String, completion: @escaping ((Bool)) -> Void) { - Reducer.makeCommand(command: .importmulti, param: params) { (response, errorDescription) in - if let result = response as? NSArray { - if result.count > 0 { - if let dict = result[0] as? NSDictionary { - if let success = dict["success"] as? Bool { - completion((success)) - } else { - completion((false)) - } - } - } else { - completion((false)) - } - } else { - completion((false)) - } - } - } - - private func saveLocally() { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - let data = self.justWords.joined(separator: " ").dataUsingUTF8StringEncoding - let passphrase = self.passphraseField.text ?? "" - - guard let encryptedWords = Crypto.encrypt(data) else { - self.showError(error: "error encrypting your seed") - return - } - - if passphrase != "" { - guard let encryptedPassphrase = Crypto.encrypt(passphrase.dataUsingUTF8StringEncoding) else { - self.showError(error: "error encrypting your seed") - return - } - - self.saveSignerAndPassphrase(encryptedSigner: encryptedWords, encryptedPassphrase: encryptedPassphrase) - } else { - - self.saveSigner(encryptedSigner: encryptedWords) - } - } - } - - private func saveSignerAndPassphrase(encryptedSigner: Data, encryptedPassphrase: Data) { - let dict = ["id":UUID(), "words":encryptedSigner, "passphrase": encryptedPassphrase] as [String:Any] - CoreDataService.saveEntity(dict: dict, entityName: .signers) { [unowned vc = self] success in - if success { - vc.saveWallet() - } else { - vc.showError(error: "error saving encrypted seed") - } - } - } - - private func saveSigner(encryptedSigner: Data) { - let dict = ["id":UUID(), "words":encryptedSigner] as [String:Any] - CoreDataService.saveEntity(dict: dict, entityName: .signers) { [unowned vc = self] success in - if success { - vc.saveWallet() - } else { - vc.showError(error: "error saving encrypted seed") - } - } - } - - private func saveWallet() { - var dict = [String:Any]() - dict["id"] = UUID() - dict["label"] = "Recovery Wallet" - dict["changeDescriptor"] = changeDesc - dict["receiveDescriptor"] = primDesc - dict["watching"] = descriptorsToImport - dict["type"] = "Single-Sig" - dict["name"] = name - dict["maxIndex"] = Int64(2500) - dict["index"] = Int64(0) - dict["blockheight"] = Int64(blockheight) - DispatchQueue.main.async { [unowned vc = self] in - dict["account"] = vc.accountField.text ?? "0" - } - CoreDataService.saveEntity(dict: dict, entityName: .wallets) { [unowned vc = self] success in - if success { - NotificationCenter.default.post(name: .refreshWallet, object: nil, userInfo: nil) - vc.wordView.text = "" - vc.spinner.removeConnectingView() - DispatchQueue.main.async { - vc.navigationController?.popToRootViewController(animated: true) - } - showAlert(vc: vc, title: "Successfully created a Fully Noded Recovery wallet ✅", message: "Your node is currently rescanning the blockchain, if your node is not pruned this can take up to an hour. Please check your balances again when the rescan completes. You can monitor rescan status in Tools > Get Wallet Info") - } - } - } - - @IBAction func removeWordAction(_ sender: Any) { - if self.justWords.count > 0 { - - DispatchQueue.main.async { [unowned vc = self] in - - vc.wordView.text = "" - vc.addedWords.removeAll() - vc.justWords.remove(at: vc.justWords.count - 1) - - for (i, word) in vc.justWords.enumerated() { - - vc.addedWords.append("\(i + 1). \(word)\n") - if i == 0 { - vc.updatePlaceHolder(wordNumber: i + 1) - } else { - vc.updatePlaceHolder(wordNumber: i + 2) - } - } - - vc.wordView.text = vc.addedWords.joined(separator: "") - - if let _ = try? BIP39Mnemonic(words: vc.justWords.joined(separator: " ")) { - vc.validWordsAdded() - } - } - } - } - - @IBAction func addWordAction(_ sender: Any) { - processTextfieldInput() - } - - private func processTextfieldInput() { - if textField.text != "" { - - //check if user pasted more then one word - let processed = processedCharacters(textField.text!) - let userAddedWords = processed.split(separator: " ") - var multipleWords = [String]() - - if userAddedWords.count > 1 { - - //user add multiple words - for (i, word) in userAddedWords.enumerated() { - - var isValid = false - - for bip39Word in bip39Words { - - if word == bip39Word { - isValid = true - multipleWords.append("\(word)") - } - - } - - if i + 1 == userAddedWords.count { - - // we finished our checks - if isValid { - - // they are valid bip39 words - addMultipleWords(words: multipleWords) - - textField.text = "" - - } else { - - //they are not all valid bip39 words - textField.text = "" - - showAlert(vc: self, title: "Error", message: "At least one of those words is not a valid BIP39 word. We suggest inputting them one at a time so you can utilize our autosuggest feature which will prevent typos.") - - } - - } - - } - - } else { - - //its one word - let processedWord = textField.text!.replacingOccurrences(of: " ", with: "") - - for word in bip39Words { - - if processedWord == word { - - addWord(word: processedWord) - textField.text = "" - - } - - } - - } - - } else { - - shakeAlert(viewToShake: textField) - - } - - } - - private func formatSubstring(subString: String) -> String { - - let formatted = String(subString.dropLast(autoCompleteCharacterCount)).lowercased() - return formatted - - } - - private func resetValues() { - - textField.textColor = .white - autoCompleteCharacterCount = 0 - textField.text = "" - - } - - func searchAutocompleteEntriesWIthSubstring(substring: String) { - - let userQuery = substring - let suggestions = getAutocompleteSuggestions(userText: substring) - self.textField.textColor = .white - - if suggestions.count > 0 { - - timer = .scheduledTimer(withTimeInterval: 0.01, repeats: false, block: { (timer) in - - let autocompleteResult = self.formatAutocompleteResult(substring: substring, possibleMatches: suggestions) - self.putColorFormattedTextInTextField(autocompleteResult: autocompleteResult, userQuery : userQuery) - self.moveCaretToEndOfUserQueryPosition(userQuery: userQuery) - - }) - - } else { - - timer = .scheduledTimer(withTimeInterval: 0.01, repeats: false, block: { [unowned vc = self] (timer) in //7 - - vc.textField.text = substring - - if let _ = try? BIP39Mnemonic(words: vc.processedCharacters(vc.textField.text!)) { - - vc.processTextfieldInput() - vc.textField.textColor = .systemGreen - vc.validWordsAdded() - - } else { - - vc.textField.textColor = .systemRed - - } - - - }) - - autoCompleteCharacterCount = 0 - - } - - } - - func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - - if textField != accountField && textField != passphraseField { - var subString = (textField.text!.capitalized as NSString).replacingCharacters(in: range, with: string) - subString = formatSubstring(subString: subString) - - if subString.count == 0 { - - resetValues() - - } else { - - searchAutocompleteEntriesWIthSubstring(substring: subString) - - } - } - - return true - - } - - func textFieldShouldReturn(_ textField: UITextField) -> Bool { - if textField != accountField && textField != passphraseField { - processTextfieldInput() - } else if textField == accountField { - accountField.endEditing(true) - } - return true - } - - func getAutocompleteSuggestions(userText: String) -> [String]{ - - var possibleMatches: [String] = [] - - for item in bip39Words { - - let myString:NSString! = item as NSString - let substringRange:NSRange! = myString.range(of: userText) - - if (substringRange.location == 0) { - - possibleMatches.append(item) - - } - - } - - return possibleMatches - - } - - func putColorFormattedTextInTextField(autocompleteResult: String, userQuery : String) { - - let coloredString: NSMutableAttributedString = NSMutableAttributedString(string: userQuery + autocompleteResult) - - coloredString.addAttribute(NSAttributedString.Key.foregroundColor, - value: UIColor.systemGreen, - range: NSRange(location: userQuery.count,length:autocompleteResult.count)) - - self.textField.attributedText = coloredString - - } - - func moveCaretToEndOfUserQueryPosition(userQuery : String) { - - if let newPosition = self.textField.position(from: self.textField.beginningOfDocument, offset: userQuery.count) { - - self.textField.selectedTextRange = self.textField.textRange(from: newPosition, to: newPosition) - - } - - let selectedRange: UITextRange? = textField.selectedTextRange - textField.offset(from: textField.beginningOfDocument, to: (selectedRange?.start)!) - - } - - func formatAutocompleteResult(substring: String, possibleMatches: [String]) -> String { - - var autoCompleteResult = possibleMatches[0] - autoCompleteResult.removeSubrange(autoCompleteResult.startIndex.. String { - var result = string.filter("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ".contains) - result = result.condenseWhitespace() - return result - } - - private func validWordsAdded() { - DispatchQueue.main.async { [unowned vc = self] in - vc.textField.resignFirstResponder() - vc.recoverOutlet.isEnabled = true - } - showAlert(vc: self, title: "Valid Words ✓", message: "That is a valid recovery phrase, you may tap \"recover\" to recover this wallet.") - } - - -} diff --git a/FullyNoded/View Controllers/Wallets/SeedDisplayerViewController.swift b/FullyNoded/View Controllers/Wallets/SeedDisplayerViewController.swift index d5928a83..de11eae4 100644 --- a/FullyNoded/View Controllers/Wallets/SeedDisplayerViewController.swift +++ b/FullyNoded/View Controllers/Wallets/SeedDisplayerViewController.swift @@ -299,16 +299,13 @@ class SeedDisplayerViewController: UIViewController, UINavigationControllerDeleg } private func importMulti(params: String, completion: @escaping ((success: Bool, errorMessage: String?)) -> Void) { - Reducer.makeCommand(command: .importmulti, param: params) { (response, errorDescription) in - guard let result = response as? NSArray, - result.count > 0, - let dict = result[0] as? NSDictionary, - let success = dict["success"] as? Bool else { + OnchainUtils.importMulti(params) { (imported, message) in + if imported { + completion((imported, message)) + } else { UserDefaults.standard.removeObject(forKey: "walletName") - completion((false, errorDescription ?? "unknown error importing your keys")) - return + completion((false, message ?? "unknown error importing your keys")) } - completion((success, errorDescription)) } } diff --git a/FullyNoded/View Controllers/Wallets/WalletDetailViewController.swift b/FullyNoded/View Controllers/Wallets/WalletDetailViewController.swift index e8ca675f..b5043223 100644 --- a/FullyNoded/View Controllers/Wallets/WalletDetailViewController.swift +++ b/FullyNoded/View Controllers/Wallets/WalletDetailViewController.swift @@ -921,19 +921,13 @@ class WalletDetailViewController: UIViewController, UITextFieldDelegate, UITable self.updateSpinnerText(text: "starting a rescan...") - Reducer.makeCommand(command: .getblockchaininfo, param: "") { [weak self] (response, errorMessage) in - if let dict = response as? NSDictionary { - if let pruned = dict["pruned"] as? Bool { - if pruned { - if let pruneHeight = dict["pruneheight"] as? Int { - Reducer.makeCommand(command: .rescanblockchain, param: "\(pruneHeight)") { (_, _) in } - } - } else { - Reducer.makeCommand(command: .rescanblockchain, param: "") { (_, _) in } - } - } + OnchainUtils.rescan { [weak self] (started, message) in + guard let self = self else { return } + + if started { + self.spinner.removeConnectingView() } else { - self?.showError(error: "Error starting a rescan, your wallet has not been saved. Please check your connection to your node and try again.") + self.showError(error: "Error starting a rescan, your wallet has not been saved. Please check your connection to your node and try again.") } } })) @@ -945,13 +939,8 @@ class WalletDetailViewController: UIViewController, UITextFieldDelegate, UITable } private func importMulti(params: String, completion: @escaping ((Bool)) -> Void) { - Reducer.makeCommand(command: .importmulti, param: params) { (response, errorDescription) in - guard let result = response as? NSArray, result.count > 0, let dict = result[0] as? NSDictionary, let success = dict["success"] as? Bool else { - completion(false) - return - } - - completion((success)) + OnchainUtils.importMulti(params) { (imported, message) in + completion((imported)) } } diff --git a/FullyNoded/Wallet Logic/ImportWallet.swift b/FullyNoded/Wallet Logic/ImportWallet.swift index 7025c1dd..8ede7350 100644 --- a/FullyNoded/Wallet Logic/ImportWallet.swift +++ b/FullyNoded/Wallet Logic/ImportWallet.swift @@ -413,20 +413,8 @@ class ImportWallet { } class func getDescriptorInfo(desc: String, completion: @escaping ((desc: String?, errorMessage: String?)) -> Void) { - Reducer.makeCommand(command: .getdescriptorinfo, param: "\"\(desc)\"") { (response, errorMessage) in - guard let dict = response as? NSDictionary, let pubKeyDescriptor = dict["descriptor"] as? String else { - completion((nil, errorMessage ?? "error getting descriptor info")) - return - } - - if let hasprivatekeys = dict["hasprivatekeys"] as? Bool, hasprivatekeys, let checksum = dict["checksum"] as? String { - // its an xprv so we need to do some fanangling... - let privkeyDesc = desc + "#" + checksum - completion((privkeyDesc, nil)) - - } else { - completion((pubKeyDescriptor, nil)) - } + OnchainUtils.getDescriptorInfo("\(desc)") { (descriptorInfo, message) in + completion((descriptorInfo?.descriptor, message)) } } @@ -471,23 +459,11 @@ class ImportWallet { class func rescan(wallet: [String:Any], completion: @escaping ((success: Bool, errorDescription: String?)) -> Void) { if !isRecovering { - Reducer.makeCommand(command: .getblockchaininfo, param: "") { (response, errorMessage) in - guard let dict = response as? NSDictionary, let pruned = dict["pruned"] as? Bool else { - completion((false, errorMessage ?? "error getting blockchain info")) - return - } - - if pruned { - guard let pruneHeight = dict["pruneheight"] as? Int else { - completion((false, errorMessage ?? "error getting prune height")) - return - } - - Reducer.makeCommand(command: .rescanblockchain, param: "\(pruneHeight)") { (_, _) in } + OnchainUtils.rescan { (started, message) in + if started { saveLocally(wallet: wallet, completion: completion) } else { - Reducer.makeCommand(command: .rescanblockchain, param: "") { (_, _) in } - saveLocally(wallet: wallet, completion: completion) + completion((false, message ?? "error rescanning")) } } } else { @@ -496,16 +472,8 @@ class ImportWallet { } class func importMultiDesc(params: String, completion: @escaping ((success: Bool, errorMessage: String?)) -> Void) { - Reducer.makeCommand(command: .importmulti, param: params) { (response, errorDescription) in - guard let result = response as? NSArray, result.count > 0, - let dict = result[0] as? NSDictionary, - let success = dict["success"] as? Bool, - success else { - completion((false, errorDescription ?? "unknown error importing your keys")) - return - } - - completion((success, nil)) + OnchainUtils.importMulti(params) { (imported, message) in + completion((imported, message)) } }