diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 6f250de..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/AtomicSwapBitcoinProvider/AtomicSwapBitcoinProvider/Provider/BitcoinSwapBlockchain.swift b/AtomicSwapBitcoinProvider/AtomicSwapBitcoinProvider/Provider/BitcoinSwapBlockchain.swift index 7cfab6a..8cd938c 100644 --- a/AtomicSwapBitcoinProvider/AtomicSwapBitcoinProvider/Provider/BitcoinSwapBlockchain.swift +++ b/AtomicSwapBitcoinProvider/AtomicSwapBitcoinProvider/Provider/BitcoinSwapBlockchain.swift @@ -1,4 +1,4 @@ -import HSCryptoKit +import OpenSslKit import BitcoinCore import AtomicSwapCore @@ -22,7 +22,7 @@ public class BitcoinSwapBlockchain: ISwapBlockchain { } public var synced: Bool { - return (kit?.syncState ?? .notSynced) == .synced + (kit?.syncState ?? .notSynced(error: BitcoinCore.StateError.notStarted)) == .synced } public func changePublicKey() throws -> AtomicSwapCore.PublicKey { @@ -46,16 +46,16 @@ public class BitcoinSwapBlockchain: ISwapBlockchain { public func watchBailTransaction(withRedeemKeyHash redeemKeyHash: Data, refundKeyHash: Data, secretHash: Data, timestamp: Int) { let redeemScript = scriptBuilder.redeemScript(redeemKeyHash: redeemKeyHash, refundKeyHash: refundKeyHash, secretHash: secretHash, timestamp: timestamp) - let scriptHash = CryptoKit.sha256ripemd160(redeemScript) + let scriptHash = Kit.sha256ripemd160(redeemScript) kit?.watch(transaction: BitcoinCore.TransactionFilter.p2shOutput(scriptHash: scriptHash), delegate: self) } public func sendBailTransaction(withRedeemKeyHash redeemKeyHash: Data, refundKeyHash: Data, secretHash: Data, timestamp: Int, amount: Double) throws -> IBailTransaction { let redeemScript = scriptBuilder.redeemScript(redeemKeyHash: redeemKeyHash, refundKeyHash: refundKeyHash, secretHash: secretHash, timestamp: timestamp) - let scriptHash = CryptoKit.sha256ripemd160(redeemScript) + let scriptHash = Kit.sha256ripemd160(redeemScript) - guard let transaction = try kit?.send(to: scriptHash, scriptType: .p2sh, value: Int(amount * BitcoinSwapBlockchain.satoshiPerBitcoin), feeRate: 42) else { + guard let transaction = try kit?.send(to: scriptHash, scriptType: .p2sh, value: Int(amount * BitcoinSwapBlockchain.satoshiPerBitcoin), feeRate: 42, sortType: .shuffle) else { throw BitcoinKitSwapBlockchainError.transactionNotSent } @@ -82,7 +82,7 @@ public class BitcoinSwapBlockchain: ISwapBlockchain { } let redeemScript = scriptBuilder.redeemScript(redeemKeyHash: redeemKeyHash, refundKeyHash: refundKeyHash, secretHash: secretHash, timestamp: timestamp) - let scriptHash = CryptoKit.sha256ripemd160(redeemScript) + let scriptHash = Kit.sha256ripemd160(redeemScript) guard let kit = self.kit else { return @@ -93,11 +93,15 @@ public class BitcoinSwapBlockchain: ISwapBlockchain { withValue: transaction.amount, index: transaction.outputIndex, lockingScript: transaction.lockingScript, transactionHash: transaction.transactionHash, type: .p2sh, redeemScript: redeemScript, keyHash: scriptHash, publicKey: publicKey ) - let unspentOutput = UnspentOutput(output: output, publicKey: publicKey, transaction: Transaction(version: 0, lockTime: 0, timestamp: nil)) + output.signatureScriptFunction = { data in + let signature = data[0] + let publicKey = data[1] - _ = try kit.redeem(from: unspentOutput, to: kit.receiveAddress(for: .p2pkh), feeRate: 43) { signature, publicKey in return OpCode.push(signature) + OpCode.push(publicKey) + OpCode.push(secret) + OpCode.push(1) + OpCode.push(redeemScript) } + + let unspentOutput = UnspentOutput(output: output, publicKey: publicKey, transaction: Transaction(version: 0, lockTime: 0, timestamp: nil)) + _ = try kit.redeem(from: unspentOutput, to: kit.receiveAddress(), feeRate: 43, sortType: .shuffle) } public func bailTransaction(from data: Data) throws -> IBailTransaction { diff --git a/AtomicSwapCore/AtomicSwapCore/Core/SwapFactory.swift b/AtomicSwapCore/AtomicSwapCore/Core/SwapFactory.swift index f01426a..bcc6ee0 100644 --- a/AtomicSwapCore/AtomicSwapCore/Core/SwapFactory.swift +++ b/AtomicSwapCore/AtomicSwapCore/Core/SwapFactory.swift @@ -1,4 +1,4 @@ -import HSCryptoKit +import OpenSslKit class SwapFactory { enum FactoryError: Error { @@ -46,7 +46,7 @@ extension SwapFactory : ISwapFactory { id: id.reduce("") { $0 + String(format: "%02x", $1) }, state: Swap.State.requested, initiator: true, initiatorCoinCode: initiatorCoinCode, responderCoinCode: responderCoinCode, rate: rate, amount: amount, - secretHash: CryptoKit.sha256(secret), secret: secret, + secretHash: Kit.sha256(secret), secret: secret, initiatorTimestamp: nil, responderTimestamp: nil, refundPKId: refundPublicKey.id, redeemPKId: redeemPublicKey.id, initiatorRefundPKH: refundPublicKey.keyHash, initiatorRedeemPKH: redeemPublicKey.keyHash, diff --git a/Demo/Demo.xcodeproj/project.pbxproj b/Demo/Demo.xcodeproj/project.pbxproj index 808d35e..66cf3b6 100644 --- a/Demo/Demo.xcodeproj/project.pbxproj +++ b/Demo/Demo.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 2FA5D6F0A098AC3AF7D5481B /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DF188B4AD5B5A04ECFA5 /* Extensions.swift */; }; D053FF4222DC9FCC002982C6 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D053FF4122DC9FCC002982C6 /* ViewController.swift */; }; D053FF4722DC9FCC002982C6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D053FF4622DC9FCC002982C6 /* Assets.xcassets */; }; D053FF5322DCA04C002982C6 /* AtomicSwapBitcoinProvider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D053FF5222DCA04C002982C6 /* AtomicSwapBitcoinProvider.framework */; }; @@ -20,7 +21,6 @@ D053FF7E22DCA2C8002982C6 /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = D053FF6122DCA2C7002982C6 /* Signal.swift */; }; D053FF7F22DCA2C8002982C6 /* PlainSwapCodec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D053FF6222DCA2C7002982C6 /* PlainSwapCodec.swift */; }; D053FF8022DCA2C8002982C6 /* TransactionRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = D053FF6322DCA2C7002982C6 /* TransactionRecord.swift */; }; - D053FF8122DCA2C8002982C6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D053FF6422DCA2C7002982C6 /* AppDelegate.swift */; }; D053FF8222DCA2C8002982C6 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = D053FF6522DCA2C7002982C6 /* LaunchScreen.xib */; }; D053FF8322DCA2C8002982C6 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D053FF6622DCA2C8002982C6 /* Configuration.swift */; }; D053FF8422DCA2C8002982C6 /* BitcoinCashAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D053FF6822DCA2C8002982C6 /* BitcoinCashAdapter.swift */; }; @@ -41,6 +41,7 @@ D053FF9422DCA2C8002982C6 /* SwapController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D053FF7A22DCA2C8002982C6 /* SwapController.xib */; }; D053FF9522DCA2C8002982C6 /* SendController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D053FF7B22DCA2C8002982C6 /* SendController.swift */; }; D053FF9622DCA2C8002982C6 /* ReceiveController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D053FF7C22DCA2C8002982C6 /* ReceiveController.xib */; }; + D06A506B247D2AE300230602 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06A506A247D2AE300230602 /* AppDelegate.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -60,6 +61,7 @@ /* Begin PBXFileReference section */ 03A01452094FD962A5A18D22 /* Pods-Demo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Demo.debug.xcconfig"; path = "Target Support Files/Pods-Demo/Pods-Demo.debug.xcconfig"; sourceTree = ""; }; + 2FA5DF188B4AD5B5A04ECFA5 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 9A9B80CAD01312EED9D799DA /* Pods-Demo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Demo.release.xcconfig"; path = "Target Support Files/Pods-Demo/Pods-Demo.release.xcconfig"; sourceTree = ""; }; D053FF3C22DC9FCC002982C6 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; D053FF4122DC9FCC002982C6 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -74,7 +76,6 @@ D053FF6122DCA2C7002982C6 /* Signal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Signal.swift; sourceTree = ""; }; D053FF6222DCA2C7002982C6 /* PlainSwapCodec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlainSwapCodec.swift; sourceTree = ""; }; D053FF6322DCA2C7002982C6 /* TransactionRecord.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionRecord.swift; sourceTree = ""; }; - D053FF6422DCA2C7002982C6 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = "../../../bitcoin-kit-ios/Demo/Demo/AppDelegate.swift"; sourceTree = ""; }; D053FF6522DCA2C7002982C6 /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; D053FF6622DCA2C8002982C6 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; D053FF6822DCA2C8002982C6 /* BitcoinCashAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCashAdapter.swift; sourceTree = ""; }; @@ -95,6 +96,7 @@ D053FF7A22DCA2C8002982C6 /* SwapController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SwapController.xib; sourceTree = ""; }; D053FF7B22DCA2C8002982C6 /* SendController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendController.swift; sourceTree = ""; }; D053FF7C22DCA2C8002982C6 /* ReceiveController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ReceiveController.xib; sourceTree = ""; }; + D06A506A247D2AE300230602 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -136,10 +138,10 @@ D053FF3E22DC9FCC002982C6 /* Demo */ = { isa = PBXGroup; children = ( + D06A506A247D2AE300230602 /* AppDelegate.swift */, D053FF6722DCA2C8002982C6 /* Adapters */, D053FF6C22DCA2C8002982C6 /* Controllers */, D053FF5F22DCA2C7002982C6 /* Core */, - D053FF6422DCA2C7002982C6 /* AppDelegate.swift */, D053FF6622DCA2C8002982C6 /* Configuration.swift */, D053FF6522DCA2C7002982C6 /* LaunchScreen.xib */, D053FF4122DC9FCC002982C6 /* ViewController.swift */, @@ -166,6 +168,7 @@ D053FF6122DCA2C7002982C6 /* Signal.swift */, D053FF6222DCA2C7002982C6 /* PlainSwapCodec.swift */, D053FF6322DCA2C7002982C6 /* TransactionRecord.swift */, + 2FA5DF188B4AD5B5A04ECFA5 /* Extensions.swift */, ); path = Core; sourceTree = ""; @@ -349,10 +352,10 @@ D053FF7E22DCA2C8002982C6 /* Signal.swift in Sources */, D053FF7F22DCA2C8002982C6 /* PlainSwapCodec.swift in Sources */, D053FF9122DCA2C8002982C6 /* MainController.swift in Sources */, + D06A506B247D2AE300230602 /* AppDelegate.swift in Sources */, D053FF8722DCA2C8002982C6 /* BaseAdapter.swift in Sources */, D053FF7D22DCA2C8002982C6 /* Manager.swift in Sources */, D053FF8C22DCA2C8002982C6 /* BalanceCell.swift in Sources */, - D053FF8122DCA2C8002982C6 /* AppDelegate.swift in Sources */, D053FF9022DCA2C8002982C6 /* SwapController.swift in Sources */, D053FF8E22DCA2C8002982C6 /* ReceiveController.swift in Sources */, D053FF8322DCA2C8002982C6 /* Configuration.swift in Sources */, @@ -360,6 +363,7 @@ D053FF9322DCA2C8002982C6 /* TransactionsController.swift in Sources */, D053FF8922DCA2C8002982C6 /* BalanceController.swift in Sources */, D053FF8022DCA2C8002982C6 /* TransactionRecord.swift in Sources */, + 2FA5D6F0A098AC3AF7D5481B /* Extensions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Demo/Demo/Adapters/BaseAdapter.swift b/Demo/Demo/Adapters/BaseAdapter.swift index 7413fd7..a5ec80d 100644 --- a/Demo/Demo/Adapters/BaseAdapter.swift +++ b/Demo/Demo/Adapters/BaseAdapter.swift @@ -2,14 +2,12 @@ import BitcoinCore import RxSwift class BaseAdapter { - var feeRate: Int { return 10 } + var feeRate: Int { 3 } private let coinRate: Decimal = pow(10, 8) let name: String let coinCode: String - var changeableAddressType: Bool { return false } - private let abstractKit: AbstractKit let lastBlockSignal = Signal() @@ -17,10 +15,6 @@ class BaseAdapter { let balanceSignal = Signal() let transactionsSignal = Signal() - var debugInfo: String { - return abstractKit.debugInfo - } - init(name: String, coinCode: String, abstractKit: AbstractKit) { self.name = name self.coinCode = coinCode @@ -28,23 +22,96 @@ class BaseAdapter { } func transactionRecord(fromTransaction transaction: TransactionInfo) -> TransactionRecord { - let fromAddresses = transaction.from.map { - TransactionAddress(address: $0.address, mine: $0.mine) + var myInputsTotalValue: Int = 0 + var myOutputsTotalValue: Int = 0 + var myChangeOutputsTotalValue: Int = 0 + var outputsTotalValue: Int = 0 + var allInputsMine = true + + var type: TransactionType + var from = [TransactionInputOutput]() + var to = [TransactionInputOutput]() + + for input in transaction.inputs { + if input.mine { + if let value = input.value { + myInputsTotalValue += value + } + } else { + allInputsMine = false + } + + from.append(TransactionInputOutput( + mine: input.mine, address: input.address, value: input.value, + changeOutput: false, pluginId: nil, pluginData: nil + )) + + // if anyNotMineFromAddress == nil, let address = input.address { + // anyNotMineFromAddress = input.address + // } + } + + for output in transaction.outputs { + guard output.value > 0 else { + continue + } + + outputsTotalValue += output.value + + if output.mine { + myOutputsTotalValue += output.value + if output.changeOutput { + myChangeOutputsTotalValue += output.value + } + } + + to.append(TransactionInputOutput( + mine: output.mine, address: output.address, value: output.value, + changeOutput: output.changeOutput, pluginId: output.pluginId, pluginData: output.pluginData + )) + + // if let pluginId = output.pluginId, pluginId == HodlerPlugin.id, + // let hodlerOutputData = output.pluginData as? HodlerOutputData, + // let approximateUnlockTime = hodlerOutputData.approximateUnlockTime { + // + // lockInfo = (lockedUntil: Date(timeIntervalSince1970: Double(approximateUnlockTime)), originalAddress: hodlerOutputData.addressString) + // } + // if anyNotMineToAddress == nil, let address = output.address { + // anyNotMineToAddress = output.address + // } + } + + var amount = myOutputsTotalValue - myInputsTotalValue + + if allInputsMine, let fee = transaction.fee { + amount += fee } - let toAddresses = transaction.to.map { - TransactionAddress(address: $0.address, mine: $0.mine) + if amount > 0 { + type = .incoming + } else if amount < 0 { + type = .outgoing + } else { + type = .sentToSelf(enteredAmount: Decimal(myOutputsTotalValue - myChangeOutputsTotalValue) / coinRate) } + // let from = type == .incoming ? anyNotMineFromAddress : nil + // let to = type == .outgoing ? anyNotMineToAddress : nil + return TransactionRecord( + uid: transaction.uid, transactionHash: transaction.transactionHash, transactionIndex: transaction.transactionIndex, - amount: Decimal(transaction.amount) / coinRate, - timestamp: Double(transaction.timestamp), - from: fromAddresses, - to: toAddresses, + interTransactionIndex: 0, + status: TransactionStatus(rawValue: transaction.status.rawValue) ?? TransactionStatus.new, + type: type, blockHeight: transaction.blockHeight, - transactionExtraType: nil + amount: Decimal(abs(amount)) / coinRate, + fee: transaction.fee.map { Decimal($0) / coinRate }, + date: Date(timeIntervalSince1970: Double(transaction.timestamp)), + from: from, + to: to, + conflictingHash: transaction.conflictingHash ) } @@ -55,10 +122,10 @@ class BaseAdapter { return NSDecimalNumber(decimal: coinValue).rounding(accordingToBehavior: handler).intValue } - func transactionsSingle(fromHash: String?, limit: Int) -> Single<[TransactionRecord]> { - return abstractKit.transactions(fromHash: fromHash, limit: limit) + func transactionsSingle(fromUid: String?, limit: Int) -> Single<[TransactionRecord]> { + abstractKit.transactions(fromUid: fromUid, limit: limit) .map { [weak self] transactions -> [TransactionRecord] in - return transactions.compactMap { + transactions.compactMap { self?.transactionRecord(fromTransaction: $0) } } @@ -69,41 +136,47 @@ class BaseAdapter { extension BaseAdapter { var lastBlockObservable: Observable { - return lastBlockSignal.asObservable().throttle(DispatchTimeInterval.milliseconds(200), scheduler: ConcurrentDispatchQueueScheduler(qos: .userInitiated)) + lastBlockSignal.asObservable().throttle(DispatchTimeInterval.milliseconds(200), scheduler: ConcurrentDispatchQueueScheduler(qos: .userInitiated)) } var syncStateObservable: Observable { - return syncStateSignal.asObservable().throttle(DispatchTimeInterval.milliseconds(200), scheduler: ConcurrentDispatchQueueScheduler(qos: .userInitiated)) + syncStateSignal.asObservable().throttle(DispatchTimeInterval.milliseconds(200), scheduler: ConcurrentDispatchQueueScheduler(qos: .userInitiated)) } var balanceObservable: Observable { - return balanceSignal.asObservable() + balanceSignal.asObservable() } var transactionsObservable: Observable { - return transactionsSignal.asObservable() + transactionsSignal.asObservable() } func start() { - DispatchQueue.global(qos: .userInitiated).async { - self.abstractKit.start() - } + self.abstractKit.start() } - var balance: Decimal { - return Decimal(abstractKit.balance) / coinRate + func refresh() { + self.abstractKit.start() + } + + var spendableBalance: Decimal { + Decimal(abstractKit.balance.spendable) / coinRate + } + + var unspendableBalance: Decimal { + Decimal(abstractKit.balance.unspendable) / coinRate } var lastBlockInfo: BlockInfo? { - return abstractKit.lastBlockInfo + abstractKit.lastBlockInfo } var syncState: BitcoinCore.KitState { - return abstractKit.syncState + abstractKit.syncState } - func receiveAddress(for type: ScriptType) -> String { - return abstractKit.receiveAddress(for: type) + func receiveAddress() -> String { + abstractKit.receiveAddress() } func validate(address: String) throws { @@ -116,12 +189,12 @@ extension BaseAdapter { } } - func sendSingle(to address: String, amount: Decimal) -> Single { + func sendSingle(to address: String, amount: Decimal, sortType: TransactionDataSortType, pluginData: [UInt8: IPluginData] = [:]) -> Single { let satoshiAmount = convertToSatoshi(value: amount) return Single.create { [unowned self] observer in do { - _ = try self.abstractKit.send(to: address, value: satoshiAmount, feeRate: self.feeRate) + _ = try self.abstractKit.send(to: address, value: satoshiAmount, feeRate: self.feeRate, sortType: sortType, pluginData: pluginData) observer(.success(())) } catch { observer(.error(error)) @@ -131,22 +204,43 @@ extension BaseAdapter { } } - func availableBalance(for address: String?) -> Decimal { - return max(0, balance - fee(for: balance, address: address)) + func availableBalance(for address: String?, pluginData: [UInt8: IPluginData] = [:]) -> Decimal { + let amount = (try? abstractKit.maxSpendableValue(toAddress: address, feeRate: feeRate, pluginData: pluginData)) ?? 0 + return Decimal(amount) / coinRate + } + + func maxSpendLimit(pluginData: [UInt8: IPluginData]) -> Int? { + do { + return try abstractKit.maxSpendLimit(pluginData: pluginData) + } catch { + return 0 + } + } + + func minSpendableAmount(for address: String?) -> Decimal { + Decimal(abstractKit.minSpendableValue(toAddress: address)) / coinRate } - func fee(for value: Decimal, address: String?) -> Decimal { + func fee(for value: Decimal, address: String?, pluginData: [UInt8: IPluginData] = [:]) -> Decimal { do { let amount = convertToSatoshi(value: value) - let fee = try abstractKit.fee(for: amount, toAddress: address, senderPay: true, feeRate: feeRate) + let fee = try abstractKit.fee(for: amount, toAddress: address, feeRate: feeRate, pluginData: pluginData) return Decimal(fee) / coinRate - } catch BitcoinCoreErrors.UnspentOutputSelection.notEnough(let maxFee) { - return Decimal(maxFee) / coinRate } catch { return 0 } } + func printDebugs() { + print(abstractKit.debugInfo) + print() + print(abstractKit.statusInfo) + } + + func rawTransaction(transactionHash: String) -> String? { + abstractKit.rawTransaction(transactionHash: transactionHash) + } + } enum SendError: Error { diff --git a/Demo/Demo/Adapters/BitcoinAdapter.swift b/Demo/Demo/Adapters/BitcoinAdapter.swift index e41cf06..bfaedd6 100644 --- a/Demo/Demo/Adapters/BitcoinAdapter.swift +++ b/Demo/Demo/Adapters/BitcoinAdapter.swift @@ -1,14 +1,14 @@ import BitcoinKit import BitcoinCore +import HdWalletKit import RxSwift class BitcoinAdapter: BaseAdapter { let bitcoinKit: BitcoinKit - override var changeableAddressType: Bool { return true } - init(words: [String], testMode: Bool, syncMode: BitcoinCore.SyncMode) { + init(words: [String], bip: Bip, testMode: Bool, syncMode: BitcoinCore.SyncMode) { let networkType: BitcoinKit.NetworkType = testMode ? .testNet : .mainNet - bitcoinKit = try! BitcoinKit(withWords: words, walletId: "walletId", syncMode: syncMode, networkType: networkType, minLogLevel: Configuration.shared.minLogLevel) + bitcoinKit = try! BitcoinKit(withWords: words, bip: bip, walletId: "walletId", syncMode: syncMode, networkType: networkType, confirmationsThreshold: 1, minLogLevel: Configuration.shared.minLogLevel) super.init(name: "Bitcoin", coinCode: "BTC", abstractKit: bitcoinKit) bitcoinKit.delegate = self @@ -29,7 +29,7 @@ extension BitcoinAdapter: BitcoinCoreDelegate { transactionsSignal.notify() } - func balanceUpdated(balance: Int) { + func balanceUpdated(balance: BalanceInfo) { balanceSignal.notify() } diff --git a/Demo/Demo/Adapters/BitcoinCashAdapter.swift b/Demo/Demo/Adapters/BitcoinCashAdapter.swift index ed0cb1f..5a939c0 100644 --- a/Demo/Demo/Adapters/BitcoinCashAdapter.swift +++ b/Demo/Demo/Adapters/BitcoinCashAdapter.swift @@ -28,7 +28,7 @@ extension BitcoinCashAdapter: BitcoinCoreDelegate { transactionsSignal.notify() } - func balanceUpdated(balance: Int) { + func balanceUpdated(balance: BalanceInfo) { balanceSignal.notify() } diff --git a/Demo/Demo/AppDelegate.swift b/Demo/Demo/AppDelegate.swift new file mode 100644 index 0000000..31f53c4 --- /dev/null +++ b/Demo/Demo/AppDelegate.swift @@ -0,0 +1,34 @@ +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + let controller = Manager.shared.savedWords == nil ? UINavigationController(rootViewController: WordsController()) : MainController() + + window = UIWindow(frame: UIScreen.main.bounds) + window?.makeKeyAndVisible() + window?.backgroundColor = .white + window?.rootViewController = controller + + return true + } + + func applicationWillResignActive(_ application: UIApplication) { + } + + func applicationDidEnterBackground(_ application: UIApplication) { + } + + func applicationWillEnterForeground(_ application: UIApplication) { + } + + func applicationDidBecomeActive(_ application: UIApplication) { + } + + func applicationWillTerminate(_ application: UIApplication) { + } + +} diff --git a/Demo/Demo/Configuration.swift b/Demo/Demo/Configuration.swift index 7dd319f..a7d9fd7 100644 --- a/Demo/Demo/Configuration.swift +++ b/Demo/Demo/Configuration.swift @@ -1,4 +1,5 @@ import BitcoinCore +import HsToolKit class Configuration { static let shared = Configuration() @@ -7,7 +8,6 @@ class Configuration { let testNet = true let mainNet = false let defaultWords = [ - "used ugly meat glad balance divorce inner artwork hire invest already piano", "clock economy moon breeze wood trust obtain scan sing gift frog else", "current force clump paper shrug extra zebra employ prefer upon mobile hire", "popular game latin harvest silly excess much valid elegant illness edge silk", diff --git a/Demo/Demo/Controllers/BalanceController.swift b/Demo/Demo/Controllers/BalanceController.swift index 0555276..a2dd743 100644 --- a/Demo/Demo/Controllers/BalanceController.swift +++ b/Demo/Demo/Controllers/BalanceController.swift @@ -1,92 +1,98 @@ import UIKit import RxSwift -import AtomicSwapCore class BalanceController: UITableViewController { private let disposeBag = DisposeBag() private var adapterDisposeBag = DisposeBag() - + private var adapters = [BaseAdapter]() - + override func viewDidLoad() { super.viewDidLoad() - + navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Logout", style: .plain, target: self, action: #selector(logout)) navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Start", style: .plain, target: self, action: #selector(start)) - + tableView.register(UINib(nibName: String(describing: BalanceCell.self), bundle: Bundle(for: BalanceCell.self)), forCellReuseIdentifier: String(describing: BalanceCell.self)) tableView.tableFooterView = UIView() tableView.separatorInset = .zero - + tableView.estimatedRowHeight = 0 - + Manager.shared.adapterSignal - .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) - .observeOn(MainScheduler.instance) - .subscribe(onNext: { [weak self] in - self?.updateAdapters() - }) - .disposed(by: disposeBag) - + .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) + .observeOn(MainScheduler.instance) + .subscribe(onNext: { [weak self] in + self?.updateAdapters() + }) + .disposed(by: disposeBag) + updateAdapters() } - + private func updateAdapters() { adapters = Manager.shared.adapters tableView.reloadData() - + adapterDisposeBag = DisposeBag() - + for (index, adapter) in adapters.enumerated() { Observable.merge([adapter.lastBlockObservable, adapter.syncStateObservable, adapter.balanceObservable]) - .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) - .observeOn(MainScheduler.instance) - .subscribe(onNext: { [weak self] in - self?.update(index: index) - }) - .disposed(by: adapterDisposeBag) + .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) + .observeOn(MainScheduler.instance) + .subscribe(onNext: { [weak self] in + self?.update(index: index) + }) + .disposed(by: adapterDisposeBag) } } - + @objc func logout() { Manager.shared.logout() - + if let window = UIApplication.shared.keyWindow { UIView.transition(with: window, duration: 0.5, options: .transitionCrossDissolve, animations: { window.rootViewController = UINavigationController(rootViewController: WordsController()) }) } } - + @objc func start() { Manager.shared.adapters.forEach { $0.start() } - try? Manager.shared.swapKit.proceedNext() + if let button = navigationItem.rightBarButtonItem { + button.title = "Refresh" + button.action = #selector(refresh) + } } - + + @objc func refresh() { + Manager.shared.adapters.forEach { $0.refresh() } + } + @IBAction func showDebugInfo() { -// print(Manager.shared.ethereumKit.debugInfo) + // print(Manager.shared.ethereumKit.debugInfo) } - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return adapters.count } - + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 160 + return 220 } - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return tableView.dequeueReusableCell(withIdentifier: String(describing: BalanceCell.self), for: indexPath) } - + override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { if let cell = cell as? BalanceCell { cell.bind(adapter: adapters[indexPath.row]) } } - + private func update(index: Int) { tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .none) } - + } diff --git a/Demo/Demo/Controllers/Cells/BalanceCell.swift b/Demo/Demo/Controllers/Cells/BalanceCell.swift index 71c7db7..1b40ed9 100644 --- a/Demo/Demo/Controllers/Cells/BalanceCell.swift +++ b/Demo/Demo/Controllers/Cells/BalanceCell.swift @@ -12,17 +12,23 @@ class BalanceCell: UITableViewCell { @IBOutlet weak var nameLabel: UILabel? @IBOutlet weak var titleLabel: UILabel? @IBOutlet weak var valueLabel: UILabel? + @IBOutlet weak var errorLabel: UILabel? func bind(adapter: BaseAdapter) { let syncStateString: String + var errorString = "" switch adapter.syncState { case .synced: syncStateString = "Synced!" + case .apiSyncing(let transactionsFound): syncStateString = "API Syncing \(transactionsFound) txs" case .syncing(let progress): syncStateString = "Syncing \(Int(progress * 100))%" - case .notSynced: syncStateString = "Not Synced" + case .notSynced(let error): + syncStateString = "Not Synced" + errorString = "\(error)" } nameLabel?.text = adapter.name + errorLabel?.text = errorString var lastBlockHeightString = "n/a" var lastBlockDateString = "n/a" @@ -40,14 +46,16 @@ class BalanceCell: UITableViewCell { Sync state: Last block: - Balance: + Spendable balance: + Unspendable balance: """, alignment: .left, label: titleLabel) set(string: """ \(syncStateString) \(lastBlockHeightString) \(lastBlockDateString) - \(adapter.balance) \(adapter.coinCode) + \(adapter.spendableBalance.formattedAmount) \(adapter.coinCode) + \(adapter.unspendableBalance.formattedAmount) \(adapter.coinCode) """, alignment: .right, label: valueLabel) } diff --git a/Demo/Demo/Controllers/Cells/BalanceCell.xib b/Demo/Demo/Controllers/Cells/BalanceCell.xib index 2086228..0ba30a5 100644 --- a/Demo/Demo/Controllers/Cells/BalanceCell.xib +++ b/Demo/Demo/Controllers/Cells/BalanceCell.xib @@ -1,37 +1,36 @@ - - - - + + - + + - - + + - + + + + + + - + diff --git a/Demo/Demo/Controllers/Cells/TransactionCell.swift b/Demo/Demo/Controllers/Cells/TransactionCell.swift index 9d4f15f..3507b7c 100644 --- a/Demo/Demo/Controllers/Cells/TransactionCell.swift +++ b/Demo/Demo/Controllers/Cells/TransactionCell.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import Hodler class TransactionCell: UITableViewCell { static let dateFormatter: DateFormatter = { @@ -12,6 +13,7 @@ class TransactionCell: UITableViewCell { @IBOutlet weak var titleLabel: UILabel? @IBOutlet weak var valueLabel: UILabel? @IBOutlet weak var transactionTypeLabel: UILabel? + private let coinRate: Decimal = pow(10, 8) func bind(transaction: TransactionRecord, coinCode: String, lastBlockHeight: Int?) { var confirmations = "n/a" @@ -21,43 +23,64 @@ class TransactionCell: UITableViewCell { } let from = transaction.from.map { from -> String in - var string = format(hash: from.address) + var string = from.address.flatMap { format(hash: $0) } ?? "Unknown address" if from.mine { - string += " (mine)" + string += "(mine)" + } + if let value = from.value { + string += "(\((Decimal(value) / coinRate).formattedAmount))" } return string } let to = transaction.to.map { to -> String in - var string = format(hash: to.address) + var string = to.address.flatMap { format(hash: $0) } ?? "Unknown address" if to.mine { - string += " (mine)" + string += "(mine)" + } + if to.changeOutput { + string += "(change)" + } + if let value = to.value { + string += "(\((Decimal(value) / coinRate).formattedAmount))" + } + if let pluginId = to.pluginId, let pluginData = to.pluginData, pluginId == HodlerPlugin.id, let hodlerData = pluginData as? HodlerOutputData { + string += "\nLocked Until: \(TransactionCell.dateFormatter.string(from: Date(timeIntervalSince1970: Double(hodlerData.approximateUnlockTime!)))) <-" + string += "\nOriginal: \(format(hash: hodlerData.addressString)) <-" } return string } set(string: """ Tx Hash: + Tx Status: Tx Index: Date: + Type: Amount: + Fee: Block: + ConflictingHash: Confirmations: \(from.map { _ in "From:" }.joined(separator: "\n")) - \(to.map { _ in "To:" }.joined(separator: "\n")) + \(transaction.to.map { "To:\(String(repeating: "\n", count: TransactionCell.rowsCount(address: $0)))" }.joined(separator: "")) """, alignment: .left, label: titleLabel) set(string: """ \(format(hash: transaction.transactionHash)) + \(transaction.status) \(transaction.transactionIndex) - \(TransactionCell.dateFormatter.string(from: Date(timeIntervalSince1970: transaction.timestamp))) - \(transaction.amount) \(coinCode) + \(TransactionCell.dateFormatter.string(from: transaction.date)) + \(transaction.type.description) + \(transaction.amount.formattedAmount) \(coinCode) + \(transaction.fee?.formattedAmount ?? "") \(coinCode) \(transaction.blockHeight.map { "# \($0)" } ?? "n/a") + \(format(hash: transaction.conflictingHash ?? "n/a")) \(confirmations) \(from.joined(separator: "\n")) \(to.joined(separator: "\n")) """, alignment: .right, label: valueLabel) - + transactionTypeLabel?.isHidden = transaction.transactionExtraType == nil transactionTypeLabel?.text = transaction.transactionExtraType } @@ -78,7 +101,32 @@ class TransactionCell: UITableViewCell { return hash } - return "\(hash[.. Int { + var rowsCount = 1 + + if let pluginId = address.pluginId, pluginId == HodlerPlugin.id { + rowsCount += 2 + } + + return rowsCount + } + + static func rowHeight(for transaction: TransactionRecord) -> Int { + let addressRowsCount = transaction.to.reduce(0) { $0 + rowsCount(address: $1) } + transaction.from.count + var height = (addressRowsCount + 9) * 21 + 10 + + if transaction.transactionExtraType != nil { + height += 20 + } + + return height } } diff --git a/Demo/Demo/Controllers/Cells/TransactionCell.xib b/Demo/Demo/Controllers/Cells/TransactionCell.xib index 1106fb8..eb1e761 100644 --- a/Demo/Demo/Controllers/Cells/TransactionCell.xib +++ b/Demo/Demo/Controllers/Cells/TransactionCell.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -16,17 +14,17 @@ - + - - + - + diff --git a/Demo/Demo/Controllers/ReceiveController.swift b/Demo/Demo/Controllers/ReceiveController.swift index 01d9354..cf51e8a 100644 --- a/Demo/Demo/Controllers/ReceiveController.swift +++ b/Demo/Demo/Controllers/ReceiveController.swift @@ -6,8 +6,7 @@ class ReceiveController: UIViewController { private let disposeBag = DisposeBag() @IBOutlet weak var addressLabel: UILabel? - @IBOutlet weak var addressTypeControl: UISegmentedControl! - + private var adapters = [BaseAdapter]() private let segmentedControl = UISegmentedControl() @@ -19,8 +18,6 @@ class ReceiveController: UIViewController { addressLabel?.layer.cornerRadius = 8 addressLabel?.clipsToBounds = true - segmentedControl.addTarget(self, action: #selector(onSegmentChanged), for: .valueChanged) - Manager.shared.adapterSignal .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) .observeOn(MainScheduler.instance) @@ -30,6 +27,7 @@ class ReceiveController: UIViewController { .disposed(by: disposeBag) updateAdapters() + segmentedControl.addTarget(self, action: #selector(onSegmentChanged), for: .valueChanged) } private func updateAdapters() { @@ -45,9 +43,6 @@ class ReceiveController: UIViewController { segmentedControl.selectedSegmentIndex = 0 segmentedControl.sendActions(for: .valueChanged) - - addressTypeControl.selectedSegmentIndex = 0 - addressTypeControl.isHidden = false } override func viewWillAppear(_ animated: Bool) { @@ -56,32 +51,19 @@ class ReceiveController: UIViewController { segmentedControl.sendActions(for: .valueChanged) } - func type(segment: Int) -> ScriptType { - switch segment { - case 1: return .p2wpkh - case 2: return .p2wpkhSh - default: return .p2pkh - } - } - @objc func onSegmentChanged() { - addressTypeControl.isHidden = segmentedControl.selectedSegmentIndex != 0 - addressTypeControl.selectedSegmentIndex = 0 updateAddress() - if let adapter = currentAdapter { - print(adapter.debugInfo) - } + currentAdapter?.printDebugs() } func updateAddress() { - let segment = addressTypeControl.selectedSegmentIndex - addressLabel?.text = " \(currentAdapter?.receiveAddress(for: type(segment: segment)) ?? "") " + addressLabel?.text = " \(currentAdapter?.receiveAddress() ?? "") " } @IBAction func onAddressTypeChanged(_ sender: Any) { updateAddress() } - + @IBAction func copyToClipboard() { if let address = addressLabel?.text?.trimmingCharacters(in: .whitespaces) { UIPasteboard.general.setValue(address, forPasteboardType: "public.plain-text") diff --git a/Demo/Demo/Controllers/ReceiveController.xib b/Demo/Demo/Controllers/ReceiveController.xib index 5492898..cda4d9d 100644 --- a/Demo/Demo/Controllers/ReceiveController.xib +++ b/Demo/Demo/Controllers/ReceiveController.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -13,7 +11,6 @@ - @@ -23,13 +20,13 @@ - - - - - - - - - - - - - - - + - - + diff --git a/Demo/Demo/Controllers/SendController.swift b/Demo/Demo/Controllers/SendController.swift index f6a012b..ec9dd15 100644 --- a/Demo/Demo/Controllers/SendController.swift +++ b/Demo/Demo/Controllers/SendController.swift @@ -1,5 +1,7 @@ import UIKit import RxSwift +import Hodler +import BitcoinCore class SendController: UIViewController { private let disposeBag = DisposeBag() @@ -7,14 +9,24 @@ class SendController: UIViewController { @IBOutlet weak var addressTextField: UITextField? @IBOutlet weak var amountTextField: UITextField? @IBOutlet weak var coinLabel: UILabel? + @IBOutlet weak var feeLabel: UILabel? + @IBOutlet weak var timeLockSwitch: UISwitch? + @IBOutlet weak var picker: UIPickerView? + + private var timeIntervalStrings = ["Hour", "Month", "Half Year", "Year"] + private var timeIntervals: [HodlerPlugin.LockTimeInterval] = [.hour, .month, .halfYear, .year] + private var selectedTimeInterval: HodlerPlugin.LockTimeInterval = .hour private var adapters = [BaseAdapter]() private let segmentedControl = UISegmentedControl() + private var timeLockEnabled = false override func viewDidLoad() { super.viewDidLoad() segmentedControl.addTarget(self, action: #selector(onSegmentChanged), for: .valueChanged) + picker?.dataSource = self + picker?.delegate = self Manager.shared.adapterSignal .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) @@ -42,6 +54,32 @@ class SendController: UIViewController { segmentedControl.sendActions(for: .valueChanged) } + private func updateFee() { + var address: String? = nil + + if let addressStr = addressTextField?.text { + do { + try currentAdapter?.validate(address: addressStr) + address = addressStr + } catch { + } + } + + guard let amountString = amountTextField?.text, let amount = Decimal(string: amountString) else { + feeLabel?.text = "Fee: " + return + } + + var pluginData = [UInt8: IPluginData]() + if timeLockEnabled { + pluginData[HodlerPlugin.id] = HodlerData(lockTimeInterval: self.selectedTimeInterval) + } + + if let fee = currentAdapter?.fee(for: amount, address: address, pluginData: pluginData) { + feeLabel?.text = "Fee: \(fee.formattedAmount)" + } + } + override func touchesEnded(_ touches: Set, with event: UIEvent?) { super.touchesEnded(touches, with: event) @@ -50,6 +88,59 @@ class SendController: UIViewController { @objc func onSegmentChanged() { coinLabel?.text = currentAdapter?.coinCode + updateFee() + } + + @IBAction func onAddressEditEnded(_ sender: Any) { + updateFee() + } + + @IBAction func onAmountEditEnded(_ sender: Any) { + updateFee() + } + + @IBAction func onTimeLockSwitchToggle(_ sender: Any) { + timeLockEnabled = !timeLockEnabled + updateFee() + } + + @IBAction func setMaxAmount() { + var address: String? = nil + + if let addressStr = addressTextField?.text { + do { + try currentAdapter?.validate(address: addressStr) + address = addressStr + } catch { + } + } + + var pluginData = [UInt8: IPluginData]() + if timeLockEnabled { + pluginData[HodlerPlugin.id] = HodlerData(lockTimeInterval: self.selectedTimeInterval) + } + + if let maxAmount = currentAdapter?.availableBalance(for: address, pluginData: pluginData) { + amountTextField?.text = "\(maxAmount)" + onAmountEditEnded(0) + } + } + + @IBAction func setMinAmount() { + var address: String? = nil + + if let addressStr = addressTextField?.text { + do { + try currentAdapter?.validate(address: addressStr) + address = addressStr + } catch { + } + } + + if let minAmount = currentAdapter?.minSpendableAmount(for: address) { + amountTextField?.text = "\(minAmount)" + onAmountEditEnded(0) + } } @IBAction func send() { @@ -69,7 +160,12 @@ class SendController: UIViewController { return } - currentAdapter?.sendSingle(to: address, amount: amount) + var pluginData = [UInt8: IPluginData]() + if timeLockEnabled { + pluginData[HodlerPlugin.id] = HodlerData(lockTimeInterval: self.selectedTimeInterval) + } + + currentAdapter?.sendSingle(to: address, amount: amount, sortType: .shuffle, pluginData: pluginData) .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) .observeOn(MainScheduler.instance) .subscribe(onSuccess: { [weak self] _ in @@ -90,7 +186,7 @@ class SendController: UIViewController { } private func showSuccess(address: String, amount: Decimal) { - let alert = UIAlertController(title: "Success", message: "\(amount.description) sent to \(address)", preferredStyle: .alert) + let alert = UIAlertController(title: "Success", message: "\(amount.formattedAmount) sent to \(address)", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .cancel)) present(alert, animated: true) } @@ -104,3 +200,31 @@ class SendController: UIViewController { } } + +extension SendController: UIPickerViewDataSource { + public func numberOfComponents(in pickerView: UIPickerView) -> Int { + 1 + } + + public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + timeIntervals.count + } +} + +extension SendController: UIPickerViewDelegate { + public func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { + 130 + } + + public func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { + 30 + } + + public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + timeIntervalStrings[row] + } + + public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + selectedTimeInterval = timeIntervals[row] + } +} diff --git a/Demo/Demo/Controllers/SendController.xib b/Demo/Demo/Controllers/SendController.xib index 4bd4477..83477e6 100644 --- a/Demo/Demo/Controllers/SendController.xib +++ b/Demo/Demo/Controllers/SendController.xib @@ -1,19 +1,19 @@ - - - - + + - + + - + + @@ -23,60 +23,103 @@ - - + + + + + + + - - - + + + + + + + + + + + - - + + + + + + + + - + diff --git a/Demo/Demo/Controllers/SwapController.swift b/Demo/Demo/Controllers/SwapController.swift index ff4930a..901525e 100644 --- a/Demo/Demo/Controllers/SwapController.swift +++ b/Demo/Demo/Controllers/SwapController.swift @@ -104,6 +104,7 @@ class SwapController: UIViewController { do { let request = try swapKit.createSwapRequest(haveCoinCode: haveCoinAdapter.coinCode, wantCoinCode: wantCoinAdapter.coinCode, rate: Double(truncating: rate as NSNumber), amount: Double(truncating: value as NSNumber)) requestStr = codec.getString(from: request) + self.view.endEditing(true) } catch { show(error: error.localizedDescription) return @@ -123,6 +124,7 @@ class SwapController: UIViewController { let request = try codec.getRequest(from: requestStr) let response = try swapKit.createSwapResponse(from: request) responseStr = codec.getString(from: response) + self.view.endEditing(true) } catch { show(error: error.localizedDescription) return @@ -140,6 +142,7 @@ class SwapController: UIViewController { do { let response = try codec.getResponse(from: responseStr) try swapKit.initiateSwap(from: response) + self.view.endEditing(true) } catch { show(error: error.localizedDescription) return @@ -151,13 +154,7 @@ class SwapController: UIViewController { } private func show(error: String) { - let alert = UIAlertController(title: "Send Error", message: error, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .cancel)) - present(alert, animated: true) - } - - private func showSuccess(address: String, amount: Decimal) { - let alert = UIAlertController(title: "Success", message: "\(amount.description) sent to \(address)", preferredStyle: .alert) + let alert = UIAlertController(title: "Error", message: error, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .cancel)) present(alert, animated: true) } diff --git a/Demo/Demo/Controllers/SwapController.xib b/Demo/Demo/Controllers/SwapController.xib index e06dc0c..2e2b3bf 100644 --- a/Demo/Demo/Controllers/SwapController.xib +++ b/Demo/Demo/Controllers/SwapController.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -28,46 +26,44 @@ - - + - + - + - - + - - - + - @@ -154,8 +148,8 @@ - - + + @@ -165,17 +159,17 @@ - + - + - + @@ -183,7 +177,7 @@ - + @@ -196,7 +190,7 @@ - + diff --git a/Demo/Demo/Controllers/TransactionsController.swift b/Demo/Demo/Controllers/TransactionsController.swift index 144741b..9958bfc 100644 --- a/Demo/Demo/Controllers/TransactionsController.swift +++ b/Demo/Demo/Controllers/TransactionsController.swift @@ -76,15 +76,15 @@ class TransactionsController: UITableViewController { } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return transactions.count + transactions.count } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 220 + CGFloat(TransactionCell.rowHeight(for: transactions[indexPath.row])) } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - return tableView.dequeueReusableCell(withIdentifier: String(describing: TransactionCell.self), for: indexPath) + tableView.dequeueReusableCell(withIdentifier: String(describing: TransactionCell.self), for: indexPath) } override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { @@ -102,7 +102,12 @@ class TransactionsController: UITableViewController { } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - UIPasteboard.general.setValue(transactions[indexPath.row].transactionHash, forPasteboardType: "public.plain-text") + let transactionHash = transactions[indexPath.row].transactionHash + + UIPasteboard.general.setValue(transactionHash, forPasteboardType: "public.plain-text") + + print("Transaction Hash: \(transactionHash)") + print("Raw Transaction: \(currentAdapter?.rawTransaction(transactionHash: transactionHash) ?? "")") let alert = UIAlertController(title: "Success", message: "Transaction Hash copied", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .cancel)) @@ -126,9 +131,9 @@ class TransactionsController: UITableViewController { loading = true - let fromHash = transactions.last?.transactionHash + let fromUid = transactions.last?.uid - currentAdapter?.transactionsSingle(fromHash: fromHash, limit: limit) + currentAdapter?.transactionsSingle(fromUid: fromUid, limit: limit) .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) .observeOn(MainScheduler.instance) .subscribe(onSuccess: { [weak self] transactions in diff --git a/Demo/Demo/Core/Extensions.swift b/Demo/Demo/Core/Extensions.swift new file mode 100644 index 0000000..902b69e --- /dev/null +++ b/Demo/Demo/Core/Extensions.swift @@ -0,0 +1,12 @@ +import Foundation + +extension Decimal { + var formattedAmount: String { + let formatter = NumberFormatter() + formatter.generatesDecimalNumbers = true + formatter.minimumIntegerDigits = 1 + formatter.minimumFractionDigits = 8 + formatter.maximumFractionDigits = 8 + return formatter.string(from: self as NSDecimalNumber)! + } +} \ No newline at end of file diff --git a/Demo/Demo/Core/Manager.swift b/Demo/Demo/Core/Manager.swift index ac1e31c..75b682f 100644 --- a/Demo/Demo/Core/Manager.swift +++ b/Demo/Demo/Core/Manager.swift @@ -45,7 +45,7 @@ class Manager { private func initAdapters(words: [String], syncMode: BitcoinCore.SyncMode) { let configuration = Configuration.shared - let bitcoinAdapter = BitcoinAdapter(words: words, testMode: configuration.testNet, syncMode: syncMode) + let bitcoinAdapter = BitcoinAdapter(words: words, bip: .bip44, testMode: configuration.testNet, syncMode: syncMode) let bitcoinCashAdapter = BitcoinCashAdapter(words: words, testMode: configuration.testNet, syncMode: .newWallet) // let dashAdapter = DashAdapter(words: words, testMode: configuration.testNet, syncMode: syncMode) diff --git a/Demo/Demo/Core/TransactionRecord.swift b/Demo/Demo/Core/TransactionRecord.swift index fe683b9..cad09c5 100644 --- a/Demo/Demo/Core/TransactionRecord.swift +++ b/Demo/Demo/Core/TransactionRecord.swift @@ -1,21 +1,44 @@ import Foundation struct TransactionRecord { + let uid: String let transactionHash: String let transactionIndex: Int - - let amount: Decimal - let timestamp: Double - - let from: [TransactionAddress] - let to: [TransactionAddress] - + let interTransactionIndex: Int + let status: TransactionStatus + let type: TransactionType let blockHeight: Int? - + let amount: Decimal + let fee: Decimal? + let date: Date + let from: [TransactionInputOutput] + let to: [TransactionInputOutput] + let conflictingHash: String? var transactionExtraType: String? } -struct TransactionAddress { - let address: String +struct TransactionInputOutput { let mine: Bool + let address: String? + let value: Int? + let changeOutput: Bool + let pluginId: UInt8? + let pluginData: Any? +} + +enum TransactionStatus: Int { + case new, relayed, invalid +} + +enum TransactionType { + case incoming, outgoing, sentToSelf(enteredAmount: Decimal) + + var description: String { + switch self { + case .incoming: return "incoming" + case .outgoing: return "outgoing" + case .sentToSelf(let possibleEnteredAmount): return "sentToSelf: \(possibleEnteredAmount.formattedAmount)" + } + } + } diff --git a/Podfile b/Podfile index 817232b..0c41ccf 100644 --- a/Podfile +++ b/Podfile @@ -14,14 +14,14 @@ project 'AtomicSwapBitcoinProvider/AtomicSwapBitcoinProvider' target :AtomicSwapCore do project 'AtomicSwapCore/AtomicSwapCore' - pod 'HSCryptoKit', '~> 1.4' + pod 'OpenSslKit.swift', '~> 1.0' pod 'GRDB.swift', '~> 4.0' end target :AtomicSwapBitcoinProvider do project 'AtomicSwapBitcoinProvider/AtomicSwapBitcoinProvider' - pod 'HSCryptoKit', '~> 1.4' + pod 'OpenSslKit.swift', '~> 1.0' pod 'BitcoinCore.swift', git: 'https://github.com/horizontalsystems/bitcoin-kit-ios/' pod 'BitcoinKit.swift', git: 'https://github.com/horizontalsystems/bitcoin-kit-ios/' pod 'BitcoinCashKit.swift', git: 'https://github.com/horizontalsystems/bitcoin-kit-ios/' @@ -31,8 +31,9 @@ target :Demo do project 'Demo/Demo' pod 'RSSelectionMenu' - pod 'HSHDWalletKit', '~> 1.1' + pod 'HSHDWalletKit', '~> 1' pod 'RxSwift', '~> 5.0' + pod 'HsToolKit.swift', '~> 1' pod 'BitcoinCore.swift', git: 'https://github.com/horizontalsystems/bitcoin-kit-ios/' pod 'BitcoinKit.swift', git: 'https://github.com/horizontalsystems/bitcoin-kit-ios/' diff --git a/Podfile.lock b/Podfile.lock index 0431fae..2d7ddd8 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,44 +1,61 @@ PODS: - - Alamofire (4.8.2) - - BigInt (4.0.0) - - BitcoinCashKit.swift (0.7.0): - - Alamofire (~> 4.0) - - BigInt (~> 4.0) - - BitcoinCore.swift (~> 0.7.0) + - Alamofire (5.2.1) + - BigInt (5.0.0) + - BitcoinCashKit.swift (0.14.3): + - BigInt (~> 5.0) + - BitcoinCore.swift (~> 0.14) - GRDB.swift (~> 4.0) - - HSCryptoKit (~> 1.4) - - HSHDWalletKit (~> 1.1) + - HdWalletKit.swift (~> 1.5) - ObjectMapper (~> 3.0) + - OpenSslKit.swift (~> 1.0) - RxSwift (~> 5.0) - - BitcoinCore.swift (0.7.0): - - Alamofire (~> 4.0) - - BigInt (~> 4.0) + - Secp256k1Kit.swift (~> 1.0) + - BitcoinCore.swift (0.14.3): + - BigInt (~> 5.0) - GRDB.swift (~> 4.0) - - HSCryptoKit (~> 1.4) - - HSHDWalletKit (~> 1.1) + - HdWalletKit.swift (~> 1.5) + - HsToolKit.swift (~> 1.0) - ObjectMapper (~> 3.0) + - OpenSslKit.swift (~> 1.0) - RxSwift (~> 5.0) - - BitcoinKit.swift (0.7.0): - - Alamofire (~> 4.0) - - BigInt (~> 4.0) - - BitcoinCore.swift (~> 0.7.0) + - Secp256k1Kit.swift (~> 1.0) + - BitcoinKit.swift (0.14.3): + - BigInt (~> 5.0) + - BitcoinCore.swift (~> 0.14) - GRDB.swift (~> 4.0) - - HSCryptoKit (~> 1.4) - - HSHDWalletKit (~> 1.1) + - HdWalletKit.swift (~> 1.5) + - Hodler.swift (~> 0.14) - ObjectMapper (~> 3.0) + - OpenSslKit.swift (~> 1.0) - RxSwift (~> 5.0) - - Cuckoo (1.0.6) - - GRDB.swift (4.0.1): - - GRDB.swift/standard (= 4.0.1) - - GRDB.swift/standard (4.0.1) - - HSCryptoKit (1.4.2) - - HSHDWalletKit (1.1): - - HSCryptoKit (~> 1.4) - - Nimble (8.0.2) - - ObjectMapper (3.5.1) - - Quick (2.1.0) - - RSSelectionMenu (6.0.3) - - RxSwift (5.0.0) + - Secp256k1Kit.swift (~> 1.0) + - Cuckoo (1.3.2): + - Cuckoo/Swift (= 1.3.2) + - Cuckoo/Swift (1.3.2) + - GRDB.swift (4.14.0): + - GRDB.swift/standard (= 4.14.0) + - GRDB.swift/standard (4.14.0) + - HdWalletKit.swift (1.5): + - OpenSslKit.swift (~> 1.0) + - Secp256k1Kit.swift (~> 1.0) + - Hodler.swift (0.14.3): + - BitcoinCore.swift (~> 0.14) + - OpenSslKit.swift (~> 1.0) + - Secp256k1Kit.swift (~> 1.0) + - HSHDWalletKit (1.3): + - OpenSslKit.swift (~> 1.0) + - Secp256k1Kit.swift (~> 1.0) + - HsToolKit.swift (1.0): + - Alamofire (~> 5.0) + - ObjectMapper (~> 3.0) + - RxSwift (~> 5.0) + - Nimble (8.0.9) + - ObjectMapper (3.5.3) + - OpenSslKit.swift (1.2) + - Quick (2.2.0) + - RSSelectionMenu (6.1.0) + - RxSwift (5.1.1) + - Secp256k1Kit.swift (1.0) DEPENDENCIES: - BitcoinCashKit.swift (from `https://github.com/horizontalsystems/bitcoin-kit-ios/`) @@ -46,26 +63,31 @@ DEPENDENCIES: - BitcoinKit.swift (from `https://github.com/horizontalsystems/bitcoin-kit-ios/`) - Cuckoo - GRDB.swift (~> 4.0) - - HSCryptoKit (~> 1.4) - - HSHDWalletKit (~> 1.1) + - HSHDWalletKit (~> 1) + - HsToolKit.swift (~> 1) - Nimble + - OpenSslKit.swift (~> 1.0) - Quick - RSSelectionMenu - RxSwift (~> 5.0) SPEC REPOS: - https://github.com/cocoapods/specs.git: + trunk: - Alamofire - BigInt - Cuckoo - GRDB.swift - - HSCryptoKit + - HdWalletKit.swift + - Hodler.swift - HSHDWalletKit + - HsToolKit.swift - Nimble - ObjectMapper + - OpenSslKit.swift - Quick - RSSelectionMenu - RxSwift + - Secp256k1Kit.swift EXTERNAL SOURCES: BitcoinCashKit.swift: @@ -77,31 +99,35 @@ EXTERNAL SOURCES: CHECKOUT OPTIONS: BitcoinCashKit.swift: - :commit: 89eadcd198235e81b768a224effcaa3e3f8273c8 + :commit: 597fd6a8ee51e7bca2eb9fa4b951b29fb796b5c0 :git: https://github.com/horizontalsystems/bitcoin-kit-ios/ BitcoinCore.swift: - :commit: 89eadcd198235e81b768a224effcaa3e3f8273c8 + :commit: 597fd6a8ee51e7bca2eb9fa4b951b29fb796b5c0 :git: https://github.com/horizontalsystems/bitcoin-kit-ios/ BitcoinKit.swift: - :commit: 89eadcd198235e81b768a224effcaa3e3f8273c8 + :commit: 597fd6a8ee51e7bca2eb9fa4b951b29fb796b5c0 :git: https://github.com/horizontalsystems/bitcoin-kit-ios/ SPEC CHECKSUMS: - Alamofire: ae5c501addb7afdbb13687d7f2f722c78734c2d3 - BigInt: 2aad1a9942dc932ec8b84290d2c564a3d76f97ab - BitcoinCashKit.swift: 88c873a444a954d2b6e485d2a8b9ea68a0e5b1b0 - BitcoinCore.swift: aae2002fb155c208e9d37296ae74b39d50ed20c9 - BitcoinKit.swift: 2d578cc38988f6c76848dfd29e301feb561ce29f - Cuckoo: 8074fc309159c4862665b83798f5ad03e061e8bd - GRDB.swift: 106a830decf1d92a3fc63c6d6a2f6586f6187297 - HSCryptoKit: 4dcb2506b5d8d85c4a31ab57734df3d1f7ea17f9 - HSHDWalletKit: 1d946bd24bd307cc494141f4e518d25db814f050 - Nimble: 622629381bda1dd5678162f21f1368cec7cbba60 - ObjectMapper: 70187b8941977c62ccfb423caf6b50be405cabf0 - Quick: 4be43f6634acfa727dd106bdf3929ce125ffa79d - RSSelectionMenu: decee83e020ee9d63e53995eb7f1ea5ca75f0667 - RxSwift: 8b0671caa829a763bbce7271095859121cbd895f + Alamofire: e911732990610fe89af59ac0077f923d72dc3dfd + BigInt: 74b4d88367b0e819d9f77393549226d36faeb0d8 + BitcoinCashKit.swift: df5b06703e6b64cb63655da4b124376b784d8760 + BitcoinCore.swift: cedc633dcdb979e3387ab201d3150619baa12464 + BitcoinKit.swift: 8b9c69ad9a36ec0687606ed92f50cfbee64ecf66 + Cuckoo: 57f71c6616b6b08cd9fe27f803da056fb619ff3e + GRDB.swift: 8cc3ab7c8b8b4ac1761deb8dca4b51c1c9d429f8 + HdWalletKit.swift: 4af4ef744a446904261971e5c31daf2eea8403c5 + Hodler.swift: a39a4f8b43e5a9de4221f6a369936ead4e91a1ef + HSHDWalletKit: 32f4be6c71cd986dcbdcd057d224638d0ff80445 + HsToolKit.swift: b89273c4018e3b70894e820c88cee8b684be9305 + Nimble: 98b888285a615fd34f20e61753cf58ea1402bde4 + ObjectMapper: 97111c97e054a6ee25917662106689f0b4127b37 + OpenSslKit.swift: 168b059e3438c21994a3e9fe6c5648df96660d94 + Quick: 7fb19e13be07b5dfb3b90d4f9824c855a11af40e + RSSelectionMenu: dac4fa365e4bbd95c0eccd0b6ff51a623a22a1c6 + RxSwift: 81470a2074fa8780320ea5fe4102807cb7118178 + Secp256k1Kit.swift: 93b1bfbb8909c12605666cc3b1e59d1d1fde7322 -PODFILE CHECKSUM: 2583f44dbd5da93bcc278bdd54797d8eac23efd0 +PODFILE CHECKSUM: 8fb24e4e0e559a2202998dc16e299422cbbb01b5 -COCOAPODS: 1.7.4 +COCOAPODS: 1.9.1