diff --git a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj index 26051561aa..269f573b43 100644 --- a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj +++ b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj @@ -9475,8 +9475,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/zcash/ZcashLightClientKit"; requirement = { - kind = exactVersion; - version = "0.20.1-beta"; + kind = revision; + revision = 690b5aa67f67dfa414f1b4aea07a1bf11fa9687e; }; }; D3993DAA28F42549008720FB /* XCRemoteSwiftPackageReference "WalletConnectSwiftV2" */ = { diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashAdapter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashAdapter.swift index 597ba47000..01e88ad1e0 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashAdapter.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashAdapter.swift @@ -22,7 +22,7 @@ class ZcashAdapter { private let synchronizer: Synchronizer - private var address: UnifiedAddress? + private var zAddress: String? private var transactionPool: ZcashTransactionPool? private let uniqueId: String @@ -45,7 +45,7 @@ class ZcashAdapter { private var waitForStart: Bool = false { didSet { - if waitForStart && address != nil { // already prepared and has address + if waitForStart && zAddress != nil { // already prepared and has address syncMain() } } @@ -173,22 +173,18 @@ class ZcashAdapter { throw AppError.ZcashError.seedRequired } logger?.log(level: .debug, message: "Successful prepared!") - guard let address = await synchronizer.getUnifiedAddress(accountIndex: 0), - let saplingAddress = address.saplingReceiver() else { + guard let address = try? await synchronizer.getUnifiedAddress(accountIndex: 0), + let saplingAddress = try? address.saplingReceiver() else { throw AppError.ZcashError.noReceiveAddress } - self.address = address + zAddress = saplingAddress.stringEncoded logger?.log(level: .debug, message: "Successful get address for 0 account! \(saplingAddress.stringEncoded)") let transactionPool = ZcashTransactionPool(receiveAddress: saplingAddress, synchronizer: synchronizer) self.transactionPool = transactionPool logger?.log(level: .debug, message: "Starting fetch transactions.") - let overviews = await synchronizer.clearedTransactions - let pending = await synchronizer.pendingTransactions - logger?.log(level: .debug, message: "Successful fetch \(overviews.count) txs and \(pending.count) pending txs") - - await transactionPool.store(confirmedTransactions: overviews, pendingTransactions: pending) + await transactionPool.initTransactions() let wrapped = transactionPool.all if !wrapped.isEmpty { @@ -198,8 +194,8 @@ class ZcashAdapter { }) } - let shielded = synchronizer.getShieldedBalance(accountIndex: 0).decimalValue.decimalValue - let shieldedVerified = synchronizer.getShieldedVerifiedBalance(accountIndex: 0).decimalValue.decimalValue + let shielded = await (try? synchronizer.getShieldedBalance(accountIndex: 0).decimalValue.decimalValue) ?? 0 + let shieldedVerified = await (try? synchronizer.getShieldedVerifiedBalance(accountIndex: 0).decimalValue.decimalValue) ?? 0 balanceSubject.onNext(BalanceData( balance: shieldedVerified, balanceLocked: shielded - shieldedVerified @@ -296,6 +292,9 @@ class ZcashAdapter { switch event { case .foundTransactions(let transactions, let inRange): logger?.log(level: .debug, message: "found \(transactions.count) mined txs in range: \(inRange)") + transactions.forEach { overview in + logger?.log(level: .debug, message: "tx: \(overview.value.decimalValue.decimalString) : \(overview.fee?.decimalString()) : \(overview.raw?.hs.hex)") + } Task { let newTxs = await transactionPool?.sync(transactions: transactions) ?? [] transactionRecordsSubject.onNext(newTxs.map { @@ -449,6 +448,7 @@ class ZcashAdapter { switch result { case .finished: App.shared.localStorage.zcashAlwaysPendingRewind = true + self?.logger?.log(level: .debug, message: "rewind Successful") completion?() case let .failure(error): self?.state = .notSynced(error: error) @@ -553,7 +553,7 @@ extension ZcashAdapter: IAdapter { return } - guard address != nil else { // else we need to try prepare library again + guard zAddress != nil else { // else we need to try prepare library again logger?.log(level: .debug, message: "No address, try to prepare kit again!") prepare(seedData: seedData, viewingKeys: [viewingKey], walletBirthday: birthday) return @@ -603,15 +603,14 @@ extension ZcashAdapter: IAdapter { } var debugInfo: String { - let tAddress = self.address?.transparentReceiver()?.stringEncoded ?? "No Info" - let zAddress = self.address?.saplingReceiver()?.stringEncoded ?? "No Info" + let zAddress = zAddress ?? "No Info" var balanceState = "No Balance Information yet" if let status = self.synchronizerState { balanceState = """ shielded balance - total: \(synchronizer.getShieldedBalance(accountIndex: 0).decimalValue.decimalValue) - verified: \(synchronizer.getShieldedVerifiedBalance(accountIndex: 0).decimalValue.decimalValue) + total: \(balanceData.balanceTotal.description) + verified: \(balanceData.balance) transparent balance total: \(String(describing: status.transparentBalance.total)) verified: \(String(describing: status.transparentBalance.verified)) @@ -620,7 +619,6 @@ extension ZcashAdapter: IAdapter { return """ ZcashAdapter z-address: \(String(describing: zAddress)) - t-address: \(String(describing: tAddress)) spendingKeys: \(spendingKey.description) balanceState: \(balanceState) """ @@ -697,7 +695,7 @@ extension ZcashAdapter: IDepositAdapter { var receiveAddress: String { // only first account - address?.saplingReceiver()?.stringEncoded ?? "n/a".localized + zAddress ?? "n/a".localized } } @@ -709,7 +707,7 @@ extension ZcashAdapter: ISendZcashAdapter { } var availableBalance: Decimal { - max(0, synchronizer.getShieldedVerifiedBalance(accountIndex: 0).decimalValue.decimalValue - fee) + max(0, balanceData.balance - fee) //TODO: check } func validate(address: String) throws -> AddressType { diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashTransactionPool.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashTransactionPool.swift index 2d16d6c151..e1826d3a05 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashTransactionPool.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashTransactionPool.swift @@ -34,22 +34,72 @@ class ZcashTransactionPool { } private func zcashTransactions(_ transactions: [PendingTransactionEntity]) -> [ZcashTransactionWrapper] { - transactions.compactMap { ZcashTransactionWrapper(pendingTransaction: $0) } + transactions.compactMap { ZcashTransactionWrapper(tx: $0) } } private func zcashTransactions(_ transactions: [ZcashTransaction.Overview]) async -> [ZcashTransactionWrapper] { var wrapped = [ZcashTransactionWrapper]() for tx in transactions { - if let tx = try? await transactionWithMemo(confirmedTransaction: tx) { + if let tx = try? await transactionWithAdditional(tx: tx) { wrapped.append(tx) } } return wrapped } - private func transactionWithMemo(confirmedTransaction: ZcashTransaction.Overview) async throws -> ZcashTransactionWrapper? { - let memos: [Memo] = (try? await synchronizer.getMemos(for: confirmedTransaction)) ?? [] - return ZcashTransactionWrapper(confirmedTransaction: confirmedTransaction, memo: memos.first) + private func zcashTransactions(_ transactions: [ZcashTransaction.Sent]) async -> [ZcashTransactionWrapper] { + var wrapped = [ZcashTransactionWrapper]() + for tx in transactions { + if let tx = try? await transactionWithAdditional(tx: tx) { + wrapped.append(tx) + } + } + return wrapped + } + + private func zcashTransactions(_ transactions: [ZcashTransaction.Received]) async -> [ZcashTransactionWrapper] { + var wrapped = [ZcashTransactionWrapper]() + for tx in transactions { + if let tx = try? await transactionWithAdditional(tx: tx) { + wrapped.append(tx) + } + } + return wrapped + } + + private func transactionWithAdditional(tx: ZcashTransaction.Overview) async throws -> ZcashTransactionWrapper? { + let memos: [Memo] = (try? await synchronizer.getMemos(for: tx)) ?? [] + let recipients = await synchronizer.getRecipients(for: tx) + print("OVERVIEW TX: TXID: \(tx.id) \(tx.value.decimalString())") + memos.forEach { memo in + print("Found Memo: \(memo)") + } + recipients.forEach { recipient in + print("Found Recipient: \(recipient)") + } + return ZcashTransactionWrapper(tx: tx, memo: memos.first, recipient: recipients.first) + } + + private func transactionWithAdditional(tx: ZcashTransaction.Sent) async throws -> ZcashTransactionWrapper? { + let memos: [Memo] = (try? await synchronizer.getMemos(for: tx)) ?? [] + let recipients = await synchronizer.getRecipients(for: tx) + print("OVERVIEW TX: TXID: \(tx.id) \(tx.value.decimalString())") + memos.forEach { memo in + print("Found Memo: \(memo)") + } + recipients.forEach { recipient in + print("Found Recipient: \(recipient)") + } + return ZcashTransactionWrapper(tx: tx, memo: memos.first, recipient: recipients.first) + } + + private func transactionWithAdditional(tx: ZcashTransaction.Received) async throws -> ZcashTransactionWrapper? { + let memos: [Memo] = (try? await synchronizer.getMemos(for: tx)) ?? [] + print("OVERVIEW TX: TXID: \(tx.id) \(tx.value.decimalString())") + memos.forEach { memo in + print("Found Memo: \(memo)") + } + return ZcashTransactionWrapper(tx: tx, memo: memos.first, recipient: nil) } @discardableResult private func sync(own: inout Set, incoming: [ZcashTransactionWrapper]) -> [ZcashTransactionWrapper] { @@ -62,9 +112,33 @@ class ZcashTransactionPool { return newTxs } - func store(confirmedTransactions: [ZcashTransaction.Overview], pendingTransactions: [PendingTransactionEntity]) async { - self.pendingTransactions = Set(zcashTransactions(pendingTransactions)) - self.confirmedTransactions = Set(await zcashTransactions(confirmedTransactions)) + func initTransactions() async { + let overviews = await synchronizer.clearedTransactions + let pending = await synchronizer.pendingTransactions + let sent = await synchronizer.sentTransactions + let received = await synchronizer.receivedTransactions + print("Found | overviews: \(overviews.count) | pending: \(pending.count) | sent: \(sent.count) | received: \(received.count)") + + pendingTransactions = Set(zcashTransactions(pending)) + confirmedTransactions = Set(await zcashTransactions(overviews)) + + let sentWrapped = await zcashTransactions(sent) + let receivedWrapped = await zcashTransactions(received) + + print("===> Overviews:") + confirmedTransactions.forEach { tx in + print("ID: \(tx.id ?? "N/A") | Value: \(tx.value) | Fee: \(tx.fee?.decimalString() ?? "N/A") | Recipient: \(tx.toAddress ?? "N/A")") + } + + print("===> Sent:") + sentWrapped.forEach { tx in + print("ID: \(tx.id ?? "N/A") | Value: \(tx.value) | Fee: \(tx.fee?.decimalString() ?? "N/A") | Recipient: \(tx.toAddress ?? "N/A")") + } + + print("===> received:") + receivedWrapped.forEach { tx in + print("ID: \(tx.id ?? "N/A") | Value: \(tx.value) | Fee: \(tx.fee?.decimalString() ?? "N/A") | Recipient: \(tx.toAddress ?? "N/A")") + } } func sync(transactions: [PendingTransactionEntity]) -> [ZcashTransactionWrapper] { diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashTransactionWrapper.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashTransactionWrapper.swift index a4219859b1..8ff570559b 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashTransactionWrapper.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashTransactionWrapper.swift @@ -13,43 +13,78 @@ class ZcashTransactionWrapper { let minedHeight: Int? let timestamp: TimeInterval let value: Zatoshi + let fee: Zatoshi? let memo: String? let failed: Bool - init?(confirmedTransaction: ZcashTransaction.Overview, memo: Memo?) { - id = confirmedTransaction.id.description - raw = confirmedTransaction.raw - transactionHash = confirmedTransaction.rawID.hs.reversedHex - transactionIndex = confirmedTransaction.index ?? 0 - toAddress = nil - isSentTransaction = confirmedTransaction.value < Zatoshi(0) - minedHeight = confirmedTransaction.minedHeight - expiryHeight = confirmedTransaction.expiryHeight - timestamp = confirmedTransaction.blockTime ?? 0 - value = confirmedTransaction.value + init?(tx: ZcashTransaction.Overview, memo: Memo?, recipient: TransactionRecipient?) { + id = tx.id.description + raw = tx.raw + transactionHash = tx.rawID.hs.reversedHex + transactionIndex = tx.index ?? 0 + toAddress = recipient?.asString + isSentTransaction = tx.value < Zatoshi(0) + minedHeight = tx.minedHeight + expiryHeight = tx.expiryHeight + timestamp = tx.blockTime ?? 0 + value = tx.value + fee = tx.fee self.memo = memo.flatMap { $0.toString() } failed = false } - init?(pendingTransaction: PendingTransactionEntity) { - guard let rawTransactionId = pendingTransaction.rawTransactionId else { + init?(tx: ZcashTransaction.Sent, memo: Memo?, recipient: TransactionRecipient?) { + id = tx.id.description + raw = tx.raw + transactionHash = tx.rawID?.hs.reversedHex ?? "n/a".localized + transactionIndex = tx.index ?? 0 + toAddress = recipient?.asString + isSentTransaction = tx.value < Zatoshi(0) + minedHeight = tx.minedHeight + expiryHeight = tx.expiryHeight + timestamp = tx.blockTime ?? 0 + value = tx.value + fee = nil // ?? todo : how to? + self.memo = memo.flatMap { $0.toString() } + failed = false + } + + init?(tx: ZcashTransaction.Received, memo: Memo?, recipient: TransactionRecipient?) { + id = tx.id.description + raw = tx.raw + transactionHash = tx.rawID?.hs.reversedHex ?? "n/a".localized + transactionIndex = tx.index + toAddress = recipient?.asString + isSentTransaction = tx.value < Zatoshi(0) + minedHeight = tx.minedHeight + expiryHeight = tx.expiryHeight + timestamp = tx.blockTime + value = tx.value + fee = nil // ?? todo: how to? + self.memo = memo.flatMap { $0.toString() } + failed = false + } + + init?(tx: PendingTransactionEntity) { + guard let rawTransactionId = tx.rawTransactionId else { return nil } - id = pendingTransaction.id?.description - raw = pendingTransaction.raw + id = tx.id?.description + raw = tx.raw transactionHash = rawTransactionId.hs.reversedHex transactionIndex = -1 - toAddress = pendingTransaction.recipient.asString + toAddress = tx.recipient.asString // if has toAddress - we must mark tx as sent - isSentTransaction = toAddress == nil ? pendingTransaction.value < Zatoshi(0) : true + isSentTransaction = toAddress == nil ? tx.value < Zatoshi(0) : true minedHeight = nil - expiryHeight = pendingTransaction.expiryHeight - timestamp = pendingTransaction.createTime - value = pendingTransaction.value - memo = pendingTransaction.memo.flatMap { String(bytes: $0, encoding: .utf8) } - failed = pendingTransaction.isFailure + expiryHeight = tx.expiryHeight + timestamp = tx.createTime + value = tx.value + fee = tx.fee + memo = tx.memo.flatMap { String(bytes: $0, encoding: .utf8) } + failed = tx.isFailure } }