// // CombineSynchronizer.swift // wallet // // Created by Francisco Gindre on 1/27/20. // Copyright © 2020 Francisco Gindre. All rights reserved. // import Foundation import Combine import ZcashLightClientKit class CombineSynchronizer { var initializer: Initializer { synchronizer.initializer } var synchronizer: SDKSynchronizer var status: CurrentValueSubject var progress: CurrentValueSubject var syncBlockHeight: CurrentValueSubject var minedTransaction = PassthroughSubject() var balance: CurrentValueSubject var verifiedBalance: CurrentValueSubject var cancellables = [AnyCancellable]() var error = PassthroughSubject() var receivedTransactions: Future<[ConfirmedTransactionEntity],Never> { Future<[ConfirmedTransactionEntity], Never>() { promise in DispatchQueue.global().async { [weak self] in guard let self = self else { promise(.success([])) return } promise(.success(self.synchronizer.receivedTransactions)) } } } var sentTransactions: Future<[ConfirmedTransactionEntity], Never> { Future<[ConfirmedTransactionEntity], Never>() { promise in DispatchQueue.global().async { [weak self] in guard let self = self else { promise(.success([])) return } promise(.success(self.synchronizer.sentTransactions)) } } } var pendingTransactions: Future<[PendingTransactionEntity], Never> { Future<[PendingTransactionEntity], Never>(){ [weak self ] promise in guard let self = self else { promise(.success([])) return } DispatchQueue.global().async { promise(.success(self.synchronizer.pendingTransactions)) } } } init(initializer: Initializer) throws { self.synchronizer = try SDKSynchronizer(initializer: initializer) self.status = CurrentValueSubject(.disconnected) self.progress = CurrentValueSubject(0) self.balance = CurrentValueSubject(0) self.verifiedBalance = CurrentValueSubject(0) self.syncBlockHeight = CurrentValueSubject(ZcashSDK.SAPLING_ACTIVATION_HEIGHT) NotificationCenter.default.publisher(for: .synchronizerSynced).sink(receiveValue: { _ in self.balance.send(initializer.getBalance().asHumanReadableZecBalance()) self.verifiedBalance.send(initializer.getVerifiedBalance().asHumanReadableZecBalance()) }).store(in: &cancellables) NotificationCenter.default.publisher(for: .synchronizerStarted).sink { _ in self.status.send(.syncing) }.store(in: &cancellables) NotificationCenter.default.publisher(for: .synchronizerProgressUpdated).receive(on: DispatchQueue.main).sink(receiveValue: { (progressNotification) in guard let newProgress = progressNotification.userInfo?[SDKSynchronizer.NotificationKeys.progress] as? Float else { return } self.progress.send(newProgress) guard let blockHeight = progressNotification.userInfo?[SDKSynchronizer.NotificationKeys.blockHeight] as? BlockHeight else { return } self.syncBlockHeight.send(blockHeight) }).store(in: &cancellables) NotificationCenter.default.publisher(for: .synchronizerMinedTransaction).sink(receiveValue: {minedNotification in guard let minedTx = minedNotification.userInfo?[SDKSynchronizer.NotificationKeys.minedTransaction] as? PendingTransactionEntity else { return } self.minedTransaction.send(minedTx) }).store(in: &cancellables) NotificationCenter.default.publisher(for: .synchronizerFailed).sink { (notification) in guard let error = notification.userInfo?[SDKSynchronizer.NotificationKeys.error] as? Error else { self.error.send(ZirclesEnvironment.WalletError.genericError(message: "An error ocurred, but we can't figure out what it is. Please check device logs for more details") ) return } self.error.send(error) }.store(in: &cancellables) } func start(retry: Bool = false){ do { if retry { stop() } try synchronizer.start(retry: retry) } catch { logger.error("error starting \(error)") } } func stop() { do { try synchronizer.stop() } catch { logger.error("error stopping \(error)") } } func cancel(pendingTransaction: PendingTransactionEntity) -> Bool { synchronizer.cancelSpend(transaction: pendingTransaction) } deinit { for c in cancellables { c.cancel() } } func send(with spendingKey: String, zatoshi: Int64, to recipientAddress: String, memo: String?,from account: Int) -> Future { Future() { promise in self.synchronizer.sendToAddress(spendingKey: spendingKey, zatoshi: zatoshi, toAddress: recipientAddress, memo: memo, from: account) { (result) in switch result { case .failure(let error): promise(.failure(error)) case .success(let pendingTx): promise(.success(pendingTx)) } } } } func latestHeight() -> Future { Future() { promise in DispatchQueue.global().async { [weak self] in guard let self = self else { return } do { promise(.success(try self.initializer.lightWalletService.latestBlockHeight())) } catch { promise(.failure(error)) } } } } }