Updated on 2026-08-14

This commit is contained in:
Tangem 2026-01-22 11:42:23 +04:00
parent d594af91fc
commit c3f5ea4284
10 changed files with 496 additions and 43 deletions

View file

@ -1,6 +1,7 @@
package com.tangem.tap.di.domain package com.tangem.tap.di.domain
import com.tangem.domain.blockaid.BlockAidGasEstimate import com.tangem.domain.blockaid.BlockAidGasEstimate
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.quotes.QuotesRepository import com.tangem.domain.quotes.QuotesRepository
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.transaction.FeeRepository
@ -14,6 +15,8 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import javax.inject.Singleton import javax.inject.Singleton
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
@ -234,4 +237,18 @@ internal object YieldSupplyDomainModule {
): YieldSupplyGetAvailabilityUseCase { ): YieldSupplyGetAvailabilityUseCase {
return YieldSupplyGetAvailabilityUseCase(yieldSupplyRepository) return YieldSupplyGetAvailabilityUseCase(yieldSupplyRepository)
} }
@Provides
@Singleton
fun provideYieldSupplyPendingProcessorUseCase(
yieldSupplyRepository: YieldSupplyRepository,
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
dispatcherProvider: CoroutineDispatcherProvider,
): YieldSupplyPendingTracker {
return YieldSupplyPendingTracker(
yieldSupplyRepository = yieldSupplyRepository,
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
coroutineScope = CoroutineScope(SupervisorJob() + dispatcherProvider.io),
)
}
} }

View file

@ -19,7 +19,6 @@ import com.tangem.datasource.local.preferences.utils.get
import com.tangem.datasource.local.preferences.utils.store import com.tangem.datasource.local.preferences.utils.store
import com.tangem.datasource.local.yieldsupply.YieldMarketsStore import com.tangem.datasource.local.yieldsupply.YieldMarketsStore
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.yield.supply.YieldSupplyRepository import com.tangem.domain.yield.supply.YieldSupplyRepository
@ -137,13 +136,10 @@ internal class DefaultYieldSupplyRepository(
} }
} }
override suspend fun getPendingTxHashes( override suspend fun getPendingTxHashes(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): List<String> {
userWalletId: UserWalletId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
): List<String> {
val walletManager = walletManagersFacade.getOrCreateWalletManager( val walletManager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId, userWalletId = userWalletId,
network = cryptoCurrencyStatus.currency.network, network = cryptoCurrency.network,
) ?: return emptyList() ) ?: return emptyList()
return walletManager.wallet.recentTransactions return walletManager.wallet.recentTransactions

View file

@ -1,7 +1,6 @@
package com.tangem.domain.yield.supply package com.tangem.domain.yield.supply
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.yield.supply.models.YieldMarketToken import com.tangem.domain.yield.supply.models.YieldMarketToken
import com.tangem.domain.yield.supply.models.YieldSupplyPendingStatus import com.tangem.domain.yield.supply.models.YieldSupplyPendingStatus
@ -84,13 +83,10 @@ interface YieldSupplyRepository {
* Retrieve the hashes of pending (unconfirmed) transactions for the given wallet and currency. * Retrieve the hashes of pending (unconfirmed) transactions for the given wallet and currency.
* *
* @param userWalletId the wallet to query * @param userWalletId the wallet to query
* @param cryptoCurrencyStatus the currency status containing network information * @param cryptoCurrency the currency containing network information
* @return list of transaction hashes that are still unconfirmed, or an empty list if none exist * @return list of transaction hashes that are still unconfirmed, or an empty list if none exist
*/ */
suspend fun getPendingTxHashes( suspend fun getPendingTxHashes(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): List<String>
userWalletId: UserWalletId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
): List<String>
/** /**
* Get the last saved userinitiated yield protocol action for the given wallet and currency, if any. * Get the last saved userinitiated yield protocol action for the given wallet and currency, if any.

View file

@ -20,7 +20,7 @@ class YieldSupplyEnterStatusUseCase(
cryptoCurrencyStatus.currency, cryptoCurrencyStatus.currency,
) )
val pendingTxHashes = yieldSupplyRepository val pendingTxHashes = yieldSupplyRepository
.getPendingTxHashes(userWalletId, cryptoCurrencyStatus) .getPendingTxHashes(userWalletId, cryptoCurrencyStatus.currency)
.toSet() .toSet()
val hasPendingTx = status?.txIds?.any { it in pendingTxHashes } == true val hasPendingTx = status?.txIds?.any { it in pendingTxHashes } == true

View file

@ -0,0 +1,147 @@
package com.tangem.domain.yield.supply.usecase
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.yield.supply.YieldSupplyRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import timber.log.Timber
import java.util.concurrent.ConcurrentHashMap
/**
* Periodically checks pending Yield Supply transaction hashes for a given set of wallet/currency pairs.
*
* - Call [addPending] right after sending a transaction to start tracking its hashes.
* - The checker runs every [CHECK_INTERVAL_MS] and queries repository for the current pending tx hashes.
* - If none of the stored hashes are pending anymore for a tracked entry, it triggers a single-network status refresh
* via [singleNetworkStatusFetcher] and removes the entry from tracking.
* - The periodic job stops automatically when there is nothing left to track.
*/
class YieldSupplyPendingTracker(
private val yieldSupplyRepository: YieldSupplyRepository,
private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
private val coroutineScope: CoroutineScope,
) {
private data class TrackedKey(
val userWalletId: UserWalletId,
val cryptoCurrencyId: CryptoCurrency.ID,
)
private data class TrackedEntry(
val cryptoCurrency: CryptoCurrency,
val txIds: Set<String>,
val attempts: Int = 0,
)
private val trackedEntries = ConcurrentHashMap<TrackedKey, TrackedEntry>()
private val mutex = Mutex()
private var checkingJob: Job? = null
/**
* Add tx ids to track for a wallet/currency pair. Starts periodic checking if needed.
*/
suspend fun addPending(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, txIds: List<String>) {
val key = TrackedKey(userWalletId, cryptoCurrency.id)
trackedEntries.compute(key) { _, existing ->
if (existing == null) {
TrackedEntry(
cryptoCurrency = cryptoCurrency,
txIds = txIds.toSet(),
)
} else {
existing.copy(txIds = existing.txIds + txIds)
}
}
startAutomaticCheckingIfNeeded()
}
private suspend fun startAutomaticCheckingIfNeeded() {
mutex.withLock {
if (checkingJob?.isActive == true) return
checkingJob = coroutineScope.launch {
while (isActive) {
try {
checkAllTracked()
} catch (ex: Exception) {
Timber.e(ex)
}
delay(CHECK_INTERVAL_MS)
}
}
}
}
private suspend fun stopAutomaticCheckingIfEmpty() {
if (trackedEntries.isEmpty()) {
mutex.withLock {
if (trackedEntries.isEmpty()) {
checkingJob?.cancel()
checkingJob = null
}
}
}
}
private suspend fun checkAllTracked() {
val keysSnapshot = trackedEntries.keys().toList()
if (keysSnapshot.isEmpty()) {
stopAutomaticCheckingIfEmpty()
return
}
val networksToFetch = mutableSetOf<SingleNetworkStatusFetcher.Params>()
for (key in keysSnapshot) {
val entry = trackedEntries[key] ?: continue
if (entry.txIds.isEmpty()) {
trackedEntries.remove(key)
continue
}
val pendingTxHashes =
yieldSupplyRepository.getPendingTxHashes(
userWalletId = key.userWalletId,
cryptoCurrency = entry.cryptoCurrency,
)
.toSet()
val hasStillPending = entry.txIds.any { it in pendingTxHashes }
if (hasStillPending) {
trackedEntries.computeIfPresent(key) { _, current ->
val newAttempts = current.attempts + 1
if (newAttempts >= MAX_ATTEMPTS) null else current.copy(attempts = newAttempts)
}
} else {
trackedEntries.remove(key)
}
networksToFetch.add(
SingleNetworkStatusFetcher.Params(
userWalletId = key.userWalletId,
network = entry.cryptoCurrency.network,
),
)
}
for (params in networksToFetch) {
singleNetworkStatusFetcher(params)
}
stopAutomaticCheckingIfEmpty()
}
private companion object {
private const val CHECK_INTERVAL_MS = 10_000L
private const val MAX_ATTEMPTS = 6
}
}

View file

@ -45,7 +45,7 @@ class YieldSupplyEnterStatusUseCaseTest {
yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token) yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token)
} returns status } returns status
coEvery { coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus) yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus.currency)
} returns listOf(pendingTxHash) } returns listOf(pendingTxHash)
val result = useCase(userWalletId, cryptoStatus) val result = useCase(userWalletId, cryptoStatus)
@ -65,7 +65,7 @@ class YieldSupplyEnterStatusUseCaseTest {
yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token) yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token)
} returns status } returns status
coEvery { coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus) yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus.currency)
} returns listOf("0xdifferent") } returns listOf("0xdifferent")
coEvery { coEvery {
yieldSupplyRepository.saveTokenProtocolPendingStatus(userWalletId, token, null) yieldSupplyRepository.saveTokenProtocolPendingStatus(userWalletId, token, null)
@ -88,7 +88,7 @@ class YieldSupplyEnterStatusUseCaseTest {
yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token) yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token)
} returns null } returns null
coEvery { coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus) yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus.currency)
} returns emptyList() } returns emptyList()
coEvery { coEvery {
yieldSupplyRepository.saveTokenProtocolPendingStatus(userWalletId, token, null) yieldSupplyRepository.saveTokenProtocolPendingStatus(userWalletId, token, null)
@ -111,7 +111,7 @@ class YieldSupplyEnterStatusUseCaseTest {
yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token) yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token)
} returns status } returns status
coEvery { coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus) yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus.currency)
} returns emptyList() } returns emptyList()
coEvery { coEvery {
yieldSupplyRepository.saveTokenProtocolPendingStatus(userWalletId, token, null) yieldSupplyRepository.saveTokenProtocolPendingStatus(userWalletId, token, null)
@ -153,7 +153,7 @@ class YieldSupplyEnterStatusUseCaseTest {
yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token) yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token)
} returns status } returns status
coEvery { coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus) yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus.currency)
} returns listOf(pendingTxHash) } returns listOf(pendingTxHash)
val result = useCase(userWalletId, cryptoStatus) val result = useCase(userWalletId, cryptoStatus)
@ -174,7 +174,7 @@ class YieldSupplyEnterStatusUseCaseTest {
yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token) yieldSupplyRepository.getTokenProtocolPendingStatus(userWalletId, token)
} returns status } returns status
coEvery { coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus) yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus.currency)
} returns listOf(matchingTxHash) } returns listOf(matchingTxHash)
val result = useCase(userWalletId, cryptoStatus) val result = useCase(userWalletId, cryptoStatus)

View file

@ -0,0 +1,308 @@
package com.tangem.domain.yield.supply.usecase
import arrow.core.Either
import com.google.common.truth.Truth.assertThat
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.yield.supply.YieldSupplyRepository
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import io.mockk.slot
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@OptIn(ExperimentalCoroutinesApi::class)
class YieldSupplyPendingTrackerTest {
private val yieldSupplyRepository: YieldSupplyRepository = mockk()
private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher = mockk()
private lateinit var testScope: TestScope
private lateinit var useCase: YieldSupplyPendingTracker
private val userWalletId = UserWalletId("00")
@BeforeEach
fun setUp() {
testScope = TestScope(StandardTestDispatcher())
useCase = YieldSupplyPendingTracker(
yieldSupplyRepository = yieldSupplyRepository,
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
coroutineScope = testScope,
)
}
@Test
fun `GIVEN new tx ids WHEN addPending THEN stores entry for tracking`() = runTest {
val token = createToken()
val txIds = listOf("0xabc123", "0xdef456")
coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId, token)
} returns txIds
coEvery {
singleNetworkStatusFetcher.invoke(any())
} returns Either.Right(Unit)
useCase.addPending(userWalletId, token, txIds)
testScope.advanceTimeBy(10_001L)
coVerify {
yieldSupplyRepository.getPendingTxHashes(userWalletId, token)
}
}
@Test
fun `GIVEN existing entry WHEN addPending THEN merges tx ids`() = runTest {
val token = createToken()
val firstTxIds = listOf("0xfirst")
val secondTxIds = listOf("0xsecond")
coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId, token)
} returns firstTxIds + secondTxIds
coEvery {
singleNetworkStatusFetcher.invoke(any())
} returns Either.Right(Unit)
useCase.addPending(userWalletId, token, firstTxIds)
useCase.addPending(userWalletId, token, secondTxIds)
testScope.advanceTimeBy(10_001L)
coVerify {
yieldSupplyRepository.getPendingTxHashes(userWalletId, token)
}
}
@Test
fun `GIVEN tx still pending WHEN addPending THEN triggers network status refresh`() = runTest {
val token = createToken()
val txIds = listOf("0xpending")
val paramsSlot = slot<SingleNetworkStatusFetcher.Params>()
coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId, token)
} returns txIds
coEvery {
singleNetworkStatusFetcher.invoke(capture(paramsSlot))
} returns Either.Right(Unit)
useCase.addPending(userWalletId, token, txIds)
testScope.advanceTimeBy(10_001L)
coVerify {
singleNetworkStatusFetcher.invoke(any())
}
assertThat(paramsSlot.captured.userWalletId).isEqualTo(userWalletId)
assertThat(paramsSlot.captured.network).isEqualTo(token.network)
}
@Test
fun `GIVEN tx no longer pending WHEN addPending THEN removes entry from tracking and refreshes network`() = runTest {
val token = createToken()
val txIds = listOf("0xconfirmed")
coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId, token)
} returns emptyList()
coEvery {
singleNetworkStatusFetcher.invoke(any())
} returns Either.Right(Unit)
useCase.addPending(userWalletId, token, txIds)
testScope.advanceTimeBy(10_001L)
coVerify(exactly = 1) {
yieldSupplyRepository.getPendingTxHashes(userWalletId, token)
}
coVerify(exactly = 1) {
singleNetworkStatusFetcher.invoke(any())
}
}
@Test
fun `GIVEN max attempts reached WHEN addPending THEN stops tracking entry`() = runTest {
val token = createToken()
val txIds = listOf("0xstuck")
coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId, token)
} returns txIds
coEvery {
singleNetworkStatusFetcher.invoke(any())
} returns Either.Right(Unit)
useCase.addPending(userWalletId, token, txIds)
testScope.advanceTimeBy(10_001L * 6)
coVerify(exactly = 6) {
yieldSupplyRepository.getPendingTxHashes(userWalletId, token)
}
testScope.advanceTimeBy(10_001L)
coVerify(exactly = 6) {
yieldSupplyRepository.getPendingTxHashes(userWalletId, token)
}
}
@Test
fun `GIVEN multiple wallets WHEN addPending THEN tracks each separately`() = runTest {
val token = createToken()
val userWalletId1 = UserWalletId("00")
val userWalletId2 = UserWalletId("01")
val txIds1 = listOf("0xtx1")
val txIds2 = listOf("0xtx2")
coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId1, token)
} returns txIds1
coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId2, token)
} returns txIds2
coEvery {
singleNetworkStatusFetcher.invoke(any())
} returns Either.Right(Unit)
useCase.addPending(userWalletId1, token, txIds1)
useCase.addPending(userWalletId2, token, txIds2)
testScope.advanceTimeBy(10_001L)
coVerify {
yieldSupplyRepository.getPendingTxHashes(userWalletId1, token)
yieldSupplyRepository.getPendingTxHashes(userWalletId2, token)
}
}
@Test
fun `GIVEN multiple wallets with same network WHEN checkAllTracked THEN fetches network status once per unique pair`() = runTest {
val token = createToken()
val userWalletId1 = UserWalletId("00")
val userWalletId2 = UserWalletId("01")
val txIds1 = listOf("0xtx1")
val txIds2 = listOf("0xtx2")
val capturedParams = mutableListOf<SingleNetworkStatusFetcher.Params>()
coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId1, token)
} returns txIds1
coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId2, token)
} returns txIds2
coEvery {
singleNetworkStatusFetcher.invoke(capture(capturedParams))
} returns Either.Right(Unit)
useCase.addPending(userWalletId1, token, txIds1)
useCase.addPending(userWalletId2, token, txIds2)
testScope.advanceTimeBy(1000)
assertThat(capturedParams).hasSize(2)
assertThat(capturedParams.map { it.userWalletId }.toSet())
.containsExactly(userWalletId1, userWalletId2)
assertThat(capturedParams.map { it.network }.toSet())
.containsExactly(token.network)
}
@Test
fun `GIVEN same wallet with different currencies WHEN addPending THEN tracks each currency separately`() = runTest {
val token1 = createToken(networkId = "ethereum", contractAddress = "0xToken1")
val token2 = createToken(networkId = "polygon", contractAddress = "0xToken2")
val txIds1 = listOf("0xtx1")
val txIds2 = listOf("0xtx2")
coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId, token1)
} returns txIds1
coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId, token2)
} returns txIds2
coEvery {
singleNetworkStatusFetcher.invoke(any())
} returns Either.Right(Unit)
useCase.addPending(userWalletId, token1, txIds1)
useCase.addPending(userWalletId, token2, txIds2)
testScope.advanceTimeBy(10_001L)
coVerify {
yieldSupplyRepository.getPendingTxHashes(userWalletId, token1)
yieldSupplyRepository.getPendingTxHashes(userWalletId, token2)
}
}
@Test
fun `GIVEN partial tx match WHEN addPending THEN still triggers refresh`() = runTest {
val token = createToken()
val trackedTxIds = listOf("0xtracked1", "0xtracked2")
val pendingTxIds = listOf("0xtracked1")
val paramsSlot = slot<SingleNetworkStatusFetcher.Params>()
coEvery {
yieldSupplyRepository.getPendingTxHashes(userWalletId, token)
} returns pendingTxIds
coEvery {
singleNetworkStatusFetcher.invoke(capture(paramsSlot))
} returns Either.Right(Unit)
useCase.addPending(userWalletId, token, trackedTxIds)
testScope.advanceTimeBy(10_001L)
coVerify {
singleNetworkStatusFetcher.invoke(any())
}
assertThat(paramsSlot.captured.userWalletId).isEqualTo(userWalletId)
}
private fun createToken(
networkId: String = "ethereum",
contractAddress: String = "0xToken",
): CryptoCurrency.Token {
val derivationPath = Network.DerivationPath.None
val network = Network(
id = Network.ID(value = networkId, derivationPath = derivationPath),
backendId = networkId,
name = networkId,
currencySymbol = networkId.take(3).uppercase(),
derivationPath = derivationPath,
isTestnet = false,
standardType = Network.StandardType.Unspecified("UNSPECIFIED"),
hasFiatFeeRate = true,
canHandleTokens = true,
transactionExtrasType = Network.TransactionExtrasType.NONE,
nameResolvingType = Network.NameResolvingType.NONE,
)
return CryptoCurrency.Token(
id = CryptoCurrency.ID(
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
body = CryptoCurrency.ID.Body.NetworkId(networkId),
suffix = CryptoCurrency.ID.Suffix.RawID(contractAddress),
),
network = network,
name = "TEST_TOKEN",
symbol = "TTK",
decimals = 18,
iconUrl = null,
isCustom = false,
contractAddress = contractAddress,
)
}
}

View file

@ -31,12 +31,9 @@ import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM
import com.tangem.features.yield.supply.impl.main.model.transformers.YieldSupplyTokenStatusSuccessTransformer import com.tangem.features.yield.supply.impl.main.model.transformers.YieldSupplyTokenStatusSuccessTransformer
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.DelayedWork
import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn import com.tangem.utils.coroutines.saveIn
import com.tangem.utils.transformer.update import com.tangem.utils.transformer.update
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@ -55,7 +52,6 @@ internal class YieldSupplyModel @Inject constructor(
private val getUserWalletUseCase: GetUserWalletUseCase, private val getUserWalletUseCase: GetUserWalletUseCase,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher, private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
@DelayedWork private val coroutineScope: CoroutineScope,
private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase, private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase,
private val yieldSupplyIsAvailableUseCase: YieldSupplyIsAvailableUseCase, private val yieldSupplyIsAvailableUseCase: YieldSupplyIsAvailableUseCase,
private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase, private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase,
@ -75,7 +71,6 @@ internal class YieldSupplyModel @Inject constructor(
var userWallet: UserWallet by Delegates.notNull() var userWallet: UserWallet by Delegates.notNull()
private var latestCryptoCurrencyStatus: CryptoCurrencyStatus? = null private var latestCryptoCurrencyStatus: CryptoCurrencyStatus? = null
private val fetchCurrencyJobHolder = JobHolder()
private val loadStatusJobHolder = JobHolder() private val loadStatusJobHolder = JobHolder()
private val isFirstCryptoCurrencyStatusEmission = AtomicBoolean(true) private val isFirstCryptoCurrencyStatusEmission = AtomicBoolean(true)
@ -201,7 +196,6 @@ internal class YieldSupplyModel @Inject constructor(
is YieldSupplyPendingStatus.Exit -> YieldSupplyUM.Processing.Exit is YieldSupplyPendingStatus.Exit -> YieldSupplyUM.Processing.Exit
} }
} }
fetchCurrencyWithDelay()
} }
private suspend fun loadStatus(cryptoCurrencyStatus: CryptoCurrencyStatus) { private suspend fun loadStatus(cryptoCurrencyStatus: CryptoCurrencyStatus) {
@ -216,20 +210,6 @@ internal class YieldSupplyModel @Inject constructor(
} }
} }
private fun fetchCurrencyWithDelay() {
coroutineScope.launch(dispatchers.io) {
delay(PROCESSING_UPDATE_DELAY)
singleNetworkStatusFetcher(
params = SingleNetworkStatusFetcher.Params(
userWalletId = userWallet.walletId,
network = cryptoCurrency.network,
),
).onLeft {
fetchCurrencyWithDelay()
}
}.saveIn(fetchCurrencyJobHolder)
}
private suspend fun loadActiveState( private suspend fun loadActiveState(
cryptoCurrencyStatus: CryptoCurrencyStatus, cryptoCurrencyStatus: CryptoCurrencyStatus,
yieldSupplyStatus: YieldSupplyStatus, yieldSupplyStatus: YieldSupplyStatus,
@ -334,8 +314,4 @@ internal class YieldSupplyModel @Inject constructor(
} }
} }
} }
private companion object {
const val PROCESSING_UPDATE_DELAY = 10_000L
}
} }

View file

@ -65,6 +65,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
private val yieldSupplyGetMaxFeeUseCase: YieldSupplyGetMaxFeeUseCase, private val yieldSupplyGetMaxFeeUseCase: YieldSupplyGetMaxFeeUseCase,
private val yieldSupplyGetCurrentFeeUseCase: YieldSupplyGetCurrentFeeUseCase, private val yieldSupplyGetCurrentFeeUseCase: YieldSupplyGetCurrentFeeUseCase,
private val yieldSupplyRepository: YieldSupplyRepository, private val yieldSupplyRepository: YieldSupplyRepository,
private val yieldSupplyPendingTracker: YieldSupplyPendingTracker,
) : Model(), YieldSupplyNotificationsComponent.ModelCallback { ) : Model(), YieldSupplyNotificationsComponent.ModelCallback {
private val params: YieldSupplyStartEarningComponent.Params = paramsContainer.require() private val params: YieldSupplyStartEarningComponent.Params = paramsContainer.require()
@ -291,6 +292,11 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
} }
modelScope.launch { modelScope.launch {
yieldSupplyPendingTracker.addPending(
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
txIds = txsData,
)
params.callback.onTransactionSent() params.callback.onTransactionSent()
} }
} }

View file

@ -23,6 +23,7 @@ import com.tangem.domain.yield.supply.YieldSupplyRepository
import com.tangem.domain.yield.supply.increaseGasLimitBy import com.tangem.domain.yield.supply.increaseGasLimitBy
import com.tangem.domain.yield.supply.models.YieldSupplyPendingStatus import com.tangem.domain.yield.supply.models.YieldSupplyPendingStatus
import com.tangem.domain.yield.supply.usecase.YieldSupplyDeactivateUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyDeactivateUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyPendingTracker
import com.tangem.domain.yield.supply.usecase.YieldSupplyStopEarningUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyStopEarningUseCase
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.impl.R
@ -61,6 +62,7 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
private val yieldSupplyAlertFactory: YieldSupplyAlertFactory, private val yieldSupplyAlertFactory: YieldSupplyAlertFactory,
private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase, private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase,
private val yieldSupplyRepository: YieldSupplyRepository, private val yieldSupplyRepository: YieldSupplyRepository,
private val yieldSupplyPendingTracker: YieldSupplyPendingTracker,
) : Model(), YieldSupplyNotificationsComponent.ModelCallback { ) : Model(), YieldSupplyNotificationsComponent.ModelCallback {
private val params: YieldSupplyStopEarningComponent.Params = paramsContainer.require() private val params: YieldSupplyStopEarningComponent.Params = paramsContainer.require()
@ -203,6 +205,11 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
} }
modelScope.launch { modelScope.launch {
yieldSupplyPendingTracker.addPending(
userWalletId = userWallet.walletId,
cryptoCurrency = cryptoCurrency,
txIds = listOf(txId),
)
params.callback.onStopEarningTransactionSent() params.callback.onStopEarningTransactionSent()
} }
} }