Updated on 2026-08-14

This commit is contained in:
Tangem 2026-01-19 13:32:27 +02:00
parent 27e199afdd
commit d839a616ef
16 changed files with 172 additions and 115 deletions

View file

@ -3,7 +3,7 @@ package com.tangem.data.staking
import arrow.core.getOrElse
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.data.staking.store.StakingBalancesStore
import com.tangem.data.staking.store.StakeKitBalancesStore
import com.tangem.domain.card.common.TapWorkarounds.isWallet2
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.staking.StakingBalance
@ -30,7 +30,7 @@ import kotlinx.coroutines.withContext
internal class DefaultStakingRepository(
private val stakeKitRepository: StakeKitRepository,
private val p2pEthPoolRepository: P2PEthPoolRepository,
private val stakingBalanceStoreV2: StakingBalancesStore,
private val stakingBalanceStoreV2: StakeKitBalancesStore,
private val dispatchers: CoroutineDispatcherProvider,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val stakingFeatureToggles: StakingFeatureToggles,

View file

@ -2,9 +2,9 @@ package com.tangem.data.staking.di
import androidx.datastore.core.DataStore
import com.tangem.data.staking.store.DefaultP2PEthPoolBalancesStore
import com.tangem.data.staking.store.DefaultStakingBalancesStore
import com.tangem.data.staking.store.DefaultStakeKitBalancesStore
import com.tangem.data.staking.store.P2PEthPoolBalancesStore
import com.tangem.data.staking.store.StakingBalancesStore
import com.tangem.data.staking.store.StakeKitBalancesStore
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.datasource.local.datastore.RuntimeSharedStore
@ -28,8 +28,8 @@ internal object StakingBalanceSupplierModule {
fun provideStakingBalancesStore(
persistenceStore: DataStore<Map<String, Set<YieldBalanceWrapperDTO>>>,
dispatchers: CoroutineDispatcherProvider,
): StakingBalancesStore {
return DefaultStakingBalancesStore(
): StakeKitBalancesStore {
return DefaultStakeKitBalancesStore(
runtimeStore = RuntimeSharedStore(),
persistenceStore = persistenceStore,
dispatchers = dispatchers,

View file

@ -5,7 +5,8 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.data.staking.*
import com.tangem.data.staking.converters.error.StakeKitErrorConverter
import com.tangem.data.staking.store.StakingBalancesStore
import com.tangem.data.staking.store.P2PEthPoolBalancesStore
import com.tangem.data.staking.store.StakeKitBalancesStore
import com.tangem.data.staking.toggles.DefaultStakingFeatureToggles
import com.tangem.data.staking.utils.DefaultStakingCleaner
import com.tangem.datasource.api.ethpool.P2PEthPoolApi
@ -56,7 +57,7 @@ internal object StakingDataModule {
fun provideStakingRepository(
stakeKitRepository: StakeKitRepository,
p2pEthPoolRepository: P2PEthPoolRepository,
stakingBalancesStore: StakingBalancesStore,
stakeKitBalancesStore: StakeKitBalancesStore,
dispatchers: CoroutineDispatcherProvider,
getUserWalletUseCase: GetUserWalletUseCase,
stakingFeatureToggles: StakingFeatureToggles,
@ -65,7 +66,7 @@ internal object StakingDataModule {
return DefaultStakingRepository(
stakeKitRepository = stakeKitRepository,
p2pEthPoolRepository = p2pEthPoolRepository,
stakingBalanceStoreV2 = stakingBalancesStore,
stakingBalanceStoreV2 = stakeKitBalancesStore,
dispatchers = dispatchers,
getUserWalletUseCase = getUserWalletUseCase,
walletManagersFacade = walletManagersFacade,
@ -138,12 +139,14 @@ internal object StakingDataModule {
@Singleton
fun provideStakingCleaner(
stakingIdFactory: StakingIdFactory,
stakingBalancesStore: StakingBalancesStore,
stakeKitBalancesStore: StakeKitBalancesStore,
p2pEthPoolBalancesStore: P2PEthPoolBalancesStore,
dispatchers: CoroutineDispatcherProvider,
): StakingCleaner {
return DefaultStakingCleaner(
stakingIdFactory = stakingIdFactory,
stakingBalancesStore = stakingBalancesStore,
stakeKitBalancesStore = stakeKitBalancesStore,
p2pEthPoolBalancesStore = p2pEthPoolBalancesStore,
dispatchers = dispatchers,
)
}

View file

@ -6,7 +6,7 @@ import arrow.core.right
import arrow.core.toOption
import com.tangem.data.common.api.safeApiCall
import com.tangem.data.staking.store.P2PEthPoolBalancesStore
import com.tangem.data.staking.store.StakingBalancesStore
import com.tangem.data.staking.store.StakeKitBalancesStore
import com.tangem.data.staking.utils.YieldBalanceRequestBodyFactory
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.ethpool.P2PEthPoolApi
@ -43,7 +43,7 @@ import javax.inject.Inject
*
* @property userWalletsStore user wallets store
* @property stakingYieldsStore staking yields store
* @property stakingBalancesStore staking balances store (StakeKit)
* @property stakeKitBalancesStore staking balances store (StakeKit)
* @property p2PEthPoolBalancesStore P2PEthPool balances store
* @property stakeKitApi stake kit API
* @property p2pEthPoolApi P2PEthPool API
@ -56,7 +56,7 @@ import javax.inject.Inject
internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
private val userWalletsStore: UserWalletsStore,
private val stakingYieldsStore: StakingYieldsStore,
private val stakingBalancesStore: StakingBalancesStore,
private val stakeKitBalancesStore: StakeKitBalancesStore,
private val p2PEthPoolBalancesStore: P2PEthPoolBalancesStore,
private val stakeKitApi: StakeKitApi,
private val p2pEthPoolApi: P2PEthPoolApi,
@ -106,7 +106,7 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
Timber.e(throwable, "Unable to fetch staking balances $params")
if (stakeKitIds.isNotEmpty()) {
stakingBalancesStore.storeError(
stakeKitBalancesStore.storeError(
userWalletId = params.userWalletId,
stakingIds = stakeKitIds.toSet(),
)
@ -121,7 +121,7 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
}
private suspend fun fetchStakeKitBalances(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
stakingBalancesStore.refresh(userWalletId = userWalletId, stakingIds = stakingIds)
stakeKitBalancesStore.refresh(userWalletId = userWalletId, stakingIds = stakingIds)
val availableStakingIds = getAvailableStakingIds(
userWalletId = userWalletId,
@ -270,7 +270,7 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
)
if (unavailableStakingIds.isNotEmpty()) {
stakingBalancesStore.storeError(userWalletId = userWalletId, stakingIds = unavailableStakingIds.toSet())
stakeKitBalancesStore.storeError(userWalletId = userWalletId, stakingIds = unavailableStakingIds.toSet())
}
return availableStakingIds.toSet().ifEmpty {
@ -322,7 +322,7 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
Timber.i(
"Successfully fetched staking balances for $userWalletId:\n${yieldBalances.joinToString("\n")}",
)
stakingBalancesStore.storeActual(userWalletId = userWalletId, values = yieldBalances)
stakeKitBalancesStore.storeActual(userWalletId = userWalletId, values = yieldBalances)
if (!allResponsesReceived(requests, yieldBalances)) {
val values = stakingIds.filter { stakingId ->
@ -332,13 +332,13 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
}
}
stakingBalancesStore.storeError(userWalletId = userWalletId, stakingIds = values.toSet())
stakeKitBalancesStore.storeError(userWalletId = userWalletId, stakingIds = values.toSet())
}
},
onError = { throwable ->
Timber.e(throwable, "Unable to fetch staking balances $userWalletId")
stakingBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds)
stakeKitBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds)
throw throwable
},

View file

@ -3,7 +3,7 @@ package com.tangem.data.staking.multi
import arrow.core.Option
import arrow.core.some
import com.tangem.data.staking.store.P2PEthPoolBalancesStore
import com.tangem.data.staking.store.StakingBalancesStore
import com.tangem.data.staking.store.StakeKitBalancesStore
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.staking.multi.MultiStakingBalanceProducer
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -22,7 +22,7 @@ import kotlinx.coroutines.flow.onEmpty
* Combines staking balances from both StakeKit and P2PEthPool providers.
*
* @property params params
* @property stakingBalancesStore StakeKit staking balances store
* @property stakeKitBalancesStore StakeKit staking balances store
* @property p2PEthPoolBalancesStore P2PEthPool balances store
* @property dispatchers dispatchers
*
@ -30,7 +30,7 @@ import kotlinx.coroutines.flow.onEmpty
*/
internal class DefaultMultiStakingBalanceProducer @AssistedInject constructor(
@Assisted val params: MultiStakingBalanceProducer.Params,
private val stakingBalancesStore: StakingBalancesStore,
private val stakeKitBalancesStore: StakeKitBalancesStore,
private val p2PEthPoolBalancesStore: P2PEthPoolBalancesStore,
private val dispatchers: CoroutineDispatcherProvider,
) : MultiStakingBalanceProducer {
@ -38,7 +38,7 @@ internal class DefaultMultiStakingBalanceProducer @AssistedInject constructor(
override val fallback: Option<Set<StakingBalance>> = emptySet<StakingBalance>().some()
override fun produce(): Flow<Set<StakingBalance>> {
val stakeKitFlow = stakingBalancesStore.get(userWalletId = params.userWalletId)
val stakeKitFlow = stakeKitBalancesStore.get(userWalletId = params.userWalletId)
val p2pEthPoolFlow = p2PEthPoolBalancesStore.get(userWalletId = params.userWalletId)
return combine(stakeKitFlow, p2pEthPoolFlow) { stakeKitBalances, p2pEthPoolBalances ->

View file

@ -8,7 +8,6 @@ import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.models.staking.StakingID
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.staking.model.StakingIntegrationID
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.CoroutineScope
@ -151,13 +150,13 @@ internal class DefaultP2PEthPoolBalancesStore(
}
private suspend fun clearInPersistence(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
val integrationIds = stakingIds.map { it.integrationId }.toSet()
val addressesToClear = stakingIds.map { it.address }.toSet()
persistenceStore.updateData { current ->
current.toMutableMap().apply {
this[userWalletId.stringValue] = this[userWalletId.stringValue].orEmpty()
.filterNot { response ->
StakingIntegrationID.P2PEthPool.value in integrationIds
response.delegatorAddress in addressesToClear
}
.toSet()
}

View file

@ -22,7 +22,7 @@ internal typealias WalletIdWithWrappers = Map<String, Set<YieldBalanceWrapperDTO
internal typealias WalletIdWithStakingBalances = Map<UserWalletId, Set<StakingBalance>>
/**
* Default implementation of [StakingBalancesStore]
* Default implementation of [StakeKitBalancesStore]
*
* @property runtimeStore runtime store
* @property persistenceStore persistence store
@ -30,11 +30,11 @@ internal typealias WalletIdWithStakingBalances = Map<UserWalletId, Set<StakingBa
*
[REDACTED_AUTHOR]
*/
internal class DefaultStakingBalancesStore(
internal class DefaultStakeKitBalancesStore(
private val runtimeStore: RuntimeSharedStore<WalletIdWithStakingBalances>,
private val persistenceStore: DataStore<WalletIdWithWrappers>,
dispatchers: CoroutineDispatcherProvider,
) : StakingBalancesStore {
) : StakeKitBalancesStore {
private val scope = CoroutineScope(context = SupervisorJob() + dispatchers.io)
@ -97,6 +97,23 @@ internal class DefaultStakingBalancesStore(
}
override suspend fun clear(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
coroutineScope {
launch { clearInRuntime(userWalletId = userWalletId, stakingIds = stakingIds) }
launch { clearInPersistence(userWalletId = userWalletId, stakingIds = stakingIds) }
}
}
private suspend fun clearInRuntime(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
runtimeStore.update(default = emptyMap()) { stored ->
stored.toMutableMap().apply {
this[userWalletId] = this[userWalletId].orEmpty()
.filterNot { it.stakingId in stakingIds }
.toSet()
}
}
}
private suspend fun clearInPersistence(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
persistenceStore.updateData { current ->
current.toMutableMap().apply {
this[userWalletId.stringValue] = this[userWalletId.stringValue].orEmpty()

View file

@ -8,7 +8,7 @@ import com.tangem.domain.models.wallet.UserWalletId
*
* Extends [BaseStakingBalancesStore] with StakeKit-specific storage operations.
*/
interface StakingBalancesStore : BaseStakingBalancesStore {
interface StakeKitBalancesStore : BaseStakingBalancesStore {
/** Store actual StakeKit yield balances */
suspend fun storeActual(userWalletId: UserWalletId, values: Set<YieldBalanceWrapperDTO>)

View file

@ -1,7 +1,8 @@
package com.tangem.data.staking.utils
import com.tangem.data.staking.store.P2PEthPoolBalancesStore
import com.tangem.data.staking.store.StakeKitBalancesStore
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.data.staking.store.StakingBalancesStore
import com.tangem.domain.models.staking.StakingID
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.staking.StakingIdFactory
@ -16,14 +17,16 @@ import timber.log.Timber
/**
* Default implementation of [StakingCleaner].
*
* @property stakingBalancesStore Store to manage staking balances.
* @property stakeKitBalancesStore Store to manage StakeKit staking balances.
* @property p2pEthPoolBalancesStore Store to manage P2PEthPool staking balances.
* @property dispatchers Coroutine dispatchers provider.
*
[REDACTED_AUTHOR]
*/
internal class DefaultStakingCleaner(
private val stakingIdFactory: StakingIdFactory,
private val stakingBalancesStore: StakingBalancesStore,
private val stakeKitBalancesStore: StakeKitBalancesStore,
private val p2pEthPoolBalancesStore: P2PEthPoolBalancesStore,
private val dispatchers: CoroutineDispatcherProvider,
) : StakingCleaner {
@ -53,15 +56,23 @@ internal class DefaultStakingCleaner(
withContext(dispatchers.default) {
awaitAll(
async { clearStatusesStore(userWalletId = userWalletId, stakingIds = stakingIds) },
async { clearStakeKitBalancesStore(userWalletId = userWalletId, stakingIds = stakingIds) },
async { clearP2PEthPoolBalancesStore(userWalletId = userWalletId, stakingIds = stakingIds) },
)
}
}
private suspend fun clearStatusesStore(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
private suspend fun clearStakeKitBalancesStore(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
runSuspendCatching {
stakingBalancesStore.clear(userWalletId, stakingIds)
stakeKitBalancesStore.clear(userWalletId, stakingIds)
}
.onFailure { Timber.e(it, "Failed to clear yield balance statuses for wallet: $userWalletId") }
.onFailure { Timber.e(it, "Failed to clear StakeKit balance statuses for wallet: $userWalletId") }
}
private suspend fun clearP2PEthPoolBalancesStore(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
runSuspendCatching {
p2pEthPoolBalancesStore.clear(userWalletId, stakingIds)
}
.onFailure { Timber.e(it, "Failed to clear P2PEthPool balance statuses for wallet: $userWalletId") }
}
}

View file

@ -5,7 +5,7 @@ import com.tangem.common.test.data.staking.MockYieldBalanceWrapperDTOFactory
import com.tangem.common.test.data.staking.MockYieldDTOFactory
import com.tangem.common.test.domain.wallet.MockUserWalletFactory
import com.tangem.data.staking.store.P2PEthPoolBalancesStore
import com.tangem.data.staking.store.StakingBalancesStore
import com.tangem.data.staking.store.StakeKitBalancesStore
import com.tangem.data.staking.utils.YieldBalanceRequestBodyFactory
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.common.response.ApiResponseError
@ -35,7 +35,7 @@ internal class DefaultMultiStakingBalanceFetcherTest {
private val userWalletsStore: UserWalletsStore = mockk()
private val stakingYieldsStore: StakingYieldsStore = mockk()
private val stakingBalancesStore: StakingBalancesStore = mockk(relaxUnitFun = true)
private val stakeKitBalancesStore: StakeKitBalancesStore = mockk(relaxUnitFun = true)
private val p2PEthPoolBalancesStore: P2PEthPoolBalancesStore = mockk(relaxUnitFun = true)
private val stakeKitApi: StakeKitApi = mockk()
private val p2pEthPoolApi: P2PEthPoolApi = mockk()
@ -44,7 +44,7 @@ internal class DefaultMultiStakingBalanceFetcherTest {
private val fetcher = DefaultMultiStakingBalanceFetcher(
userWalletsStore = userWalletsStore,
stakingYieldsStore = stakingYieldsStore,
stakingBalancesStore = stakingBalancesStore,
stakeKitBalancesStore = stakeKitBalancesStore,
p2PEthPoolBalancesStore = p2PEthPoolBalancesStore,
stakeKitApi = stakeKitApi,
p2pEthPoolApi = p2pEthPoolApi,
@ -54,7 +54,7 @@ internal class DefaultMultiStakingBalanceFetcherTest {
@BeforeEach
fun resetMocks() {
clearMocks(userWalletsStore, stakingYieldsStore, stakingBalancesStore, stakeKitApi)
clearMocks(userWalletsStore, stakingYieldsStore, stakeKitBalancesStore, stakeKitApi)
}
@Test
@ -81,13 +81,13 @@ internal class DefaultMultiStakingBalanceFetcherTest {
// Assert
coVerifyOrder {
userWalletsStore.getSyncOrNull(params.userWalletId)
stakingBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakeKitBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingYieldsStore.getSyncWithTimeout()
stakeKitApi.getMultipleYieldBalances(requests)
stakingBalancesStore.storeActual(userWalletId = userWalletId, values = result)
stakeKitBalancesStore.storeActual(userWalletId = userWalletId, values = result)
}
coVerify(inverse = true) { stakingBalancesStore.storeError(any(), any()) }
coVerify(inverse = true) { stakeKitBalancesStore.storeError(any(), any()) }
assertEitherRight(actual)
}
@ -113,11 +113,11 @@ internal class DefaultMultiStakingBalanceFetcherTest {
// Assert
coVerifyOrder {
userWalletsStore.getSyncOrNull(params.userWalletId)
stakingBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakeKitBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingYieldsStore.getSyncWithTimeout()
stakingBalancesStore.storeError(userWalletId = userWalletId, stakingIds = setOf(solanaId))
stakeKitBalancesStore.storeError(userWalletId = userWalletId, stakingIds = setOf(solanaId))
stakeKitApi.getMultipleYieldBalances(requests)
stakingBalancesStore.storeActual(userWalletId = userWalletId, values = result)
stakeKitBalancesStore.storeActual(userWalletId = userWalletId, values = result)
}
assertEitherRight(actual)
@ -138,11 +138,11 @@ internal class DefaultMultiStakingBalanceFetcherTest {
coVerifyOrder { userWalletsStore.getSyncOrNull(params.userWalletId) }
coVerify(inverse = true) {
stakingBalancesStore.refresh(userWalletId = any(), stakingIds = any())
stakeKitBalancesStore.refresh(userWalletId = any(), stakingIds = any())
stakingYieldsStore.getSyncWithTimeout()
stakeKitApi.getSingleYieldBalance(integrationId = any(), body = any())
stakingBalancesStore.storeActual(userWalletId = any(), values = any())
stakingBalancesStore.storeError(userWalletId = any(), stakingIds = any())
stakeKitBalancesStore.storeActual(userWalletId = any(), values = any())
stakeKitBalancesStore.storeError(userWalletId = any(), stakingIds = any())
}
val expected = IllegalStateException("Wallet ${params.userWalletId} is not supported: ${userWallet.toOption()}")
@ -164,11 +164,11 @@ internal class DefaultMultiStakingBalanceFetcherTest {
coVerifyOrder { userWalletsStore.getSyncOrNull(params.userWalletId) }
coVerify(inverse = true) {
stakingBalancesStore.refresh(userWalletId = any(), stakingIds = any())
stakeKitBalancesStore.refresh(userWalletId = any(), stakingIds = any())
stakingYieldsStore.getSyncWithTimeout()
stakeKitApi.getSingleYieldBalance(integrationId = any(), body = any())
stakingBalancesStore.storeActual(userWalletId = any(), values = any())
stakingBalancesStore.storeError(userWalletId = any(), stakingIds = any())
stakeKitBalancesStore.storeActual(userWalletId = any(), values = any())
stakeKitBalancesStore.storeError(userWalletId = any(), stakingIds = any())
}
val expected = IllegalStateException("Wallet ${params.userWalletId} is not supported: ${null.toOption()}")
@ -190,14 +190,14 @@ internal class DefaultMultiStakingBalanceFetcherTest {
// Assert
coVerifyOrder {
userWalletsStore.getSyncOrNull(params.userWalletId)
stakingBalancesStore.refresh(params.userWalletId, tonAndSolanaIds)
stakeKitBalancesStore.refresh(params.userWalletId, tonAndSolanaIds)
stakingYieldsStore.getSyncWithTimeout()
stakingBalancesStore.storeError(userWalletId, tonAndSolanaIds)
stakeKitBalancesStore.storeError(userWalletId, tonAndSolanaIds)
}
coVerify(inverse = true) {
stakeKitApi.getMultipleYieldBalances(any())
stakingBalancesStore.storeActual(userWalletId = any(), values = any())
stakeKitBalancesStore.storeActual(userWalletId = any(), values = any())
}
val expected = IllegalStateException("No enabled yields for ${params.userWalletId}")
@ -219,14 +219,14 @@ internal class DefaultMultiStakingBalanceFetcherTest {
// Assert
coVerifyOrder {
userWalletsStore.getSyncOrNull(params.userWalletId)
stakingBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakeKitBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingYieldsStore.getSyncWithTimeout()
stakingBalancesStore.storeError(userWalletId, tonAndSolanaIds)
stakeKitBalancesStore.storeError(userWalletId, tonAndSolanaIds)
}
coVerify(inverse = true) {
stakeKitApi.getMultipleYieldBalances(any())
stakingBalancesStore.storeActual(userWalletId = any(), values = any())
stakeKitBalancesStore.storeActual(userWalletId = any(), values = any())
}
val expected = IllegalStateException("No enabled yields for ${params.userWalletId}")
@ -253,14 +253,14 @@ internal class DefaultMultiStakingBalanceFetcherTest {
// Assert
coVerifyOrder {
userWalletsStore.getSyncOrNull(params.userWalletId)
stakingBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakeKitBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingYieldsStore.getSyncWithTimeout()
stakingBalancesStore.storeError(userWalletId, tonAndSolanaIds)
stakeKitBalancesStore.storeError(userWalletId, tonAndSolanaIds)
}
coVerify(inverse = true) {
stakeKitApi.getMultipleYieldBalances(any())
stakingBalancesStore.storeActual(userWalletId = any(), values = any())
stakeKitBalancesStore.storeActual(userWalletId = any(), values = any())
}
val expected = IllegalStateException("No enabled yields for ${params.userWalletId}")
@ -284,14 +284,14 @@ internal class DefaultMultiStakingBalanceFetcherTest {
// Assert
coVerifyOrder {
userWalletsStore.getSyncOrNull(params.userWalletId)
stakingBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakeKitBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingYieldsStore.getSyncWithTimeout()
stakingBalancesStore.storeError(userWalletId, tonAndSolanaIds)
stakeKitBalancesStore.storeError(userWalletId, tonAndSolanaIds)
}
coVerify(inverse = true) {
stakeKitApi.getMultipleYieldBalances(any())
stakingBalancesStore.storeActual(userWalletId = any(), values = any())
stakeKitBalancesStore.storeActual(userWalletId = any(), values = any())
}
val expected = IllegalStateException(
@ -329,13 +329,13 @@ internal class DefaultMultiStakingBalanceFetcherTest {
// Assert
coVerifyOrder {
userWalletsStore.getSyncOrNull(params.userWalletId)
stakingBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakeKitBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingYieldsStore.getSyncWithTimeout()
stakeKitApi.getMultipleYieldBalances(requests)
stakingBalancesStore.storeError(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
stakeKitBalancesStore.storeError(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
}
coVerify(inverse = true) { stakingBalancesStore.storeActual(userWalletId = any(), values = any()) }
coVerify(inverse = true) { stakeKitBalancesStore.storeActual(userWalletId = any(), values = any()) }
val expected = ApiResponseError.NetworkException()

View file

@ -4,7 +4,7 @@ import com.google.common.truth.Truth
import com.tangem.common.test.data.staking.MockYieldBalanceWrapperDTOFactory
import com.tangem.common.test.data.staking.MockP2PEthPoolAccountResponseFactory
import com.tangem.data.staking.store.P2PEthPoolBalancesStore
import com.tangem.data.staking.store.StakingBalancesStore
import com.tangem.data.staking.store.StakeKitBalancesStore
import com.tangem.data.staking.toDomain
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.staking.*
@ -27,13 +27,13 @@ internal class DefaultMultiStakingBalanceProducerTest {
private val params = MultiStakingBalanceProducer.Params(userWalletId = UserWalletId("011"))
private val stakingBalancesStore = mockk<StakingBalancesStore>()
private val stakeKitBalancesStore = mockk<StakeKitBalancesStore>()
private val p2PEthPoolBalancesStore = mockk<P2PEthPoolBalancesStore>()
private val dispatchers = TestingCoroutineDispatcherProvider()
private val producer = DefaultMultiStakingBalanceProducer(
params = params,
stakingBalancesStore = stakingBalancesStore,
stakeKitBalancesStore = stakeKitBalancesStore,
p2PEthPoolBalancesStore = p2PEthPoolBalancesStore,
dispatchers = dispatchers,
)
@ -47,13 +47,13 @@ internal class DefaultMultiStakingBalanceProducerTest {
val networksStatusesFlow = flowOf(balances)
every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
every { stakeKitBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns flowOf(emptySet())
val actual = producer.produce()
// check after producer.produce()
verify { stakingBalancesStore.get(params.userWalletId) }
verify { stakeKitBalancesStore.get(params.userWalletId) }
verify { p2PEthPoolBalancesStore.get(params.userWalletId) }
val values = getEmittedValues(flow = actual)
@ -66,13 +66,13 @@ internal class DefaultMultiStakingBalanceProducerTest {
fun `test that flow is updated if balances are updated`() = runTest {
val networksStatusesFlow = MutableSharedFlow<Set<StakingBalance>>(replay = 2)
every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
every { stakeKitBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns flowOf(emptySet())
val actual = producer.produce()
// check after producer.produce()
verify { stakingBalancesStore.get(params.userWalletId) }
verify { stakeKitBalancesStore.get(params.userWalletId) }
verify { p2PEthPoolBalancesStore.get(params.userWalletId) }
// first emit
@ -107,13 +107,13 @@ internal class DefaultMultiStakingBalanceProducerTest {
fun `test that flow is filtered the same balance`() = runTest {
val networksStatusesFlow = MutableSharedFlow<Set<StakingBalance>>(replay = 2)
every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
every { stakeKitBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns flowOf(emptySet())
val actual = producer.produce()
// check after producer.produce()
verify { stakingBalancesStore.get(params.userWalletId) }
verify { stakeKitBalancesStore.get(params.userWalletId) }
verify { p2PEthPoolBalancesStore.get(params.userWalletId) }
// first emit
@ -156,13 +156,13 @@ internal class DefaultMultiStakingBalanceProducerTest {
}
.buffer(capacity = 5)
every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
every { stakeKitBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns flowOf(emptySet())
val actual = producer.produceWithFallback()
// check after producer.produce()
verify { stakingBalancesStore.get(params.userWalletId) }
verify { stakeKitBalancesStore.get(params.userWalletId) }
verify { p2PEthPoolBalancesStore.get(params.userWalletId) }
val values1 = getEmittedValues(flow = actual)
@ -180,13 +180,13 @@ internal class DefaultMultiStakingBalanceProducerTest {
@Test
fun `test that flow is empty`() = runTest {
every { stakingBalancesStore.get(params.userWalletId) } returns emptyFlow()
every { stakeKitBalancesStore.get(params.userWalletId) } returns emptyFlow()
every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns emptyFlow()
val actual = producer.produce()
// check after producer.produce()
verify { stakingBalancesStore.get(params.userWalletId) }
verify { stakeKitBalancesStore.get(params.userWalletId) }
verify { p2PEthPoolBalancesStore.get(params.userWalletId) }
val values = getEmittedValues(flow = actual)
@ -200,13 +200,13 @@ internal class DefaultMultiStakingBalanceProducerTest {
val stakeKitBalances = createStakeKitBalances()
val p2pEthPoolBalances = createP2PEthPoolBalances()
every { stakingBalancesStore.get(params.userWalletId) } returns flowOf(stakeKitBalances)
every { stakeKitBalancesStore.get(params.userWalletId) } returns flowOf(stakeKitBalances)
every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns flowOf(p2pEthPoolBalances)
val actual = producer.produce()
// check after producer.produce()
verify { stakingBalancesStore.get(params.userWalletId) }
verify { stakeKitBalancesStore.get(params.userWalletId) }
verify { p2PEthPoolBalancesStore.get(params.userWalletId) }
val values = getEmittedValues(flow = actual)
@ -220,13 +220,13 @@ internal class DefaultMultiStakingBalanceProducerTest {
val stakeKitBalances = createStakeKitBalancesWithTonOnly()
val p2pEthPoolFlow = MutableSharedFlow<Set<StakingBalance>>(replay = 2)
every { stakingBalancesStore.get(params.userWalletId) } returns flowOf(stakeKitBalances)
every { stakeKitBalancesStore.get(params.userWalletId) } returns flowOf(stakeKitBalances)
every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns p2pEthPoolFlow
val actual = producer.produce()
// check after producer.produce()
verify { stakingBalancesStore.get(params.userWalletId) }
verify { stakeKitBalancesStore.get(params.userWalletId) }
verify { p2PEthPoolBalancesStore.get(params.userWalletId) }
// first emit - empty P2PEthPool

View file

@ -20,7 +20,7 @@ internal class StakingBalancesStoreGetMethodTest {
private val runtimeStore = RuntimeSharedStore<WalletIdWithStakingBalances>()
private val persistenceStore = MockStateDataStore<WalletIdWithWrappers>(default = emptyMap())
private val store = DefaultStakingBalancesStore(
private val store = DefaultStakeKitBalancesStore(
runtimeStore = runtimeStore,
persistenceStore = persistenceStore,
dispatchers = TestingCoroutineDispatcherProvider(),

View file

@ -27,7 +27,7 @@ internal class StakingBalancesStoreInitializationTest {
every { persistenceStore.data } returns emptyFlow()
DefaultStakingBalancesStore(
DefaultStakeKitBalancesStore(
runtimeStore = runtimeStore,
persistenceStore = persistenceStore,
dispatchers = TestingCoroutineDispatcherProvider(),
@ -41,7 +41,7 @@ internal class StakingBalancesStoreInitializationTest {
val runtimeStore = RuntimeSharedStore<WalletIdWithStakingBalances>()
val persistenceStore = MockStateDataStore<WalletIdWithWrappers>(default = emptyMap())
DefaultStakingBalancesStore(
DefaultStakeKitBalancesStore(
runtimeStore = runtimeStore,
persistenceStore = persistenceStore,
dispatchers = TestingCoroutineDispatcherProvider(),
@ -63,7 +63,7 @@ internal class StakingBalancesStoreInitializationTest {
}
}
DefaultStakingBalancesStore(
DefaultStakeKitBalancesStore(
runtimeStore = runtimeStore,
persistenceStore = persistenceStore,
dispatchers = TestingCoroutineDispatcherProvider(),

View file

@ -23,7 +23,7 @@ internal class StakingBalancesStoreUpdateMethodsTest {
private val runtimeStore = RuntimeSharedStore<WalletIdWithStakingBalances>()
private val persistenceStore = MockStateDataStore<WalletIdWithWrappers>(default = emptyMap())
private val store = DefaultStakingBalancesStore(
private val store = DefaultStakeKitBalancesStore(
runtimeStore = runtimeStore,
persistenceStore = persistenceStore,
dispatchers = TestingCoroutineDispatcherProvider(),

View file

@ -2,7 +2,8 @@ package com.tangem.data.staking.utils
import arrow.core.right
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
import com.tangem.data.staking.store.StakingBalancesStore
import com.tangem.data.staking.store.P2PEthPoolBalancesStore
import com.tangem.data.staking.store.StakeKitBalancesStore
import com.tangem.domain.models.staking.StakingID
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.staking.StakingIdFactory
@ -10,7 +11,7 @@ import com.tangem.domain.staking.model.StakingIntegrationID
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.clearMocks
import io.mockk.coEvery
import io.mockk.coVerifyOrder
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
@ -22,10 +23,12 @@ import org.junit.jupiter.api.TestInstance
class DefaultStakingCleanerTest {
private val stakingIdFactory = mockk<StakingIdFactory>(relaxed = true)
private val stakingBalancesStore = mockk<StakingBalancesStore>(relaxed = true)
private val stakeKitBalancesStore = mockk<StakeKitBalancesStore>(relaxed = true)
private val p2pEthPoolBalancesStore = mockk<P2PEthPoolBalancesStore>(relaxed = true)
private val cleaner = DefaultStakingCleaner(
stakingIdFactory = stakingIdFactory,
stakingBalancesStore = stakingBalancesStore,
stakeKitBalancesStore = stakeKitBalancesStore,
p2pEthPoolBalancesStore = p2pEthPoolBalancesStore,
dispatchers = TestingCoroutineDispatcherProvider(),
)
@ -33,7 +36,7 @@ class DefaultStakingCleanerTest {
@BeforeEach
fun setUp() {
clearMocks(stakingIdFactory, stakingBalancesStore)
clearMocks(stakingIdFactory, stakeKitBalancesStore, p2pEthPoolBalancesStore)
}
@Nested
@ -50,8 +53,9 @@ class DefaultStakingCleanerTest {
cleaner(userWalletId = userWalletId, stakingIds = stakingIds)
// Assert
coVerifyOrder {
stakingBalancesStore.clear(userWalletId = userWalletId, stakingIds = stakingIds)
coVerify {
stakeKitBalancesStore.clear(userWalletId = userWalletId, stakingIds = stakingIds)
p2pEthPoolBalancesStore.clear(userWalletId = userWalletId, stakingIds = stakingIds)
}
}
@ -61,22 +65,39 @@ class DefaultStakingCleanerTest {
cleaner(userWalletId = userWalletId, stakingIds = emptySet())
// Assert
coVerifyOrder(inverse = true) {
stakingBalancesStore.clear(userWalletId = any(), stakingIds = any())
coVerify(inverse = true) {
stakeKitBalancesStore.clear(userWalletId = any(), stakingIds = any())
p2pEthPoolBalancesStore.clear(userWalletId = any(), stakingIds = any())
}
}
@Test
fun `should catch exception from stakingBalancesStore and not throw`() = runTest {
// Arrange
coEvery { stakingBalancesStore.clear(userWalletId, stakingIds) } throws Exception()
coEvery { stakeKitBalancesStore.clear(userWalletId, stakingIds) } throws Exception()
// Act
cleaner(userWalletId = userWalletId, stakingIds = stakingIds)
// Assert
coVerifyOrder {
stakingBalancesStore.clear(userWalletId = userWalletId, stakingIds = stakingIds)
coVerify {
stakeKitBalancesStore.clear(userWalletId = userWalletId, stakingIds = stakingIds)
p2pEthPoolBalancesStore.clear(userWalletId = userWalletId, stakingIds = stakingIds)
}
}
@Test
fun `should catch exception from p2pEthPoolBalancesStore and not throw`() = runTest {
// Arrange
coEvery { p2pEthPoolBalancesStore.clear(userWalletId, stakingIds) } throws Exception()
// Act
cleaner(userWalletId = userWalletId, stakingIds = stakingIds)
// Assert
coVerify {
stakeKitBalancesStore.clear(userWalletId = userWalletId, stakingIds = stakingIds)
p2pEthPoolBalancesStore.clear(userWalletId = userWalletId, stakingIds = stakingIds)
}
}
}
@ -102,9 +123,10 @@ class DefaultStakingCleanerTest {
cleaner(userWalletId = userWalletId, currency = coin)
// Assert
coVerifyOrder {
coVerify {
stakingIdFactory.create(userWalletId = userWalletId, cryptoCurrency = coin)
stakingBalancesStore.clear(userWalletId = userWalletId, stakingIds = setOf(stakingId))
stakeKitBalancesStore.clear(userWalletId = userWalletId, stakingIds = setOf(stakingId))
p2pEthPoolBalancesStore.clear(userWalletId = userWalletId, stakingIds = setOf(stakingId))
}
}
@ -114,9 +136,10 @@ class DefaultStakingCleanerTest {
cleaner(userWalletId = userWalletId, currencies = emptyList())
// Assert
coVerifyOrder(inverse = true) {
coVerify(inverse = true) {
stakingIdFactory.create(userWalletId = any(), cryptoCurrency = any())
stakingBalancesStore.clear(userWalletId = any(), stakingIds = any())
stakeKitBalancesStore.clear(userWalletId = any(), stakingIds = any())
p2pEthPoolBalancesStore.clear(userWalletId = any(), stakingIds = any())
}
}
@ -130,16 +153,17 @@ class DefaultStakingCleanerTest {
coEvery { stakingIdFactory.create(userWalletId, coin) } returns stakingId.right()
coEvery {
stakingBalancesStore.clear(userWalletId = userWalletId, stakingIds = setOf(stakingId))
stakeKitBalancesStore.clear(userWalletId = userWalletId, stakingIds = setOf(stakingId))
} throws Exception()
// Act
cleaner(userWalletId = userWalletId, currency = coin)
// Assert
coVerifyOrder {
coVerify {
stakingIdFactory.create(userWalletId = userWalletId, cryptoCurrency = coin)
stakingBalancesStore.clear(userWalletId = userWalletId, stakingIds = setOf(stakingId))
stakeKitBalancesStore.clear(userWalletId = userWalletId, stakingIds = setOf(stakingId))
p2pEthPoolBalancesStore.clear(userWalletId = userWalletId, stakingIds = setOf(stakingId))
}
}
}

View file

@ -267,11 +267,12 @@ internal class StakingModel @Inject constructor(
private val transactionsInProgress: CopyOnWriteArrayList<StakingTransaction> = CopyOnWriteArrayList()
private var actionsJobHolder: JobHolder = JobHolder()
private var approvalJobHolder: JobHolder = JobHolder()
private var feeJobHolder: JobHolder = JobHolder()
private var sendTransactionJobHolder = JobHolder()
private var stepChangesJobHolder = JobHolder()
private val actionsJobHolder: JobHolder = JobHolder()
private val approvalJobHolder: JobHolder = JobHolder()
private val feeJobHolder: JobHolder = JobHolder()
private val sendTransactionJobHolder = JobHolder()
private val stepChangesJobHolder = JobHolder()
private val balanceHidingJobHolder = JobHolder()
init {
subscribeOnSelectedAppCurrency()
@ -286,6 +287,7 @@ internal class StakingModel @Inject constructor(
feeJobHolder.cancel()
sendTransactionJobHolder.cancel()
stepChangesJobHolder.cancel()
balanceHidingJobHolder.cancel()
}
override fun onBackClick() {
@ -1284,6 +1286,7 @@ internal class StakingModel @Inject constructor(
}
.flowOn(dispatchers.main)
.launchIn(modelScope)
.saveIn(balanceHidingJobHolder)
}
private fun subscribeOnSelectedAppCurrency() {