From b1afb1cd058cc0ef2633aa69f8c6db3cbb9761fc Mon Sep 17 00:00:00 2001 From: ant013 Date: Wed, 27 Sep 2023 15:23:15 +0400 Subject: [PATCH] Update zcash library - Implement spend-before-sync - Refactor balanceData. separate custom balanceData types - Fix zcash transactions memos and recipients --- .../project.pbxproj | 6 + .../Core/Adapters/BinanceAdapter.swift | 4 +- .../Core/Adapters/BitcoinBaseAdapter.swift | 6 +- .../Core/Adapters/Evm/BaseEvmAdapter.swift | 2 +- .../Core/Adapters/Evm/Eip20Adapter.swift | 2 +- .../Core/Adapters/Evm/EvmAdapter.swift | 2 +- .../Core/Adapters/Tron/BaseTronAdapter.swift | 2 +- .../Core/Adapters/Tron/Trc20Adapter.swift | 2 +- .../Core/Adapters/Tron/TronAdapter.swift | 2 +- .../Core/Adapters/ZcashAdapter.swift | 80 ++++---- .../Core/Adapters/ZcashTransactionPool.swift | 27 ++- .../Adapters/ZcashTransactionWrapper.swift | 4 +- .../Core/Managers/RateAppManager.swift | 2 +- .../Core/Storage/StorageMigrator.swift | 39 ++-- .../Models/BalanceData.swift | 174 ++++++++++++++++-- .../Models/EnabledWalletCache.swift | 21 ++- .../Models/EnabledWalletCache_v_0_36.swift | 51 +++++ .../CoinSelect/CoinSelectService.swift | 2 +- .../Modules/SendEvm/SendEvmService.swift | 10 +- .../Modules/SendTron/SendTronService.swift | 10 +- .../Adapters/OneInch/OneInchService.swift | 2 +- .../Adapters/Uniswap/UniswapService.swift | 2 +- .../Adapters/UniswapV3/UniswapV3Service.swift | 2 +- .../WalletTokenBalanceService.swift | 2 +- .../WalletTokenBalanceViewItemFactory.swift | 109 ++++------- .../TokenList/WalletTokenListService.swift | 2 +- .../Wallet/WalletCexElementService.swift | 2 +- .../Modules/Wallet/WalletService.swift | 2 +- .../Modules/Wallet/WalletSorter.swift | 4 +- .../UserInterface/Extensions/HudHelper.swift | 2 +- .../en.lproj/Localizable.strings | 3 + 31 files changed, 378 insertions(+), 202 deletions(-) create mode 100644 UnstoppableWallet/UnstoppableWallet/Models/EnabledWalletCache_v_0_36.swift diff --git a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj index 5275a9ed65..a4dbc85627 100644 --- a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj +++ b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj @@ -2238,6 +2238,7 @@ ABC9AB0F8FE5808DB889C081 /* WalletConnectScanQrViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A3DBB89D0AE0C127742B /* WalletConnectScanQrViewModel.swift */; }; ABC9AB11FDD018A96BB86557 /* BottomGradientHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9ADE822BC024F9B798211 /* BottomGradientHolder.swift */; }; ABC9AB1E703AE57DF856ECD9 /* SendAmountCautionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A07A33870908ED1BA338 /* SendAmountCautionViewModel.swift */; }; + ABC9AB2E235EA006E2DAD8DD /* EnabledWalletCache_v_0_36.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A68AFE3CF24D2B88808F /* EnabledWalletCache_v_0_36.swift */; }; ABC9AB308727D81FBB8EBCDD /* BackupCloudPassphraseService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AF6AA02DA39787C053F0 /* BackupCloudPassphraseService.swift */; }; ABC9AB3DAD30AA400DEB719C /* SendBitcoinService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A696DCBBE4761E77311C /* SendBitcoinService.swift */; }; ABC9AB401FD98F99EF6B07C6 /* RestoreTypeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A939DD222D4A2BD3D71C /* RestoreTypeViewController.swift */; }; @@ -2302,6 +2303,7 @@ ABC9AD3001AAA0570B503876 /* ManageBarButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A6CFDF38D413679D2088 /* ManageBarButtonView.swift */; }; ABC9AD3276132B33F6045AFF /* MarketCategoryMarketCapFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9ADC1A3B17225B6CC0869 /* MarketCategoryMarketCapFetcher.swift */; }; ABC9AD41E7C88963F6512905 /* ChartIndicatorsRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A3758FE2D56036DF27FF /* ChartIndicatorsRepository.swift */; }; + ABC9AD46006A85E907826E2B /* EnabledWalletCache_v_0_36.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A68AFE3CF24D2B88808F /* EnabledWalletCache_v_0_36.swift */; }; ABC9AD46AE6B5F432E0D2085 /* WalletTokenBalanceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A52822CE6B8830CF5EF4 /* WalletTokenBalanceViewModel.swift */; }; ABC9AD49CCD14F97CD912454 /* SendBitcoinAdapterService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A1BD3B1B53C72DDF923A /* SendBitcoinAdapterService.swift */; }; ABC9AD565E3BAB7074D02D40 /* ProFeaturesAuthorizationAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A8D3446ABE98F4D1C0CC /* ProFeaturesAuthorizationAdapter.swift */; }; @@ -3800,6 +3802,7 @@ ABC9A6363DB5DAE5B58AFDC0 /* UniswapV3Module.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniswapV3Module.swift; sourceTree = ""; }; ABC9A64A66778C137FA9642C /* WalletTokenViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletTokenViewController.swift; sourceTree = ""; }; ABC9A6663522498A53CF4174 /* KdfParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KdfParams.swift; sourceTree = ""; }; + ABC9A68AFE3CF24D2B88808F /* EnabledWalletCache_v_0_36.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnabledWalletCache_v_0_36.swift; sourceTree = ""; }; ABC9A696DCBBE4761E77311C /* SendBitcoinService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendBitcoinService.swift; sourceTree = ""; }; ABC9A6B2EF46FF7EDA4728D3 /* CheckboxView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckboxView.swift; sourceTree = ""; }; ABC9A6CFDF38D413679D2088 /* ManageBarButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManageBarButtonView.swift; sourceTree = ""; }; @@ -5048,6 +5051,7 @@ 11B35799B0DCCF655F0766BF /* CexDepositNetwork.swift */, 11B35B617A9CE668EEF4978B /* AmountData.swift */, 11B359A35AEB7964A94AFFC0 /* BiometryType.swift */, + ABC9A68AFE3CF24D2B88808F /* EnabledWalletCache_v_0_36.swift */, ); path = Models; sourceTree = ""; @@ -9243,6 +9247,7 @@ 11B35353A5C1E254839CD61B /* InteractiveDismiss.swift in Sources */, 11B3553AD73FD1179249F277 /* SecondaryButtonStyle.swift in Sources */, 11B3523E8B466F259DB32E37 /* SecondaryCircleButtonStyle.swift in Sources */, + ABC9AD46006A85E907826E2B /* EnabledWalletCache_v_0_36.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -10545,6 +10550,7 @@ 11B350A27335B798701EE7B3 /* InteractiveDismiss.swift in Sources */, 11B35DDBD7EC98FAE5794F76 /* SecondaryButtonStyle.swift in Sources */, 11B35224D7A5A864C1C6F167 /* SecondaryCircleButtonStyle.swift in Sources */, + ABC9AB2E235EA006E2DAD8DD /* EnabledWalletCache_v_0_36.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/BinanceAdapter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/BinanceAdapter.swift index 3953e7ba3f..ddfa4bce3b 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/BinanceAdapter.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/BinanceAdapter.swift @@ -51,7 +51,7 @@ class BinanceAdapter { } private func balanceInfo(balance: Decimal) -> BalanceData { - BalanceData(balance: balance) + BalanceData(available: balance) } } @@ -114,7 +114,7 @@ extension BinanceAdapter: IBalanceAdapter { var balanceDataUpdatedObservable: Observable { asset.balanceObservable.map { [weak self] in - self?.balanceInfo(balance: $0) ?? BalanceData(balance: 0) + self?.balanceInfo(balance: $0) ?? BalanceData(available: 0) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/BitcoinBaseAdapter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/BitcoinBaseAdapter.swift index 95fdcafee4..2bd2f90cbf 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/BitcoinBaseAdapter.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/BitcoinBaseAdapter.swift @@ -140,8 +140,8 @@ class BitcoinBaseAdapter { } private func balanceData(balanceInfo: BalanceInfo) -> BalanceData { - BalanceData( - balance: Decimal(balanceInfo.spendable) / coinRate, + LockedBalanceData( + available: Decimal(balanceInfo.spendable) / coinRate, locked: Decimal(balanceInfo.unspendable) / coinRate ) } @@ -389,6 +389,6 @@ class DepositAddress { let address: String init(_ receiveAddress: String) { - self.address = receiveAddress + address = receiveAddress } } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Evm/BaseEvmAdapter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Evm/BaseEvmAdapter.swift index 1b142e564b..43e8ffe048 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Evm/BaseEvmAdapter.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Evm/BaseEvmAdapter.swift @@ -44,7 +44,7 @@ class BaseEvmAdapter { } func balanceData(balance: BigUInt?) -> BalanceData { - BalanceData(balance: balanceDecimal(kitBalance: balance, decimals: decimals)) + BalanceData(available: balanceDecimal(kitBalance: balance, decimals: decimals)) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Evm/Eip20Adapter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Evm/Eip20Adapter.swift index 0a06b0523d..de0bcfe791 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Evm/Eip20Adapter.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Evm/Eip20Adapter.swift @@ -59,7 +59,7 @@ extension Eip20Adapter: IBalanceAdapter { var balanceDataUpdatedObservable: Observable { eip20Kit.balanceObservable.map { [weak self] in - self?.balanceData(balance: $0) ?? BalanceData(balance: 0) + self?.balanceData(balance: $0) ?? BalanceData(available: 0) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Evm/EvmAdapter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Evm/EvmAdapter.swift index d4f4faaa91..c11aa0f631 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Evm/EvmAdapter.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Evm/EvmAdapter.swift @@ -57,7 +57,7 @@ extension EvmAdapter: IBalanceAdapter { var balanceDataUpdatedObservable: Observable { evmKit.accountStateObservable.map { [weak self] in - self?.balanceData(balance: $0.balance) ?? BalanceData(balance: 0) + self?.balanceData(balance: $0.balance) ?? BalanceData(available: 0) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Tron/BaseTronAdapter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Tron/BaseTronAdapter.swift index 801ed7cd92..106883f427 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Tron/BaseTronAdapter.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Tron/BaseTronAdapter.swift @@ -44,7 +44,7 @@ class BaseTronAdapter { } func balanceData(balance: BigUInt?) -> BalanceData { - BalanceData(balance: balanceDecimal(kitBalance: balance, decimals: decimals)) + BalanceData(available: balanceDecimal(kitBalance: balance, decimals: decimals)) } func accountActive(address: TronKit.Address) async -> Bool { diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Tron/Trc20Adapter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Tron/Trc20Adapter.swift index 231d07f1af..4510a3dc2f 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Tron/Trc20Adapter.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Tron/Trc20Adapter.swift @@ -51,7 +51,7 @@ extension Trc20Adapter: IBalanceAdapter { var balanceDataUpdatedObservable: Observable { tronKit.trc20BalancePublisher(contractAddress: contractAddress).asObservable().map { [weak self] in - self?.balanceData(balance: $0) ?? BalanceData(balance: 0) + self?.balanceData(balance: $0) ?? BalanceData(available: 0) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Tron/TronAdapter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Tron/TronAdapter.swift index 2e0977baf2..c177f8d359 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Tron/TronAdapter.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/Tron/TronAdapter.swift @@ -55,7 +55,7 @@ extension TronAdapter: IBalanceAdapter { var balanceDataUpdatedObservable: Observable { tronKit.trxBalancePublisher.asObservable().map { [weak self] in - self?.balanceData(balance: $0) ?? BalanceData(balance: 0) + self?.balanceData(balance: $0) ?? BalanceData(available: 0) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashAdapter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashAdapter.swift index 0b7482ce23..155fc46c74 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashAdapter.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashAdapter.swift @@ -45,15 +45,6 @@ class ZcashAdapter { private var started = false private var lastBlockHeight: Int = 0 - private var waitForStart: Bool = false { - didSet { - print("Change waitForStart to \(waitForStart) : zAddress : \(zAddress != nil)") - if waitForStart, zAddress != nil { // already prepared and has address - syncMain() - } - } - } - private var synchronizerState: SynchronizerState? { didSet { lastBlockUpdatedSubject.onNext(()) @@ -89,7 +80,7 @@ class ZcashAdapter { } init(wallet: Wallet, restoreSettings: RestoreSettings) throws { - logger = HsToolKit.Logger(minLogLevel: .debug) // App.shared.logger.scoped(with: "ZCashKit") // + logger = App.shared.logger.scoped(with: "ZCashKit") // HsToolKit.Logger(minLogLevel: .debug) // guard let seed = wallet.account.type.mnemonicSeed else { throw AdapterError.unsupportedAccount @@ -141,7 +132,6 @@ class ZcashAdapter { // subscribe on background and events from sapling downloader NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil) - prepare(seedData: seedData, walletBirthday: birthday, for: initMode) } private func prepare(seedData: [UInt8], walletBirthday: BlockHeight, for initMode: WalletInitMode) { @@ -195,9 +185,9 @@ class ZcashAdapter { let shielded = await (try? synchronizer.getShieldedBalance(accountIndex: 0).decimalValue.decimalValue) ?? 0 let shieldedVerified = await (try? synchronizer.getShieldedVerifiedBalance(accountIndex: 0).decimalValue.decimalValue) ?? 0 self?.balanceSubject.onNext( - BalanceData( - balance: shieldedVerified, - locked: shielded - shieldedVerified + VerifiedBalanceData( + fullBalance: shielded, + available: shieldedVerified ) ) self?.lastBlockHeight = try await synchronizer.latestHeight() @@ -218,9 +208,26 @@ class ZcashAdapter { private func finishPrepare() { state = .idle - if waitForStart { - logger?.log(level: .debug, message: "Start kit after finish preparing!") - start() + logger?.log(level: .debug, message: "Start kit after finish preparing!") + startSynchronizer() + } + + private func startSynchronizer() { + guard !state.isPrepairing else { // postpone start library until preparing will finish + logger?.log(level: .debug, message: "Can't start because preparing!") + return + } + + if zAddress == nil { // else we need to try prepare library again + logger?.log(level: .debug, message: "No address, try to prepare kit again!") + prepare(seedData: seedData, walletBirthday: birthday, for: initMode) + + return + } + + if saplingDataExist() { + logger?.log(level: .debug, message: "Start syncing kit!") + syncMain() } } @@ -298,7 +305,7 @@ class ZcashAdapter { transactions.forEach { overview in logger?.log(level: .debug, message: "tx: v =\(overview.value.decimalValue.decimalString) : fee = \(overview.fee?.decimalString() ?? "N/A") : height = \(overview.minedHeight?.description ?? "N/A")") } - let lastBlockHeight = inRange.upperBound + let lastBlockHeight = max(inRange.upperBound, lastBlockHeight) Task { let newTxs = await transactionPool?.sync(transactions: transactions, lastBlockHeight: lastBlockHeight) ?? [] transactionRecordsSubject.onNext(newTxs.map { @@ -474,16 +481,12 @@ class ZcashAdapter { private var _balanceData: BalanceData { guard let synchronizerState = synchronizerState else { - return BalanceData(balance: 0) + return BalanceData(available: 0) } - let verifiedBalance: Zatoshi = synchronizerState.shieldedBalance.verified - let balance: Zatoshi = synchronizerState.shieldedBalance.total - let diff = balance - verifiedBalance - - return BalanceData( - balance: verifiedBalance.decimalValue.decimalValue, - locked: diff.decimalValue.decimalValue + return VerifiedBalanceData( + fullBalance: synchronizerState.shieldedBalance.total.decimalValue.decimalValue, + available: synchronizerState.shieldedBalance.verified.decimalValue.decimalValue ) } @@ -571,24 +574,7 @@ extension ZcashAdapter: IAdapter { } func start() { - guard !state.isPrepairing else { // postpone start library until preparing will finish - logger?.log(level: .debug, message: "Can't start because preparing!") - waitForStart = true - return - } - - if zAddress == nil { // else we need to try prepare library again - logger?.log(level: .debug, message: "No address, try to prepare kit again!") - prepare(seedData: seedData, walletBirthday: birthday, for: initMode) - - return - } - - waitForStart = false // if we has address just start syncing library or downloading sapling data - if saplingDataExist() { - logger?.log(level: .debug, message: "Start syncing kit!") - syncMain() - } + prepare(seedData: seedData, walletBirthday: birthday, for: initMode) } func stop() { @@ -597,7 +583,7 @@ extension ZcashAdapter: IAdapter { } func refresh() { - start() + startSynchronizer() } private func syncMain() { @@ -632,7 +618,7 @@ extension ZcashAdapter: IAdapter { balanceState = """ shielded balance total: \(balanceData.balanceTotal.description) - verified: \(balanceData.balance) + verified: \(balanceData.available) transparent balance total: \(String(describing: status.transparentBalance.total)) verified: \(String(describing: status.transparentBalance.verified)) @@ -726,7 +712,7 @@ extension ZcashAdapter: ISendZcashAdapter { } var availableBalance: Decimal { - max(0, balanceData.balance - fee) // TODO: check + max(0, balanceData.available - fee) } func validate(address: String) throws -> AddressType { diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashTransactionPool.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashTransactionPool.swift index f3e77497de..7fccd05526 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashTransactionPool.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashTransactionPool.swift @@ -1,6 +1,6 @@ import Foundation -import ZcashLightClientKit import RxSwift +import ZcashLightClientKit class ZcashTransactionPool { private var confirmedTransactions = Set() @@ -8,7 +8,6 @@ class ZcashTransactionPool { private let synchronizer: Synchronizer private let receiveAddress: SaplingAddress - init(receiveAddress: SaplingAddress, synchronizer: Synchronizer) { self.receiveAddress = receiveAddress self.synchronizer = synchronizer @@ -45,8 +44,16 @@ class ZcashTransactionPool { private func transactionWithAdditional(tx: ZcashTransaction.Overview, lastBlockHeight: Int) async throws -> ZcashTransactionWrapper? { let memos: [Memo] = (try? await synchronizer.getMemos(for: tx)) ?? [] + let firstMemo = memos + .compactMap { $0.toString() } + .first + let recipients = await synchronizer.getRecipients(for: tx) - return ZcashTransactionWrapper(tx: tx, memo: memos.first, recipient: recipients.first, lastBlockHeight: lastBlockHeight) + let firstAddress = recipients + .filter { $0.hasAddress } + .first + + return ZcashTransactionWrapper(tx: tx, memo: firstMemo, recipient: firstAddress, lastBlockHeight: lastBlockHeight) } private func sync(own: inout Set, incoming: [ZcashTransactionWrapper]) { @@ -63,7 +70,7 @@ class ZcashTransactionPool { func sync(transactions: [ZcashTransaction.Overview], lastBlockHeight: Int) async -> [ZcashTransactionWrapper] { let txs = await zcashTransactions(transactions, lastBlockHeight: lastBlockHeight) - // todo: sync pending and confirmed but How? + // TODO: sync pending and confirmed but How? sync(own: &confirmedTransactions, incoming: txs) return txs } @@ -71,11 +78,9 @@ class ZcashTransactionPool { func transaction(by hash: String) -> ZcashTransactionWrapper? { transactions(filter: .all).first { $0.transactionHash == hash } } - } extension ZcashTransactionPool { - var all: [ZcashTransactionWrapper] { transactions(filter: .all) } @@ -88,9 +93,17 @@ extension ZcashTransactionPool { } if let index = transactions.firstIndex(where: { $0.transactionHash == transaction.transactionHash }) { - return Single.just((Array(transactions.suffix(from: index + 1).prefix(limit)))) + return Single.just(Array(transactions.suffix(from: index + 1).prefix(limit))) } return Single.just([]) } +} +extension TransactionRecipient { + var hasAddress: Bool { + switch self { + case .address: return true + case .internalAccount: return false + } + } } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashTransactionWrapper.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashTransactionWrapper.swift index 5c334a9c63..ea287a82ac 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashTransactionWrapper.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/ZcashTransactionWrapper.swift @@ -16,7 +16,7 @@ class ZcashTransactionWrapper { let memo: String? let failed: Bool - init?(tx: ZcashTransaction.Overview, memo: Memo?, recipient: TransactionRecipient?, lastBlockHeight: Int) { + init?(tx: ZcashTransaction.Overview, memo: String?, recipient: TransactionRecipient?, lastBlockHeight: Int) { raw = tx.raw transactionHash = tx.rawID.hs.reversedHex transactionIndex = tx.index ?? 0 @@ -32,7 +32,7 @@ class ZcashTransactionWrapper { timestamp = failed ? 0 : (tx.blockTime ?? Date().timeIntervalSince1970) // need this to update pending transactions and shows on transaction tab value = tx.value fee = tx.fee - self.memo = memo.flatMap { $0.toString() } + self.memo = memo } } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Managers/RateAppManager.swift b/UnstoppableWallet/UnstoppableWallet/Core/Managers/RateAppManager.swift index 0431b5eb51..3c54adbe2e 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Managers/RateAppManager.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Managers/RateAppManager.swift @@ -29,7 +29,7 @@ class RateAppManager { return false } - return adapter.balanceData.balance > 0 + return adapter.balanceData.available > 0 } guard hasBalance else { diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Storage/StorageMigrator.swift b/UnstoppableWallet/UnstoppableWallet/Core/Storage/StorageMigrator.swift index 521395a3c1..d9c1e3eb91 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Storage/StorageMigrator.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Storage/StorageMigrator.swift @@ -669,20 +669,20 @@ class StorageMigrator { try record.insert(db) } - // EnabledWalletCache + // EnabledWalletCache_v_0_36 if try db.tableExists("enabled_wallet_caches") { try db.drop(table: "enabled_wallet_caches") } - try db.create(table: EnabledWalletCache.databaseTableName) { t in - t.column(EnabledWalletCache.Columns.tokenQueryId.name, .text).notNull() + try db.create(table: EnabledWalletCache_v_0_36.databaseTableName) { t in + t.column(EnabledWalletCache_v_0_36.Columns.tokenQueryId.name, .text).notNull() t.column("coinSettingsId", .text).notNull() - t.column(EnabledWalletCache.Columns.accountId.name, .text).notNull() - t.column(EnabledWalletCache.Columns.balance.name, .text).notNull() - t.column(EnabledWalletCache.Columns.balanceLocked.name, .text).notNull() + t.column(EnabledWalletCache_v_0_36.Columns.accountId.name, .text).notNull() + t.column(EnabledWalletCache_v_0_36.Columns.balance.name, .text).notNull() + t.column(EnabledWalletCache_v_0_36.Columns.balanceLocked.name, .text).notNull() - t.primaryKey([EnabledWalletCache.Columns.tokenQueryId.name, EnabledWalletCache.Columns.accountId.name], onConflict: .replace) + t.primaryKey([EnabledWalletCache_v_0_36.Columns.tokenQueryId.name, EnabledWalletCache_v_0_36.Columns.accountId.name], onConflict: .replace) } } @@ -730,14 +730,14 @@ class StorageMigrator { } migrator.registerMigration("Update EnabledWallet entities") { db in - try db.drop(table: EnabledWalletCache.databaseTableName) - try db.create(table: EnabledWalletCache.databaseTableName) { t in - t.column(EnabledWalletCache.Columns.tokenQueryId.name, .text).notNull() - t.column(EnabledWalletCache.Columns.accountId.name, .text).notNull() - t.column(EnabledWalletCache.Columns.balance.name, .text).notNull() - t.column(EnabledWalletCache.Columns.balanceLocked.name, .text).notNull() + try db.drop(table: EnabledWalletCache_v_0_36.databaseTableName) + try db.create(table: EnabledWalletCache_v_0_36.databaseTableName) { t in + t.column(EnabledWalletCache_v_0_36.Columns.tokenQueryId.name, .text).notNull() + t.column(EnabledWalletCache_v_0_36.Columns.accountId.name, .text).notNull() + t.column(EnabledWalletCache_v_0_36.Columns.balance.name, .text).notNull() + t.column(EnabledWalletCache_v_0_36.Columns.balanceLocked.name, .text).notNull() - t.primaryKey([EnabledWalletCache.Columns.tokenQueryId.name, EnabledWalletCache.Columns.accountId.name], onConflict: .replace) + t.primaryKey([EnabledWalletCache_v_0_36.Columns.tokenQueryId.name, EnabledWalletCache_v_0_36.Columns.accountId.name], onConflict: .replace) } var enabledWallets: [EnabledWallet] = [] @@ -773,6 +773,17 @@ class StorageMigrator { } } + migrator.registerMigration("Update EnabledWalletCache fields") { db in + try db.drop(table: EnabledWalletCache_v_0_36.databaseTableName) + try db.create(table: EnabledWalletCache.databaseTableName) { t in + t.column(EnabledWalletCache.Columns.tokenQueryId.name, .text).notNull() + t.column(EnabledWalletCache.Columns.accountId.name, .text).notNull() + t.column(EnabledWalletCache.Columns.balances.name, .text).notNull() + + t.primaryKey([EnabledWalletCache.Columns.tokenQueryId.name, EnabledWalletCache_v_0_36.Columns.accountId.name], onConflict: .replace) + } + } + try migrator.migrate(dbPool) } diff --git a/UnstoppableWallet/UnstoppableWallet/Models/BalanceData.swift b/UnstoppableWallet/UnstoppableWallet/Models/BalanceData.swift index a897bb2a69..3a07913ada 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/BalanceData.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/BalanceData.swift @@ -1,26 +1,170 @@ import Foundation -struct BalanceData: Equatable { - let balance: Decimal +class BalanceData: Codable, Equatable { + let available: Decimal + + enum CodingKeys: String, CodingKey { + case available + } + + init(available: Decimal) { + self.available = available + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(available, forKey: .available) + } + + var balanceTotal: Decimal { + available + } + + var sendBeforeSync: Bool { + false + } + + var customStates: [CustomState] { + [] + } + + static func == (lhs: BalanceData, rhs: BalanceData) -> Bool { + lhs.available == rhs.available + } +} + +extension BalanceData { + private static var types: [Decodable.Type] { [VerifiedBalanceData.self, LockedBalanceData.self] } + + static func instance(data: Data) throws -> BalanceData { + let decoder = JSONDecoder() + for type in types { + if let decoded = try? decoder.decode(type, from: data), + let instance = decoded as? BalanceData + { + return instance + } + } + return try decoder.decode(BalanceData.self, from: data) + } + + struct CustomState { + let title: String + let value: Decimal + let infoTitle: String + let infoDescription: String + } +} + +class LockedBalanceData: BalanceData { let locked: Decimal - let staked: Decimal - let frozen: Decimal - init(balance: Decimal, locked: Decimal = 0, staked: Decimal = 0, frozen: Decimal = 0) { - self.balance = balance + init(available: Decimal, locked: Decimal = 0) { self.locked = locked - self.staked = staked - self.frozen = frozen + super.init(available: available) } - var balanceTotal: Decimal { - balance + locked + staked + frozen + enum CodingKeys: String, CodingKey { + case locked + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + locked = try container.decode(Decimal.self, forKey: .locked) + + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(locked, forKey: .locked) + } + + override var balanceTotal: Decimal { + super.balanceTotal + locked } - static func ==(lhs: BalanceData, rhs: BalanceData) -> Bool { - lhs.balance == rhs.balance && - lhs.locked == rhs.locked && - lhs.staked == rhs.staked && - lhs.frozen == rhs.frozen + override var customStates: [CustomState] { + var states = super.customStates + if !locked.isZero { + states.append( + CustomState( + title: "balance.token.locked".localized, + value: locked, + infoTitle: "balance.token.locked.info.title".localized, + infoDescription: "balance.token.locked.info.description".localized + ) + ) + } + return states + } + + static func == (lhs: LockedBalanceData, rhs: LockedBalanceData) -> Bool { + lhs.available == rhs.available && + lhs.locked == rhs.locked } } + +class VerifiedBalanceData: BalanceData { + let fullBalance: Decimal + + override var balanceTotal: Decimal { super.balanceTotal } + override var sendBeforeSync: Bool { true } + + init(fullBalance: Decimal, available: Decimal) { + self.fullBalance = fullBalance + super.init(available: available) + } + + enum CodingKeys: String, CodingKey { + case full + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + fullBalance = try container.decode(Decimal.self, forKey: .full) + + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(fullBalance, forKey: .full) + } + + override var customStates: [CustomState] { + var states = super.customStates + let processingBalance = fullBalance - available + if !processingBalance.isZero { + states.append( + CustomState( + title: "balance.token.processing".localized, + value: processingBalance, + infoTitle: "balance.token.processing.info.title".localized, + infoDescription: "balance.token.processing.info.description".localized + ) + ) + } + return states + } +} + +// TODO: implement when will be needed +// let staked: Decimal +// let frozen: Decimal +// CustomState( +// title: "balance.token.staked".localized, +// value: item.balanceData.staked, +// infoTitle: "balance.token.staked.info.title".localized, +// infoDescription: "balance.token.staked.info.description".localized +// ), +// CustomState( +// title: "balance.token.frozen".localized, +// value: item.balanceData.frozen, +// infoTitle: "balance.token.frozen.info.title".localized, +// infoDescription: "balance.token.frozen.info.description".localized +// ), diff --git a/UnstoppableWallet/UnstoppableWallet/Models/EnabledWalletCache.swift b/UnstoppableWallet/UnstoppableWallet/Models/EnabledWalletCache.swift index 88ce2f1547..0dfbbc6823 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/EnabledWalletCache.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/EnabledWalletCache.swift @@ -4,20 +4,23 @@ import GRDB class EnabledWalletCache: Record { let tokenQueryId: String let accountId: String - let balance: Decimal - let balanceLocked: Decimal + let balances: Data init(wallet: Wallet, balanceData: BalanceData) { tokenQueryId = wallet.token.tokenQuery.id accountId = wallet.account.id - balance = balanceData.balance - balanceLocked = balanceData.locked + balances = balanceData.encoded super.init() } var balanceData: BalanceData { - BalanceData(balance: balance, locked: balanceLocked) + do { + let balanceData = try BalanceData.instance(data: balances) + return balanceData + } catch { + return BalanceData(available: 0) + } } override class var databaseTableName: String { @@ -25,14 +28,13 @@ class EnabledWalletCache: Record { } enum Columns: String, ColumnExpression { - case tokenQueryId, accountId, balance, balanceLocked // todo: migration - remove coinSettingsId + case tokenQueryId, accountId, balances } required init(row: Row) { tokenQueryId = row[Columns.tokenQueryId] accountId = row[Columns.accountId] - balance = row[Columns.balance] - balanceLocked = row[Columns.balanceLocked] + balances = row[Columns.balances] super.init(row: row) } @@ -40,8 +42,7 @@ class EnabledWalletCache: Record { override func encode(to container: inout PersistenceContainer) { container[Columns.tokenQueryId] = tokenQueryId container[Columns.accountId] = accountId - container[Columns.balance] = balance - container[Columns.balanceLocked] = balanceLocked + container[Columns.balances] = balances } } diff --git a/UnstoppableWallet/UnstoppableWallet/Models/EnabledWalletCache_v_0_36.swift b/UnstoppableWallet/UnstoppableWallet/Models/EnabledWalletCache_v_0_36.swift new file mode 100644 index 0000000000..c57622703f --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Models/EnabledWalletCache_v_0_36.swift @@ -0,0 +1,51 @@ +import Foundation +import GRDB + +class EnabledWalletCache_v_0_36: Record { + let tokenQueryId: String + let accountId: String + let balance: Decimal + let balanceLocked: Decimal + let balances: Data + + init(wallet: Wallet, balanceData: BalanceData) { + tokenQueryId = wallet.token.tokenQuery.id + accountId = wallet.account.id + balance = balanceData.available + balanceLocked = 0 + + balances = Data() + + super.init() + } + + var balanceData: BalanceData { + BalanceData(available: balance) + } + + override class var databaseTableName: String { + "enabled_wallet_caches" + } + + enum Columns: String, ColumnExpression { + case tokenQueryId, accountId, balance, balanceLocked // todo: migration - remove coinSettingsId + } + + required init(row: Row) { + tokenQueryId = row[Columns.tokenQueryId] + accountId = row[Columns.accountId] + balance = row[Columns.balance] + balanceLocked = row[Columns.balanceLocked] + balances = Data() + + super.init(row: row) + } + + override func encode(to container: inout PersistenceContainer) { + container[Columns.tokenQueryId] = tokenQueryId + container[Columns.accountId] = accountId + container[Columns.balance] = balance + container[Columns.balanceLocked] = balanceLocked + } + +} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/CoinSelect/CoinSelectService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/CoinSelect/CoinSelectService.swift index 2ca95f91c9..7584112392 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/CoinSelect/CoinSelectService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/CoinSelect/CoinSelectService.swift @@ -51,7 +51,7 @@ class CoinSelectService { return nil } - return (token: wallet.token, balance: adapter.balanceData.balance) + return (token: wallet.token, balance: adapter.balanceData.available) } return balanceCoins.map { token, balance -> Item in diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/SendEvm/SendEvmService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/SendEvm/SendEvmService.swift index 49d8044544..8cd8211bbf 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/SendEvm/SendEvmService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/SendEvm/SendEvmService.swift @@ -76,7 +76,7 @@ class SendEvmService { throw AmountError.invalidDecimal } - guard amount <= adapter.balanceData.balance else { + guard amount <= adapter.balanceData.available else { throw AmountError.insufficientBalance } @@ -100,7 +100,7 @@ extension SendEvmService { extension SendEvmService: IAvailableBalanceService { var availableBalance: DataStatus { - .completed(adapter.balanceData.balance) + .completed(adapter.balanceData.available) } var availableBalanceObservable: Observable> { @@ -120,7 +120,7 @@ extension SendEvmService: IAmountInputService { } var balance: Decimal? { - adapter.balanceData.balance + adapter.balanceData.available } var amountObservable: Observable { @@ -132,7 +132,7 @@ extension SendEvmService: IAmountInputService { } var balanceObservable: Observable { - .just(adapter.balanceData.balance) + .just(adapter.balanceData.available) } func onChange(amount: Decimal) { @@ -141,7 +141,7 @@ extension SendEvmService: IAmountInputService { evmAmount = try validEvmAmount(amount: amount) var amountWarning: AmountWarning? = nil - if amount.isEqual(to: adapter.balanceData.balance) { + if amount.isEqual(to: adapter.balanceData.available) { switch sendToken.type { case .native: amountWarning = AmountWarning.coinNeededForFee default: () diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/SendTron/SendTronService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/SendTron/SendTronService.swift index 2c065365cf..dc2103693e 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/SendTron/SendTronService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/SendTron/SendTronService.swift @@ -82,7 +82,7 @@ class SendTronService { throw AmountError.invalidDecimal } - guard amount <= adapter.balanceData.balance else { + guard amount <= adapter.balanceData.available else { throw AmountError.insufficientBalance } @@ -114,7 +114,7 @@ extension SendTronService { extension SendTronService: IAvailableBalanceService { var availableBalance: DataStatus { - .completed(adapter.balanceData.balance) + .completed(adapter.balanceData.available) } var availableBalanceObservable: Observable> { @@ -134,7 +134,7 @@ extension SendTronService: IAmountInputService { } var balance: Decimal? { - adapter.balanceData.balance + adapter.balanceData.available } var amountObservable: Observable { @@ -146,7 +146,7 @@ extension SendTronService: IAmountInputService { } var balanceObservable: Observable { - .just(adapter.balanceData.balance) + .just(adapter.balanceData.available) } func onChange(amount: Decimal) { @@ -155,7 +155,7 @@ extension SendTronService: IAmountInputService { tronAmount = try validTronAmount(amount: amount) var amountWarning: AmountWarning? = nil - if amount.isEqual(to: adapter.balanceData.balance) { + if amount.isEqual(to: adapter.balanceData.available) { switch sendToken.type { case .native: amountWarning = AmountWarning.coinNeededForFee default: () diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Swap/Adapters/OneInch/OneInchService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Swap/Adapters/OneInch/OneInchService.swift index 3a0a7d2bbc..ef136c7943 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Swap/Adapters/OneInch/OneInchService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Swap/Adapters/OneInch/OneInchService.swift @@ -173,7 +173,7 @@ class OneInchService { } private func balance(token: MarketKit.Token) -> Decimal? { - (adapterManager.adapter(for: token) as? IBalanceAdapter)?.balanceData.balance + (adapterManager.adapter(for: token) as? IBalanceAdapter)?.balanceData.available } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Swap/Adapters/Uniswap/UniswapService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Swap/Adapters/Uniswap/UniswapService.swift index af13819c1a..24e4777909 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Swap/Adapters/Uniswap/UniswapService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Swap/Adapters/Uniswap/UniswapService.swift @@ -173,7 +173,7 @@ class UniswapService { } private func balance(token: MarketKit.Token) -> Decimal? { - (adapterManager.adapter(for: token) as? IBalanceAdapter)?.balanceData.balance + (adapterManager.adapter(for: token) as? IBalanceAdapter)?.balanceData.available } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Swap/Adapters/UniswapV3/UniswapV3Service.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Swap/Adapters/UniswapV3/UniswapV3Service.swift index 2d8ac16b1d..99349555b1 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Swap/Adapters/UniswapV3/UniswapV3Service.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Swap/Adapters/UniswapV3/UniswapV3Service.swift @@ -173,7 +173,7 @@ class UniswapV3Service { } private func balance(token: MarketKit.Token) -> Decimal? { - (adapterManager.adapter(for: token) as? IBalanceAdapter)?.balanceData.balance + (adapterManager.adapter(for: token) as? IBalanceAdapter)?.balanceData.available } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/Token/DataSources/WalletTokenBalance/WalletTokenBalanceService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/Token/DataSources/WalletTokenBalance/WalletTokenBalanceService.swift index fe816e2892..bcbc6d358f 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/Token/DataSources/WalletTokenBalance/WalletTokenBalanceService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/Token/DataSources/WalletTokenBalance/WalletTokenBalanceService.swift @@ -78,7 +78,7 @@ class WalletTokenBalanceService { } private var fallbackBalanceData: BalanceData { - BalanceData(balance: 0) + BalanceData(available: 0) } private var fallbackAdapterState: AdapterState { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/Token/DataSources/WalletTokenBalance/WalletTokenBalanceViewItemFactory.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/Token/DataSources/WalletTokenBalance/WalletTokenBalanceViewItemFactory.swift index e996178c80..a7b25752d8 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/Token/DataSources/WalletTokenBalance/WalletTokenBalanceViewItemFactory.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/Token/DataSources/WalletTokenBalance/WalletTokenBalanceViewItemFactory.swift @@ -1,5 +1,5 @@ -import Foundation import CurrencyKit +import Foundation import MarketKit class WalletTokenBalanceViewItemFactory { @@ -14,11 +14,16 @@ class WalletTokenBalanceViewItemFactory { var buttons = [WalletModule.Button: ButtonState]() switch item.element { - case .wallet(let wallet): + case let .wallet(wallet): if item.watchAccount { buttons[.address] = .enabled } else { - let sendButtonState: ButtonState = item.state == .synced ? .enabled : .disabled + let sendButtonState: ButtonState + switch item.state { + case .synced: sendButtonState = .enabled + case .syncing, .customSyncing: sendButtonState = item.balanceData.sendBeforeSync ? .enabled : .disabled + case .stopped, .notSynced: sendButtonState = .disabled + } buttons[.send] = sendButtonState buttons[.receive] = .enabled @@ -27,7 +32,7 @@ class WalletTokenBalanceViewItemFactory { buttons[.swap] = sendButtonState } } - case .cexAsset(let cexAsset): + case let .cexAsset(cexAsset): buttons[.withdraw] = cexAsset.withdrawEnabled ? .enabled : .disabled buttons[.deposit] = cexAsset.depositEnabled ? .enabled : .disabled } @@ -41,21 +46,21 @@ class WalletTokenBalanceViewItemFactory { let state = item.state return WalletTokenBalanceViewModel.ViewItem( - isMainNet: item.isMainNet, - iconUrlString: iconUrlString(coin: item.element.coin, state: state), - placeholderIconName: item.element.wallet?.token.placeholderImageName ?? "placeholder_circle_32", - syncSpinnerProgress: syncSpinnerProgress(state: state), - indefiniteSearchCircle: indefiniteSearchCircle(state: state), - failedImageViewVisible: failedImageViewVisible(state: state), - balanceValue: balanceValue(item: item, balanceHidden: balanceHidden), - descriptionValue: descriptionValue(item: item, balanceHidden: balanceHidden), - customStates: customStates(item: item, balanceHidden: balanceHidden) + isMainNet: item.isMainNet, + iconUrlString: iconUrlString(coin: item.element.coin, state: state), + placeholderIconName: item.element.wallet?.token.placeholderImageName ?? "placeholder_circle_32", + syncSpinnerProgress: syncSpinnerProgress(state: state), + indefiniteSearchCircle: indefiniteSearchCircle(state: state), + failedImageViewVisible: failedImageViewVisible(state: state), + balanceValue: balanceValue(item: item, balanceHidden: balanceHidden), + descriptionValue: descriptionValue(item: item, balanceHidden: balanceHidden), + customStates: customStates(item: item, balanceHidden: balanceHidden) ) } private func descriptionValue(item: WalletTokenBalanceService.BalanceItem, balanceHidden: Bool) -> (text: String?, dimmed: Bool) { if case let .syncing(progress, lastBlockDate) = item.state { - var text: String = "" + var text = "" if let progress = progress { text = "balance.syncing_percent".localized("\(progress)%") } else { @@ -86,7 +91,7 @@ class WalletTokenBalanceViewItemFactory { private func syncSpinnerProgress(state: AdapterState) -> Int? { switch state { - case let .syncing(progress, _), .customSyncing(_, _, let progress): + case let .syncing(progress, _), let .customSyncing(_, _, progress): return progress.map { max(minimumProgress, $0) } ?? infiniteProgress default: return nil } @@ -116,8 +121,8 @@ class WalletTokenBalanceViewItemFactory { private func coinValue(value: Decimal, decimalCount: Int, symbol: String? = nil, balanceHidden: Bool, state: AdapterState) -> (text: String?, dimmed: Bool) { ( - text: balanceHidden ? "*****" : ValueFormatter.instance.formatFull(value: value, decimalCount: decimalCount, symbol: symbol), - dimmed: state != .synced + text: balanceHidden ? "*****" : ValueFormatter.instance.formatFull(value: value, decimalCount: decimalCount, symbol: symbol), + dimmed: state != .synced ) } @@ -130,66 +135,22 @@ class WalletTokenBalanceViewItemFactory { let currencyValue = CurrencyValue(currency: price.currency, value: value * price.value) return ( - text: balanceHidden ? "*****" : ValueFormatter.instance.formatFull(currencyValue: currencyValue), - dimmed: state != .synced || priceItem.expired + text: balanceHidden ? "*****" : ValueFormatter.instance.formatFull(currencyValue: currencyValue), + dimmed: state != .synced || priceItem.expired ) } private func customStates(item: WalletTokenBalanceService.BalanceItem, balanceHidden: Bool) -> [WalletTokenBalanceViewModel.BalanceCustomStateViewItem] { - let stateItems = [ - CustomStateItem( - title: "balance.token.locked".localized, - amount: item.balanceData.locked, - infoTitle: "balance.token.locked.info.title".localized, - infoDescription: "balance.token.locked.info.description".localized - ), - CustomStateItem( - title: "balance.token.staked".localized, - amount: item.balanceData.staked, - infoTitle: "balance.token.staked.info.title".localized, - infoDescription: "balance.token.staked.info.description".localized - ), - CustomStateItem( - title: "balance.token.frozen".localized, - amount: item.balanceData.frozen, - infoTitle: "balance.token.frozen.info.title".localized, - infoDescription: "balance.token.frozen.info.description".localized - ), - ] - - return stateItems - .compactMap { - lockedAmountViewItem( - customStateItem: $0, - item: item, - balanceHidden: balanceHidden - ) - } - } - - private func lockedAmountViewItem(customStateItem: CustomStateItem, item: WalletTokenBalanceService.BalanceItem, balanceHidden: Bool) -> WalletTokenBalanceViewModel.BalanceCustomStateViewItem? { - guard customStateItem.amount > 0 else { - return nil - } - - let value = coinValue(value: customStateItem.amount, decimalCount: item.element.decimals, symbol: item.element.coin?.code, balanceHidden: balanceHidden, state: item.state) - return .init( - title: customStateItem.title, - amountValue: value, - infoTitle: customStateItem.infoTitle, - infoDescription: customStateItem.infoDescription - ) - } - -} - -extension WalletTokenBalanceViewItemFactory { - - private struct CustomStateItem { - let title: String - let amount: Decimal - let infoTitle: String - let infoDescription: String + item.balanceData + .customStates + .map { + let value = coinValue(value: $0.value, decimalCount: item.element.decimals, symbol: item.element.coin?.code, balanceHidden: balanceHidden, state: item.state) + return .init( + title: $0.title, + amountValue: value, + infoTitle: $0.infoTitle, + infoDescription: $0.infoDescription + ) + } } - } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/TokenList/WalletTokenListService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/TokenList/WalletTokenListService.swift index bfc40d0135..4a53d0034b 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/TokenList/WalletTokenListService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/TokenList/WalletTokenListService.swift @@ -158,7 +158,7 @@ class WalletTokenListService: IWalletTokenListService { } private var fallbackBalanceData: BalanceData { - BalanceData(balance: 0) + BalanceData(available: 0) } private var fallbackAdapterState: AdapterState { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletCexElementService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletCexElementService.swift index 093ef2c326..39b1ae553c 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletCexElementService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletCexElementService.swift @@ -90,7 +90,7 @@ extension WalletCexElementService: IWalletElementService { return nil } - return BalanceData(balance: cexAsset.freeBalance, locked: cexAsset.lockedBalance) + return VerifiedBalanceData(fullBalance: cexAsset.freeBalance + cexAsset.lockedBalance, available: cexAsset.freeBalance) } func state(element: WalletModule.Element) -> AdapterState? { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletService.swift index bd4a20827a..86f034259e 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletService.swift @@ -285,7 +285,7 @@ class WalletService { } private var fallbackBalanceData: BalanceData { - BalanceData(balance: 0) + BalanceData(available: 0) } private var fallbackAdapterState: AdapterState { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletSorter.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletSorter.swift index 132ffbd6b2..d03be88f48 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletSorter.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletSorter.swift @@ -52,7 +52,7 @@ protocol ISortableWalletItem { extension WalletService.Item: ISortableWalletItem { var balance: Decimal { - balanceData.balance + balanceData.available } var name: String { @@ -68,7 +68,7 @@ extension WalletService.Item: ISortableWalletItem { extension WalletTokenListService.Item: ISortableWalletItem { var balance: Decimal { - balanceData.balance + balanceData.available } var name: String { diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/Extensions/HudHelper.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/Extensions/HudHelper.swift index 1bf5328fbc..56df90caf6 100644 --- a/UnstoppableWallet/UnstoppableWallet/UserInterface/Extensions/HudHelper.swift +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/Extensions/HudHelper.swift @@ -117,7 +117,7 @@ extension HudHelper { var showingTime: TimeInterval? { switch self { - case .waitingForSession, .disconnectingWalletConnect, .enabling: return nil + case .waitingForSession, .disconnectingWalletConnect, .sending, .enabling: return nil default: return 2 } } diff --git a/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings b/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings index 007468ec22..230b8dddd7 100644 --- a/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings +++ b/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings @@ -322,6 +322,9 @@ Go to Settings - > %@ and allow access to the camera."; "balance.token.locked" = "Locked"; "balance.token.locked.info.title" = "TimeLock"; "balance.token.locked.info.description" = "The sender sent these funds with a spending lock that will expire on the shown date. \n\nNo worries, the received Bitcoins are already yours, but until the lock period expires you cannot spend them on the Bitcoin network."; +"balance.token.processing" = "Processing"; +"balance.token.processing.info.title" = "Processing amount"; +"balance.token.processing.info.description" = "Transactions with this amount still syncing. And when they will be confirmed, this tokens will be available for spending"; "balance.token.staked" = "Staked"; "balance.token.staked.info.title" = "Staked title"; "balance.token.staked.info.description" = "Staked Description Text";