2022-11-17 03:25:55 -08:00
|
|
|
//
|
|
|
|
// SDKSynchronizerLive.swift
|
|
|
|
// secant-testnet
|
|
|
|
//
|
|
|
|
// Created by Lukáš Korba on 15.11.2022.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
import Combine
|
|
|
|
import ComposableArchitecture
|
|
|
|
import ZcashLightClientKit
|
|
|
|
|
|
|
|
enum SDKSynchronizerDependency: DependencyKey {
|
|
|
|
static let liveValue: SDKSynchronizerClient = LiveSDKSynchronizerClient()
|
|
|
|
}
|
|
|
|
|
|
|
|
class LiveSDKSynchronizerClient: SDKSynchronizerClient {
|
|
|
|
private var cancellables: [AnyCancellable] = []
|
|
|
|
private(set) var synchronizer: SDKSynchronizer?
|
2023-01-20 05:55:08 -08:00
|
|
|
// TODO: [#497] Since 0.17.0-beta SDKSynchronizer has `lastState` property which does exactly the same as `stateChanged`. Problem is that we have
|
|
|
|
// synchronizer as optional. And now it would be complicated to handle the situation when `lastState` isn't always available. Let's handle this
|
|
|
|
// in future.
|
2022-11-17 03:25:55 -08:00
|
|
|
private(set) var stateChanged: CurrentValueSubject<SDKSynchronizerState, Never>
|
|
|
|
private(set) var notificationCenter: NotificationCenterClient
|
|
|
|
private(set) var walletBirthday: BlockHeight?
|
|
|
|
private(set) var latestScannedSynchronizerState: SDKSynchronizer.SynchronizerState?
|
|
|
|
|
|
|
|
init(notificationCenter: NotificationCenterClient = .live) {
|
|
|
|
self.notificationCenter = notificationCenter
|
|
|
|
self.stateChanged = CurrentValueSubject<SDKSynchronizerState, Never>(.unknown)
|
|
|
|
}
|
|
|
|
|
|
|
|
deinit {
|
|
|
|
synchronizer?.stop()
|
|
|
|
}
|
|
|
|
|
2022-11-22 02:32:48 -08:00
|
|
|
func prepareWith(initializer: Initializer, seedBytes: [UInt8]) throws {
|
|
|
|
let synchronizer = try SDKSynchronizer(initializer: initializer)
|
2022-11-17 03:25:55 -08:00
|
|
|
|
|
|
|
notificationCenter.publisherFor(.synchronizerStarted)?
|
|
|
|
.receive(on: DispatchQueue.main)
|
2023-03-08 06:14:01 -08:00
|
|
|
.sink { [weak self] notif in
|
|
|
|
let synchronizerState = notif.userInfo?[SDKSynchronizer.NotificationKeys.synchronizerState] as? SDKSynchronizer.SynchronizerState
|
|
|
|
self?.synchronizerStarted(synchronizerState)
|
|
|
|
}
|
2022-11-17 03:25:55 -08:00
|
|
|
.store(in: &cancellables)
|
|
|
|
|
|
|
|
notificationCenter.publisherFor(.synchronizerSynced)?
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.sink { [weak self] output in
|
|
|
|
let synchronizerState = output.userInfo?[SDKSynchronizer.NotificationKeys.synchronizerState] as? SDKSynchronizer.SynchronizerState
|
|
|
|
self?.synchronizerSynced(synchronizerState)
|
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
|
|
|
|
|
|
|
notificationCenter.publisherFor(.synchronizerProgressUpdated)?
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.sink { [weak self] _ in self?.synchronizerProgressUpdated() }
|
|
|
|
.store(in: &cancellables)
|
|
|
|
|
|
|
|
notificationCenter.publisherFor(.synchronizerStopped)?
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.sink { [weak self] _ in self?.synchronizerStopped() }
|
|
|
|
.store(in: &cancellables)
|
|
|
|
|
2022-11-22 02:32:48 -08:00
|
|
|
guard try synchronizer.prepare(with: seedBytes) == .success else {
|
|
|
|
throw SynchronizerError.initFailed(message: "")
|
|
|
|
}
|
|
|
|
|
|
|
|
self.synchronizer = synchronizer
|
2022-11-17 03:25:55 -08:00
|
|
|
walletBirthday = initializer.walletBirthday
|
|
|
|
}
|
|
|
|
|
|
|
|
func start(retry: Bool) throws {
|
|
|
|
try synchronizer?.start(retry: retry)
|
|
|
|
}
|
|
|
|
|
|
|
|
func stop() {
|
|
|
|
synchronizer?.stop()
|
|
|
|
}
|
2023-02-27 13:43:38 -08:00
|
|
|
|
|
|
|
func isSyncing() -> Bool {
|
|
|
|
latestScannedSynchronizerState?.syncStatus.isSyncing ?? false
|
|
|
|
}
|
|
|
|
|
|
|
|
func isInitialized() -> Bool {
|
|
|
|
synchronizer != nil
|
|
|
|
}
|
2022-11-17 03:25:55 -08:00
|
|
|
|
2023-03-08 06:14:01 -08:00
|
|
|
func synchronizerStarted(_ synchronizerState: SDKSynchronizer.SynchronizerState?) {
|
|
|
|
latestScannedSynchronizerState = synchronizerState
|
2022-11-17 03:25:55 -08:00
|
|
|
stateChanged.send(.started)
|
|
|
|
}
|
|
|
|
|
|
|
|
func synchronizerSynced(_ synchronizerState: SDKSynchronizer.SynchronizerState?) {
|
|
|
|
latestScannedSynchronizerState = synchronizerState
|
2023-03-08 06:14:01 -08:00
|
|
|
stateChanged.send(.synced)
|
2022-11-17 03:25:55 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
func synchronizerProgressUpdated() {
|
|
|
|
stateChanged.send(.progressUpdated)
|
|
|
|
}
|
|
|
|
|
|
|
|
func synchronizerStopped() {
|
|
|
|
stateChanged.send(.stopped)
|
|
|
|
}
|
|
|
|
|
|
|
|
func statusSnapshot() -> SyncStatusSnapshot {
|
2023-03-06 06:14:18 -08:00
|
|
|
guard let synchronizer else {
|
2022-11-17 03:25:55 -08:00
|
|
|
return .default
|
|
|
|
}
|
|
|
|
|
|
|
|
return SyncStatusSnapshot.snapshotFor(state: synchronizer.status)
|
|
|
|
}
|
|
|
|
|
2023-03-03 09:58:53 -08:00
|
|
|
func rewind(_ policy: RewindPolicy) -> AnyPublisher<Void, Error>? {
|
|
|
|
return synchronizer?.rewind(policy)
|
2022-11-17 03:25:55 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
func getShieldedBalance() -> WalletBalance? {
|
|
|
|
latestScannedSynchronizerState?.shieldedBalance
|
|
|
|
}
|
|
|
|
|
|
|
|
func getTransparentBalance() -> WalletBalance? {
|
|
|
|
latestScannedSynchronizerState?.transparentBalance
|
|
|
|
}
|
|
|
|
|
2023-02-13 09:13:42 -08:00
|
|
|
func getAllSentTransactions() -> EffectTask<[WalletEvent]> {
|
2023-02-06 00:38:34 -08:00
|
|
|
if let transactions = try? synchronizer?.allSentTransactions() {
|
2023-02-13 09:13:42 -08:00
|
|
|
return EffectTask(value: transactions.map {
|
2023-02-06 00:38:34 -08:00
|
|
|
let memos = try? synchronizer?.getMemos(for: $0)
|
|
|
|
let transaction = TransactionState.init(transaction: $0, memos: memos)
|
|
|
|
return WalletEvent(id: transaction.id, state: .send(transaction), timestamp: transaction.timestamp)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return .none
|
|
|
|
}
|
|
|
|
|
2023-02-13 09:13:42 -08:00
|
|
|
func getAllReceivedTransactions() -> EffectTask<[WalletEvent]> {
|
2023-02-06 00:38:34 -08:00
|
|
|
if let transactions = try? synchronizer?.allReceivedTransactions() {
|
2023-02-13 09:13:42 -08:00
|
|
|
return EffectTask(value: transactions.map {
|
2023-02-06 00:38:34 -08:00
|
|
|
let memos = try? synchronizer?.getMemos(for: $0)
|
|
|
|
let transaction = TransactionState.init(transaction: $0, memos: memos)
|
|
|
|
return WalletEvent(id: transaction.id, state: .send(transaction), timestamp: transaction.timestamp)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return .none
|
|
|
|
}
|
|
|
|
|
2023-02-13 09:13:42 -08:00
|
|
|
func getAllClearedTransactions() -> EffectTask<[WalletEvent]> {
|
2023-02-06 00:38:34 -08:00
|
|
|
if let transactions = try? synchronizer?.allClearedTransactions() {
|
2023-02-13 09:13:42 -08:00
|
|
|
return EffectTask(value: transactions.map {
|
2023-02-06 00:38:34 -08:00
|
|
|
let memos = try? synchronizer?.getMemos(for: $0)
|
|
|
|
let transaction = TransactionState.init(transaction: $0, memos: memos)
|
2022-11-17 03:25:55 -08:00
|
|
|
return WalletEvent(id: transaction.id, state: .send(transaction), timestamp: transaction.timestamp)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return .none
|
|
|
|
}
|
|
|
|
|
2023-02-13 09:13:42 -08:00
|
|
|
func getAllPendingTransactions() -> EffectTask<[WalletEvent]> {
|
2023-02-06 00:38:34 -08:00
|
|
|
if let transactions = try? synchronizer?.allPendingTransactions(),
|
2022-11-17 03:25:55 -08:00
|
|
|
let syncedBlockHeight = synchronizer?.latestScannedHeight {
|
2023-02-13 09:13:42 -08:00
|
|
|
return EffectTask(value: transactions.map {
|
2022-11-17 03:25:55 -08:00
|
|
|
let transaction = TransactionState.init(pendingTransaction: $0, latestBlockHeight: syncedBlockHeight)
|
|
|
|
return WalletEvent(id: transaction.id, state: .pending(transaction), timestamp: transaction.timestamp)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return .none
|
|
|
|
}
|
|
|
|
|
2023-02-13 09:13:42 -08:00
|
|
|
func getAllTransactions() -> EffectTask<[WalletEvent]> {
|
2022-11-17 03:25:55 -08:00
|
|
|
if let pendingTransactions = try? synchronizer?.allPendingTransactions(),
|
|
|
|
let clearedTransactions = try? synchronizer?.allClearedTransactions(),
|
|
|
|
let syncedBlockHeight = synchronizer?.latestScannedHeight {
|
|
|
|
let clearedTxs: [WalletEvent] = clearedTransactions.map {
|
2023-02-06 00:38:34 -08:00
|
|
|
let transaction = TransactionState.init(transaction: $0)
|
2022-11-17 03:25:55 -08:00
|
|
|
return WalletEvent(id: transaction.id, state: .send(transaction), timestamp: transaction.timestamp)
|
|
|
|
}
|
|
|
|
let pendingTxs: [WalletEvent] = pendingTransactions.map {
|
|
|
|
let transaction = TransactionState.init(pendingTransaction: $0, latestBlockHeight: syncedBlockHeight)
|
|
|
|
return WalletEvent(id: transaction.id, state: .pending(transaction), timestamp: transaction.timestamp)
|
|
|
|
}
|
2023-02-06 00:38:34 -08:00
|
|
|
let cTxs = clearedTxs.filter { transaction in
|
2022-11-17 03:25:55 -08:00
|
|
|
pendingTxs.first { pending in
|
2023-02-06 00:38:34 -08:00
|
|
|
pending.id == transaction.id
|
2022-11-17 03:25:55 -08:00
|
|
|
} == nil
|
|
|
|
}
|
2023-02-06 00:38:34 -08:00
|
|
|
|
2022-11-17 03:25:55 -08:00
|
|
|
return .merge(
|
2023-02-13 09:13:42 -08:00
|
|
|
EffectTask(value: cTxs),
|
|
|
|
EffectTask(value: pendingTxs)
|
2022-11-17 03:25:55 -08:00
|
|
|
)
|
|
|
|
.flatMap(Publishers.Sequence.init(sequence:))
|
|
|
|
.collect()
|
|
|
|
.eraseToEffect()
|
|
|
|
}
|
|
|
|
|
|
|
|
return .none
|
|
|
|
}
|
|
|
|
|
2023-01-05 11:07:25 -08:00
|
|
|
func getUnifiedAddress(account: Int) -> UnifiedAddress? {
|
|
|
|
synchronizer?.getUnifiedAddress(accountIndex: account)
|
|
|
|
}
|
|
|
|
|
2022-11-17 03:25:55 -08:00
|
|
|
func getTransparentAddress(account: Int) -> TransparentAddress? {
|
|
|
|
synchronizer?.getTransparentAddress(accountIndex: account)
|
|
|
|
}
|
|
|
|
|
2022-11-22 02:32:48 -08:00
|
|
|
func getSaplingAddress(accountIndex: Int) async -> SaplingAddress? {
|
|
|
|
await synchronizer?.getSaplingAddress(accountIndex: accountIndex)
|
2022-11-17 03:25:55 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
func sendTransaction(
|
2022-11-22 02:32:48 -08:00
|
|
|
with spendingKey: UnifiedSpendingKey,
|
2022-11-17 03:25:55 -08:00
|
|
|
zatoshi: Zatoshi,
|
2022-11-22 02:32:48 -08:00
|
|
|
to recipientAddress: Recipient,
|
|
|
|
memo: Memo?
|
2023-02-13 09:13:42 -08:00
|
|
|
) -> EffectTask<Result<TransactionState, NSError>> {
|
|
|
|
return .run { [weak self] send in
|
2022-11-22 02:32:48 -08:00
|
|
|
do {
|
|
|
|
guard let synchronizer = self?.synchronizer else {
|
|
|
|
await send(.failure(SDKSynchronizerClientError.synchronizerNotInitialized as NSError))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let pendingTransaction = try await synchronizer.sendToAddress(
|
2022-11-17 03:25:55 -08:00
|
|
|
spendingKey: spendingKey,
|
|
|
|
zatoshi: zatoshi,
|
|
|
|
toAddress: recipientAddress,
|
2022-11-22 02:32:48 -08:00
|
|
|
memo: memo
|
|
|
|
)
|
|
|
|
|
|
|
|
await send(.success(TransactionState(pendingTransaction: pendingTransaction)))
|
|
|
|
} catch {
|
|
|
|
await send(.failure(error as NSError))
|
2022-11-17 03:25:55 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-02-27 13:43:38 -08:00
|
|
|
|
|
|
|
func wipe() -> AnyPublisher<Void, Error>? {
|
|
|
|
synchronizer?.wipe()
|
|
|
|
}
|
2022-11-17 03:25:55 -08:00
|
|
|
}
|