Updated on 2026-08-14

This commit is contained in:
Tangem 2023-10-10 22:14:03 +03:00
parent 8027be74aa
commit a730af0f1f
9 changed files with 174 additions and 35 deletions

View file

@ -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(

View file

@ -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

View file

@ -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"
}
}

View file

@ -51,6 +51,12 @@ internal class DefaultNetworksRepository(
}
}.cancellable()
override suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, networks: Set<Network>) {
withContext(dispatchers.io) {
fetchNetworksPendingTransactionsIfCacheExpired(userWalletId, networks, false)
}
}
override suspend fun getNetworkStatusesSync(
userWalletId: UserWalletId,
networks: Set<Network>,
@ -76,6 +82,22 @@ internal class DefaultNetworksRepository(
}
}
private suspend fun fetchNetworksPendingTransactionsIfCacheExpired(
userWalletId: UserWalletId,
networks: Set<Network>,
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<CryptoCurrency> {
val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
"Unable to find user wallet with provided ID: $userWalletId"

View file

@ -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,

View file

@ -36,6 +36,15 @@ interface WalletManagersFacade {
extraTokens: Set<CryptoCurrency.Token>,
): 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.
*

View file

@ -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<Network>) {
coroutineScope {
networksRepository.fetchNetworkPendingTransactions(userWalletId, networks)
}
}
}

View file

@ -20,6 +20,14 @@ interface NetworksRepository {
*/
fun getNetworkStatusesUpdates(userWalletId: UserWalletId, networks: Set<Network>): Flow<Set<NetworkStatus>>
/**
* 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<Network>)
/**
* Retrieves network statuses of specified blockchain networks for a specific user wallet.
*

View file

@ -21,6 +21,10 @@ internal class MockNetworksRepository(
return statuses.map { it.getOrElse { e -> throw e } }
}
override suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, networks: Set<Network>) {
// no-op
}
override suspend fun getNetworkStatusesSync(
userWalletId: UserWalletId,
networks: Set<Network>,