From a730af0f1f5e10815bfd67ae647987fbaef5cfa9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Oct 2023 22:14:03 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/di/domain/TokensDomainModule.kt | 8 +++ .../send/redux/middlewares/SendMiddleware.kt | 57 ++++++++++--------- .../tap/features/send/ui/SendViewModel.kt | 10 +--- .../repository/DefaultNetworksRepository.kt | 57 +++++++++++++++++++ .../DefaultWalletManagersFacade.kt | 34 +++++++++++ .../walletmanager/WalletManagersFacade.kt | 9 +++ .../tokens/FetchPendingTransactionsUseCase.kt | 22 +++++++ .../tokens/repository/NetworksRepository.kt | 8 +++ .../repository/MockNetworksRepository.kt | 4 ++ 9 files changed, 174 insertions(+), 35 deletions(-) create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchPendingTransactionsUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 9bbf1229fa..330be53972 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -28,6 +28,14 @@ internal object TokensDomainModule { return FetchTokenListUseCase(currenciesRepository, networksRepository, quotesRepository) } + @Provides + @ViewModelScoped + fun provideFetchPendingTransactionsUseCase( + networksRepository: NetworksRepository, + ): FetchPendingTransactionsUseCase { + return FetchPendingTransactionsUseCase(networksRepository) + } + @Provides @ViewModelScoped fun provideGetTokenListUseCase( diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 4e0490a4f9..1af3704e50 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -12,7 +12,6 @@ import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard -import com.tangem.common.services.Result import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.NavigationAction import com.tangem.domain.common.TapWorkarounds.isStart2Coin @@ -174,33 +173,35 @@ private fun sendTransaction( transactionExtras.tonMemoState?.memo?.let { txData = txData.copy(extras = TonTransactionExtras(it)) } scope.launch { - val updateWalletResult = walletManager.safeUpdate() - if (updateWalletResult is Result.Failure) { - withMainContext { - when (val error = updateWalletResult.error) { - is TapError -> store.dispatchErrorNotification(error) - is BlockchainSdkError -> { - updateFeedbackManagerInfo( - walletManager = walletManager, - amountToSend = amountToSend, - feeAmount = fee.amount, - destinationAddress = destinationAddress, - ) - dispatch(SendAction.Dialog.SendTransactionFails.BlockchainSdkError(error = error)) - } - else -> { - val tapError = if (error.message == null) { - TapError.UnknownError - } else { - TapError.CustomError(error.message!!) - } - store.dispatchErrorNotification(tapError) - } - } - dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED)) - } - return@launch - } + // TODO: Risky commented this part, unknown logic, need to test if removed + // TODO: [REDACTED_JIRA] + // val updateWalletResult = walletManager.safeUpdate() + // if (updateWalletResult is Result.Failure) { + // withMainContext { + // when (val error = updateWalletResult.error) { + // is TapError -> store.dispatchErrorNotification(error) + // is BlockchainSdkError -> { + // updateFeedbackManagerInfo( + // walletManager = walletManager, + // amountToSend = amountToSend, + // feeAmount = fee.amount, + // destinationAddress = destinationAddress, + // ) + // dispatch(SendAction.Dialog.SendTransactionFails.BlockchainSdkError(error = error)) + // } + // else -> { + // val tapError = if (error.message == null) { + // TapError.UnknownError + // } else { + // TapError.CustomError(error.message!!) + // } + // store.dispatchErrorNotification(tapError) + // } + // } + // dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED)) + // } + // return@launch + // } val tangemSdk = store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).sdk val linkedTerminalState = tangemSdk.config.linkedTerminal diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendViewModel.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendViewModel.kt index 06cb71c643..999140cff9 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendViewModel.kt @@ -3,6 +3,7 @@ package com.tangem.tap.features.send.ui import androidx.lifecycle.* import com.tangem.domain.balancehiding.IsBalanceHiddenUseCase import com.tangem.domain.balancehiding.ListenToFlipsUseCase +import com.tangem.domain.tokens.FetchPendingTransactionsUseCase import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network @@ -32,6 +33,7 @@ internal class SendViewModel @Inject constructor( private val listenToFlipsUseCase: ListenToFlipsUseCase, private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase, private val getSelectedWalletUseCase: GetSelectedWalletUseCase, + private val fetchPendingTransactionsUseCase: FetchPendingTransactionsUseCase, @DelayedWork private val coroutineScope: CoroutineScope, savedStateHandle: SavedStateHandle, ) : ViewModel(), DefaultLifecycleObserver { @@ -75,12 +77,7 @@ internal class SendViewModel @Inject constructor( } private suspend fun updateForPendingTx(userWallet: UserWallet, network: Network) { - updateDelayedCurrencyStatusUseCase( - userWalletId = userWallet.walletId, - network = network, - delayMillis = UPDATE_PENDING_TX_DELAY_MILLIS, - refresh = true, - ) + fetchPendingTransactionsUseCase(userWallet.walletId, setOf(network)) } private suspend fun updateForBalance(userWallet: UserWallet, network: Network) { @@ -94,7 +91,6 @@ internal class SendViewModel @Inject constructor( companion object { private const val UPDATE_BALANCE_DELAY_MILLIS = 11000L - private const val UPDATE_PENDING_TX_DELAY_MILLIS = 1000L private const val TAG = "SendViewModel" } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index 7a8a4e726f..673b9fdf4c 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -51,6 +51,12 @@ internal class DefaultNetworksRepository( } }.cancellable() + override suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, networks: Set) { + withContext(dispatchers.io) { + fetchNetworksPendingTransactionsIfCacheExpired(userWalletId, networks, false) + } + } + override suspend fun getNetworkStatusesSync( userWalletId: UserWalletId, networks: Set, @@ -76,6 +82,22 @@ internal class DefaultNetworksRepository( } } + private suspend fun fetchNetworksPendingTransactionsIfCacheExpired( + userWalletId: UserWalletId, + networks: Set, + refresh: Boolean, + ) { + coroutineScope { + networks + .map { network -> + async { + fetchNetworkPendingTransactionsIfCacheExpired(userWalletId, network, refresh) + } + } + .awaitAll() + } + } + private suspend fun fetchNetworkStatusIfCacheExpired( userWalletId: UserWalletId, network: Network, @@ -88,6 +110,20 @@ internal class DefaultNetworksRepository( ) } + private suspend fun fetchNetworkPendingTransactionsIfCacheExpired( + userWalletId: UserWalletId, + network: Network, + refresh: Boolean, + ) { + val key = getNetworksStatusesCacheKey(userWalletId, network) + cacheRegistry.invalidate(key) + cacheRegistry.invokeOnExpire( + key = key, + skipCache = refresh, + block = { fetchNetworkPendingTransactions(userWalletId, network) }, + ) + } + private suspend fun fetchNetworkStatus(userWalletId: UserWalletId, network: Network) { val currencies = getCurrencies(userWalletId, network) @@ -110,6 +146,27 @@ internal class DefaultNetworksRepository( networksStatusesStore.store(userWalletId, networkStatus) } + private suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, network: Network) { + val currencies = getCurrencies(userWalletId, network) + + val result = walletManagersFacade.updatePendingTransactions( + userWalletId = userWalletId, + network = network, + ) + + withContext(NonCancellable) { + invalidateCacheKeyIfNeeded(userWalletId, network, result) + } + + val networkStatus = networkStatusFactory.createNetworkStatus( + network = network, + result = result, + currencies = currencies.toSet(), + ) + + networksStatusesStore.store(userWalletId, networkStatus) + } + private suspend fun getCurrencies(userWalletId: UserWalletId, network: Network): Sequence { val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { "Unable to find user wallet with provided ID: $userWalletId" diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index 01a025f741..1f4caa5690 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -57,6 +57,28 @@ class DefaultWalletManagersFacade( return getAndUpdateWalletManager(userWallet, blockchain, derivationPath, extraTokens) } + override suspend fun updatePendingTransactions( + userWalletId: UserWalletId, + network: Network, + ): UpdateWalletManagerResult { + val userWallet = getUserWallet(userWalletId) + val blockchain = Blockchain.fromId(network.id.value) + val derivationPath = network.derivationPath.value + + if (derivationPath != null && !userWallet.scanResponse.hasDerivation(blockchain, derivationPath)) { + Timber.w("Derivation missed for: $blockchain") + return UpdateWalletManagerResult.MissedDerivation + } + + val walletManager = getOrCreateWalletManager(userWalletId, blockchain, derivationPath) + if (walletManager == null || blockchain == Blockchain.Unknown) { + Timber.w("Unable to get a wallet manager for blockchain: $blockchain") + return UpdateWalletManagerResult.Unreachable + } + + return getLastWalletManagerResult(walletManager) + } + override suspend fun getExploreUrl( userWalletId: UserWalletId, network: Network, @@ -196,6 +218,18 @@ class DefaultWalletManagersFacade( } } + private fun getLastWalletManagerResult(walletManager: WalletManager): UpdateWalletManagerResult { + return try { + resultFactory.getResult(walletManager) + } catch (e: BlockchainSdkError.AccountNotFound) { + resultFactory.getNoAccountResult(walletManager = walletManager, customMessage = e.customMessage) + } catch (e: Throwable) { + Timber.w(e, "Unable to update a wallet manager for: ${walletManager.wallet.blockchain}") + + UpdateWalletManagerResult.Unreachable + } + } + override suspend fun getOrCreateWalletManager( userWalletId: UserWalletId, blockchain: Blockchain, diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt index 6df4bf3f84..19e400b067 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt @@ -36,6 +36,15 @@ interface WalletManagersFacade { extraTokens: Set, ): UpdateWalletManagerResult + /** + * Returns [UpdateWalletManagerResult] with last pending transactions + * + * @param userWalletId The ID of the user's wallet. + * @param network The network. + * @return The result of updating the wallet manager. + */ + suspend fun updatePendingTransactions(userWalletId: UserWalletId, network: Network): UpdateWalletManagerResult + /** * Returns network explorer URL of the wallet manager associated with a user's wallet and network. * diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchPendingTransactionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchPendingTransactionsUseCase.kt new file mode 100644 index 0000000000..aeda66d1f9 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchPendingTransactionsUseCase.kt @@ -0,0 +1,22 @@ +package com.tangem.domain.tokens + +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.coroutineScope + +/** + * Use case responsible for fetching current pending transactions + * + * @param networksRepository The repository for retrieving network-related data. + */ +class FetchPendingTransactionsUseCase( + private val networksRepository: NetworksRepository, +) { + + suspend operator fun invoke(userWalletId: UserWalletId, networks: Set) { + coroutineScope { + networksRepository.fetchNetworkPendingTransactions(userWalletId, networks) + } + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt index 30baab37f3..8e1b1103ff 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt @@ -20,6 +20,14 @@ interface NetworksRepository { */ fun getNetworkStatusesUpdates(userWalletId: UserWalletId, networks: Set): Flow> + /** + * Fetches pending transactions for given network + * + * @param userWalletId The unique identifier of the user wallet. + * @param networks A set of network which statuses are to be retrieved. + */ + suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, networks: Set) + /** * Retrieves network statuses of specified blockchain networks for a specific user wallet. * diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt index ffefd750e0..f345dbedeb 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt @@ -21,6 +21,10 @@ internal class MockNetworksRepository( return statuses.map { it.getOrElse { e -> throw e } } } + override suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, networks: Set) { + // no-op + } + override suspend fun getNetworkStatusesSync( userWalletId: UserWalletId, networks: Set,