200 lines
8.6 KiB
Swift
200 lines
8.6 KiB
Swift
//
|
|
// SDKSynchronizerLive.swift
|
|
// secant-testnet
|
|
//
|
|
// Created by Lukáš Korba on 15.11.2022.
|
|
//
|
|
|
|
import Foundation
|
|
import Combine
|
|
import ComposableArchitecture
|
|
import ZcashLightClientKit
|
|
import DatabaseFiles
|
|
import Models
|
|
import ZcashSDKEnvironment
|
|
|
|
extension SDKSynchronizerClient: DependencyKey {
|
|
public static let liveValue: SDKSynchronizerClient = Self.live()
|
|
|
|
public static func live(
|
|
databaseFiles: DatabaseFilesClient = .liveValue
|
|
) -> Self {
|
|
@Dependency (\.zcashSDKEnvironment) var zcashSDKEnvironment
|
|
|
|
let network = zcashSDKEnvironment.network
|
|
|
|
#if DEBUG
|
|
let loggingPolicy = Initializer.LoggingPolicy.default(.debug)
|
|
#else
|
|
let loggingPolicy = Initializer.LoggingPolicy.noLogging
|
|
#endif
|
|
|
|
let initializer = Initializer(
|
|
cacheDbURL: databaseFiles.cacheDbURLFor(network),
|
|
fsBlockDbRoot: databaseFiles.fsBlockDbRootFor(network),
|
|
generalStorageURL: databaseFiles.documentsDirectory(),
|
|
dataDbURL: databaseFiles.dataDbURLFor(network),
|
|
endpoint: zcashSDKEnvironment.endpoint(),
|
|
network: network,
|
|
spendParamsURL: databaseFiles.spendParamsURLFor(network),
|
|
outputParamsURL: databaseFiles.outputParamsURLFor(network),
|
|
saplingParamsSourceURL: SaplingParamsSourceURL.default,
|
|
loggingPolicy: loggingPolicy
|
|
)
|
|
|
|
let synchronizer = SDKSynchronizer(initializer: initializer)
|
|
|
|
return SDKSynchronizerClient(
|
|
stateStream: { synchronizer.stateStream },
|
|
eventStream: { synchronizer.eventStream },
|
|
latestState: { synchronizer.latestState },
|
|
prepareWith: { seedBytes, walletBirtday, walletMode in
|
|
let result = try await synchronizer.prepare(with: seedBytes, walletBirthday: walletBirtday, for: walletMode)
|
|
if result != .success { throw ZcashError.synchronizerNotPrepared }
|
|
},
|
|
start: { retry in try await synchronizer.start(retry: retry) },
|
|
stop: { synchronizer.stop() },
|
|
isSyncing: { synchronizer.latestState.syncStatus.isSyncing },
|
|
isInitialized: { synchronizer.latestState.syncStatus != SyncStatus.unprepared },
|
|
rewind: { synchronizer.rewind($0) },
|
|
getAllTransactions: {
|
|
let clearedTransactions = try await synchronizer.allTransactions()
|
|
|
|
var clearedTxs: [TransactionState] = []
|
|
|
|
for clearedTransaction in clearedTransactions {
|
|
var transaction = TransactionState.init(
|
|
transaction: clearedTransaction,
|
|
memos: clearedTransaction.memoCount > 0 ? try await synchronizer.getMemos(for: clearedTransaction) : nil,
|
|
latestBlockHeight: try await SDKSynchronizerClient.latestBlockHeight(synchronizer: synchronizer)
|
|
)
|
|
|
|
let recipients = await synchronizer.getRecipients(for: clearedTransaction)
|
|
let addresses = recipients.compactMap {
|
|
if case let .address(address) = $0 {
|
|
return address
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
transaction.zAddress = addresses.first?.stringEncoded
|
|
|
|
clearedTxs.append(transaction)
|
|
}
|
|
|
|
return clearedTxs
|
|
},
|
|
getUnifiedAddress: { try await synchronizer.getUnifiedAddress(accountIndex: $0) },
|
|
getTransparentAddress: { try await synchronizer.getTransparentAddress(accountIndex: $0) },
|
|
getSaplingAddress: { try await synchronizer.getSaplingAddress(accountIndex: $0) },
|
|
sendTransaction: { spendingKey, amount, recipient, memo in
|
|
let pendingTransaction = try await synchronizer.sendToAddress(
|
|
spendingKey: spendingKey,
|
|
zatoshi: amount,
|
|
toAddress: recipient,
|
|
memo: memo
|
|
)
|
|
|
|
return TransactionState(
|
|
transaction: pendingTransaction,
|
|
latestBlockHeight: try await SDKSynchronizerClient.latestBlockHeight(synchronizer: synchronizer)
|
|
)
|
|
},
|
|
shieldFunds: { spendingKey, memo, shieldingThreshold in
|
|
let pendingTransaction = try await synchronizer.shieldFunds(
|
|
spendingKey: spendingKey,
|
|
memo: memo,
|
|
shieldingThreshold: shieldingThreshold
|
|
)
|
|
|
|
return TransactionState(
|
|
transaction: pendingTransaction,
|
|
latestBlockHeight: try await SDKSynchronizerClient.latestBlockHeight(synchronizer: synchronizer)
|
|
)
|
|
},
|
|
wipe: { synchronizer.wipe() },
|
|
switchToEndpoint: { endpoint in
|
|
try await synchronizer.switchTo(endpoint: endpoint)
|
|
},
|
|
proposeTransfer: { accountIndex, recipient, amount, memo in
|
|
try await synchronizer.proposeTransfer(
|
|
accountIndex: accountIndex,
|
|
recipient: recipient,
|
|
amount: amount,
|
|
memo: memo
|
|
)
|
|
},
|
|
createProposedTransactions: { proposal, spendingKey in
|
|
let stream = try await synchronizer.createProposedTransactions(
|
|
proposal: proposal,
|
|
spendingKey: spendingKey
|
|
)
|
|
|
|
let transactionCount = proposal.transactionCount()
|
|
var successCount = 0
|
|
var iterator = stream.makeAsyncIterator()
|
|
|
|
var txIds: [String] = []
|
|
var statuses: [String] = []
|
|
|
|
for _ in 1...transactionCount {
|
|
if let transactionSubmitResult = try await iterator.next() {
|
|
switch transactionSubmitResult {
|
|
case .success(txId: let id):
|
|
successCount += 1
|
|
txIds.append(id.toHexStringTxId())
|
|
statuses.append("success")
|
|
case let .grpcFailure(txId: id, error: error):
|
|
txIds.append(id.toHexStringTxId())
|
|
statuses.append(error.localizedDescription)
|
|
case let .submitFailure(txId: id, code: code, description: description):
|
|
txIds.append(id.toHexStringTxId())
|
|
statuses.append("code: \(code) desc: \(description)")
|
|
case .notAttempted(txId: let id):
|
|
txIds.append(id.toHexStringTxId())
|
|
statuses.append("notAttempted")
|
|
}
|
|
}
|
|
}
|
|
|
|
if successCount == 0 {
|
|
return .failure
|
|
} else if successCount == transactionCount {
|
|
return .success
|
|
} else {
|
|
return .partial(txIds: txIds, statuses: statuses)
|
|
}
|
|
},
|
|
proposeShielding: { accountIndex, shieldingThreshold, memo, transparentReceiver in
|
|
try await synchronizer.proposeShielding(
|
|
accountIndex: accountIndex,
|
|
shieldingThreshold: shieldingThreshold,
|
|
memo: memo,
|
|
transparentReceiver: transparentReceiver
|
|
)
|
|
},
|
|
isSeedRelevantToAnyDerivedAccount: { seed in
|
|
try await synchronizer.isSeedRelevantToAnyDerivedAccount(seed: seed)
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
// TODO: [#1313] SDK improvements so a client doesn't need to determing if the transaction isPending
|
|
// https://github.com/zcash/ZcashLightClientKit/issues/1313
|
|
// Once #1313 is done, cleint will no longer need to call for a `latestHeight()`
|
|
private extension SDKSynchronizerClient {
|
|
static func latestBlockHeight(synchronizer: SDKSynchronizer) async throws -> BlockHeight {
|
|
let latestBlockHeight: BlockHeight
|
|
|
|
if synchronizer.latestState.latestBlockHeight > 0 {
|
|
latestBlockHeight = synchronizer.latestState.latestBlockHeight
|
|
} else {
|
|
latestBlockHeight = try await synchronizer.latestHeight()
|
|
}
|
|
|
|
return latestBlockHeight
|
|
}
|
|
}
|