Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-07 09:05:00 +02:00
parent daf2a6b636
commit 81a7b760ae
104 changed files with 1197 additions and 941 deletions

View file

@ -3,11 +3,11 @@ 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.YieldsBalancesStore
import com.tangem.data.staking.store.StakingBalancesStore
import com.tangem.domain.card.common.TapWorkarounds.isWallet2
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.models.staking.StakingID
import com.tangem.domain.models.staking.YieldBalance
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.staking.model.StakingAvailability
@ -30,7 +30,7 @@ import kotlinx.coroutines.withContext
internal class DefaultStakingRepository(
private val stakeKitRepository: StakeKitRepository,
private val p2pEthPoolRepository: P2PEthPoolRepository,
private val stakingBalanceStoreV2: YieldsBalancesStore,
private val stakingBalanceStoreV2: StakingBalancesStore,
private val dispatchers: CoroutineDispatcherProvider,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val stakingFeatureToggles: StakingFeatureToggles,
@ -106,13 +106,13 @@ internal class DefaultStakingRepository(
return withContext(dispatchers.default) {
val balances = stakingBalanceStoreV2.getAllSyncOrNull(userWalletId) ?: return@withContext false
val hasDataYieldBalance by lazy {
balances.any { yieldBalance ->
(yieldBalance as? YieldBalance.Data)?.balance?.items?.isNotEmpty() == true
val hasDataStakingBalance by lazy {
balances.any { stakingBalance ->
stakingBalance is StakingBalance.Data
}
}
balances.isNotEmpty() && hasDataYieldBalance
balances.isNotEmpty() && hasDataStakingBalance
}
}
@ -135,7 +135,7 @@ internal class DefaultStakingRepository(
address = address,
),
)
if (balance != null && balance is YieldBalance.Data && balance.balance.items.isNotEmpty()) {
if ((balance as? StakingBalance.Data.StakeKit)?.balance?.items?.isNotEmpty() == true) {
return true
} else {
stakingFeatureToggles.isCardanoStakingEnabled

View file

@ -0,0 +1,60 @@
package com.tangem.data.staking.converters.ethpool
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolExitQueueDTO
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolExitRequestDTO
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolStakeDTO
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.staking.*
import com.tangem.domain.staking.model.StakingIntegrationID
import kotlinx.datetime.Instant
/** Converts P2P ETH Pool API response to [StakingBalance.Data.P2P] */
internal object P2PStakingBalanceConverter {
fun convert(response: P2PEthPoolAccountResponse, source: StatusSource): StakingBalance.Data.P2P {
val stakingId = StakingID(
integrationId = StakingIntegrationID.P2P.EthereumPooled.value,
address = response.delegatorAddress,
)
val account = P2PStakingAccount(
delegatorAddress = response.delegatorAddress,
vaultAddress = response.vaultAddress,
stake = convertStake(response.stake),
availableToUnstake = response.availableToUnstake,
availableToWithdraw = response.availableToWithdraw,
exitQueue = convertExitQueue(response.exitQueue),
)
return StakingBalance.Data.P2P(
stakingId = stakingId,
source = source,
account = account,
)
}
private fun convertStake(dto: P2PEthPoolStakeDTO): P2PStake {
return P2PStake(
assets = dto.assets,
totalEarnedAssets = dto.totalEarnedAssets,
)
}
private fun convertExitQueue(dto: P2PEthPoolExitQueueDTO): P2PExitQueue {
return P2PExitQueue(
total = dto.total.toBigDecimal(),
requests = dto.requests.map(::convertExitRequest),
)
}
private fun convertExitRequest(dto: P2PEthPoolExitRequestDTO): P2PExitRequest {
return P2PExitRequest(
ticket = dto.ticket,
totalAssets = dto.totalAssets.toBigDecimal(),
timestamp = Instant.fromEpochSeconds(dto.timestamp),
withdrawalTimestamp = Instant.fromEpochSeconds(dto.withdrawalTimestamp),
isClaimable = dto.isClaimable,
)
}
}

View file

@ -0,0 +1,24 @@
package com.tangem.data.staking.di
import com.tangem.data.staking.multi.DefaultMultiStakingBalanceFetcher
import com.tangem.data.staking.single.DefaultSingleStakingBalanceFetcher
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
import com.tangem.domain.staking.single.SingleStakingBalanceFetcher
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface StakingBalanceFetcherModule {
@Binds
@Singleton
fun bindSingleStakingBalanceFetcher(impl: DefaultSingleStakingBalanceFetcher): SingleStakingBalanceFetcher
@Binds
@Singleton
fun bindMultiStakingBalanceFetcher(impl: DefaultMultiStakingBalanceFetcher): MultiStakingBalanceFetcher
}

View file

@ -0,0 +1,28 @@
package com.tangem.data.staking.di
import com.tangem.data.staking.multi.DefaultMultiStakingBalanceProducer
import com.tangem.data.staking.single.DefaultSingleStakingBalanceProducer
import com.tangem.domain.staking.multi.MultiStakingBalanceProducer
import com.tangem.domain.staking.single.SingleStakingBalanceProducer
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface StakingBalanceProducerFactoryModule {
@Binds
@Singleton
fun bindSingleStakingBalanceProducerFactory(
impl: DefaultSingleStakingBalanceProducer.Factory,
): SingleStakingBalanceProducer.Factory
@Binds
@Singleton
fun bindMultiStakingBalanceProducerFactory(
impl: DefaultMultiStakingBalanceProducer.Factory,
): MultiStakingBalanceProducer.Factory
}

View file

@ -2,19 +2,17 @@ package com.tangem.data.staking.di
import androidx.datastore.core.DataStore
import com.tangem.data.staking.store.DefaultP2PBalancesStore
import com.tangem.data.staking.store.DefaultYieldsBalancesStore
import com.tangem.data.staking.store.DefaultStakingBalancesStore
import com.tangem.data.staking.store.P2PBalancesStore
import com.tangem.data.staking.store.YieldsBalancesStore
import com.tangem.data.staking.store.StakingBalancesStore
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
import com.tangem.datasource.local.token.P2PEthPoolVaultsStore
import com.tangem.domain.staking.multi.MultiYieldBalanceProducer
import com.tangem.domain.staking.multi.MultiYieldBalanceSupplier
import com.tangem.domain.staking.single.SingleYieldBalanceProducer
import com.tangem.domain.staking.single.SingleYieldBalanceSupplier
import com.tangem.domain.staking.multi.MultiStakingBalanceProducer
import com.tangem.domain.staking.multi.MultiStakingBalanceSupplier
import com.tangem.domain.staking.single.SingleStakingBalanceProducer
import com.tangem.domain.staking.single.SingleStakingBalanceSupplier
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runSuspendCatching
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -23,15 +21,15 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object YieldBalanceSupplierModule {
internal object StakingBalanceSupplierModule {
@Provides
@Singleton
fun provideYieldsBalancesStore(
fun provideStakingBalancesStore(
persistenceStore: DataStore<Map<String, Set<YieldBalanceWrapperDTO>>>,
dispatchers: CoroutineDispatcherProvider,
): YieldsBalancesStore {
return DefaultYieldsBalancesStore(
): StakingBalancesStore {
return DefaultStakingBalancesStore(
runtimeStore = RuntimeSharedStore(),
persistenceStore = persistenceStore,
dispatchers = dispatchers,
@ -42,25 +40,25 @@ internal object YieldBalanceSupplierModule {
@Singleton
fun provideP2PBalancesStore(
persistenceStore: DataStore<Map<String, Set<P2PEthPoolAccountResponse>>>,
p2pVaultsStore: P2PEthPoolVaultsStore,
dispatchers: CoroutineDispatcherProvider,
): P2PBalancesStore {
return DefaultP2PBalancesStore(
runtimeStore = RuntimeSharedStore(),
persistenceStore = persistenceStore,
vaultsProvider = { runSuspendCatching { p2pVaultsStore.getSync() }.getOrNull().orEmpty() },
dispatchers = dispatchers,
)
}
@Provides
@Singleton
fun provideSingleYieldBalanceSupplier(factory: SingleYieldBalanceProducer.Factory): SingleYieldBalanceSupplier {
return object : SingleYieldBalanceSupplier(
fun provideSingleStakingBalanceSupplier(
factory: SingleStakingBalanceProducer.Factory,
): SingleStakingBalanceSupplier {
return object : SingleStakingBalanceSupplier(
factory = factory,
keyCreator = { params ->
listOf(
"single_yield_balance",
"single_staking_balance",
params.userWalletId.stringValue,
params.stakingId.integrationId,
params.stakingId.address,
@ -72,10 +70,10 @@ internal object YieldBalanceSupplierModule {
@Provides
@Singleton
fun provideMultiYieldBalanceSupplier(factory: MultiYieldBalanceProducer.Factory): MultiYieldBalanceSupplier {
return object : MultiYieldBalanceSupplier(
fun provideMultiStakingBalanceSupplier(factory: MultiStakingBalanceProducer.Factory): MultiStakingBalanceSupplier {
return object : MultiStakingBalanceSupplier(
factory = factory,
keyCreator = { "multi_yields_balances_${it.userWalletId.stringValue}" },
keyCreator = { "multi_staking_balances_${it.userWalletId.stringValue}" },
) {}
}
}

View file

@ -5,7 +5,7 @@ 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.YieldsBalancesStore
import com.tangem.data.staking.store.StakingBalancesStore
import com.tangem.data.staking.toggles.DefaultStakingFeatureToggles
import com.tangem.data.staking.utils.DefaultStakingCleaner
import com.tangem.datasource.api.ethpool.P2PEthPoolApi
@ -55,7 +55,7 @@ internal object StakingDataModule {
fun provideStakingRepository(
stakeKitRepository: StakeKitRepository,
p2pEthPoolRepository: P2PEthPoolRepository,
yieldsBalancesStore: YieldsBalancesStore,
stakingBalancesStore: StakingBalancesStore,
dispatchers: CoroutineDispatcherProvider,
getUserWalletUseCase: GetUserWalletUseCase,
stakingFeatureToggles: StakingFeatureToggles,
@ -64,7 +64,7 @@ internal object StakingDataModule {
return DefaultStakingRepository(
stakeKitRepository = stakeKitRepository,
p2pEthPoolRepository = p2pEthPoolRepository,
stakingBalanceStoreV2 = yieldsBalancesStore,
stakingBalanceStoreV2 = stakingBalancesStore,
dispatchers = dispatchers,
getUserWalletUseCase = getUserWalletUseCase,
walletManagersFacade = walletManagersFacade,
@ -134,11 +134,11 @@ internal object StakingDataModule {
@Provides
@Singleton
fun provideStakingCleaner(
yieldsBalancesStore: YieldsBalancesStore,
stakingBalancesStore: StakingBalancesStore,
dispatchers: CoroutineDispatcherProvider,
): StakingCleaner {
return DefaultStakingCleaner(
yieldsBalancesStore = yieldsBalancesStore,
stakingBalancesStore = stakingBalancesStore,
dispatchers = dispatchers,
)
}

View file

@ -1,24 +0,0 @@
package com.tangem.data.staking.di
import com.tangem.data.staking.multi.DefaultMultiYieldBalanceFetcher
import com.tangem.data.staking.single.DefaultSingleYieldBalanceFetcher
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface YieldBalanceFetcherModule {
@Binds
@Singleton
fun bindSingleYieldBalanceFetcher(impl: DefaultSingleYieldBalanceFetcher): SingleYieldBalanceFetcher
@Binds
@Singleton
fun bindMultiYieldBalanceFetcher(impl: DefaultMultiYieldBalanceFetcher): MultiYieldBalanceFetcher
}

View file

@ -1,28 +0,0 @@
package com.tangem.data.staking.di
import com.tangem.data.staking.multi.DefaultMultiYieldBalanceProducer
import com.tangem.data.staking.single.DefaultSingleYieldBalanceProducer
import com.tangem.domain.staking.multi.MultiYieldBalanceProducer
import com.tangem.domain.staking.single.SingleYieldBalanceProducer
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface YieldBalanceProducerFactoryModule {
@Binds
@Singleton
fun bindSingleYieldBalanceProducerFactory(
impl: DefaultSingleYieldBalanceProducer.Factory,
): SingleYieldBalanceProducer.Factory
@Binds
@Singleton
fun bindMultiYieldBalanceProducerFactory(
impl: DefaultMultiYieldBalanceProducer.Factory,
): MultiYieldBalanceProducer.Factory
}

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.P2PBalancesStore
import com.tangem.data.staking.store.YieldsBalancesStore
import com.tangem.data.staking.store.StakingBalancesStore
import com.tangem.data.staking.utils.YieldBalanceRequestBodyFactory
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.ethpool.P2PEthPoolApi
@ -25,7 +25,7 @@ import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.staking.model.StakingIntegrationID
import com.tangem.domain.staking.model.ethpool.P2PEthPoolNetwork
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runSuspendCatching
import kotlinx.coroutines.async
@ -36,13 +36,13 @@ import timber.log.Timber
import javax.inject.Inject
/**
* Default implementation of [MultiYieldBalanceFetcher]
* Default implementation of [MultiStakingBalanceFetcher]
*
* Supports both StakeKit and P2P staking providers.
*
* @property userWalletsStore user wallets store
* @property stakingYieldsStore staking yields store
* @property yieldsBalancesStore yields balances store (StakeKit)
* @property stakingBalancesStore staking balances store (StakeKit)
* @property p2pBalancesStore P2P balances store
* @property stakeKitApi stake kit API
* @property p2pApi P2P ETH Pool API
@ -52,19 +52,19 @@ import javax.inject.Inject
[REDACTED_AUTHOR]
*/
@Suppress("LongParameterList")
internal class DefaultMultiYieldBalanceFetcher @Inject constructor(
internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
private val userWalletsStore: UserWalletsStore,
private val stakingYieldsStore: StakingYieldsStore,
private val yieldsBalancesStore: YieldsBalancesStore,
private val stakingBalancesStore: StakingBalancesStore,
private val p2pBalancesStore: P2PBalancesStore,
private val stakeKitApi: StakeKitApi,
private val p2pApi: P2PEthPoolApi,
private val p2pVaultsStore: P2PEthPoolVaultsStore,
private val dispatchers: CoroutineDispatcherProvider,
) : MultiYieldBalanceFetcher {
) : MultiStakingBalanceFetcher {
override suspend fun invoke(params: MultiYieldBalanceFetcher.Params): Either<Throwable, Unit> {
Timber.i("Start fetching yield balances for params:\n$params")
override suspend fun invoke(params: MultiStakingBalanceFetcher.Params): Either<Throwable, Unit> {
Timber.i("Start fetching staking balances for params:\n$params")
val stakingIds = params.stakingIds.ifEmpty {
Timber.i("Nothing to fetch, empty stakingIds for ${params.userWalletId}")
@ -102,10 +102,13 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor(
}
}
.onLeft { throwable ->
Timber.e(throwable, "Unable to fetch yield balances $params")
Timber.e(throwable, "Unable to fetch staking balances $params")
if (stakeKitIds.isNotEmpty()) {
yieldsBalancesStore.storeError(userWalletId = params.userWalletId, stakingIds = stakeKitIds.toSet())
stakingBalancesStore.storeError(
userWalletId = params.userWalletId,
stakingIds = stakeKitIds.toSet(),
)
}
if (p2pIds.isNotEmpty()) {
p2pBalancesStore.storeError(userWalletId = params.userWalletId, stakingIds = p2pIds.toSet())
@ -114,7 +117,7 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor(
}
private suspend fun fetchStakeKitBalances(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
yieldsBalancesStore.refresh(userWalletId = userWalletId, stakingIds = stakingIds)
stakingBalancesStore.refresh(userWalletId = userWalletId, stakingIds = stakingIds)
val availableStakingIds = getAvailableStakingIds(
userWalletId = userWalletId,
@ -252,7 +255,7 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor(
)
if (unavailableStakingIds.isNotEmpty()) {
yieldsBalancesStore.storeError(userWalletId = userWalletId, stakingIds = unavailableStakingIds.toSet())
stakingBalancesStore.storeError(userWalletId = userWalletId, stakingIds = unavailableStakingIds.toSet())
}
return availableStakingIds.toSet().ifEmpty {
@ -301,8 +304,10 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor(
.toSet()
}
Timber.i("Successfully fetched yield balances for $userWalletId:\n${yieldBalances.joinToString("\n")}")
yieldsBalancesStore.storeActual(userWalletId = userWalletId, values = yieldBalances)
Timber.i(
"Successfully fetched staking balances for $userWalletId:\n${yieldBalances.joinToString("\n")}",
)
stakingBalancesStore.storeActual(userWalletId = userWalletId, values = yieldBalances)
if (!allResponsesReceived(requests, yieldBalances)) {
val values = stakingIds.filter { stakingId ->
@ -312,13 +317,13 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor(
}
}
yieldsBalancesStore.storeError(userWalletId = userWalletId, stakingIds = values.toSet())
stakingBalancesStore.storeError(userWalletId = userWalletId, stakingIds = values.toSet())
}
},
onError = { throwable ->
Timber.e(throwable, "Unable to fetch yield balances $userWalletId")
Timber.e(throwable, "Unable to fetch staking balances $userWalletId")
yieldsBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds)
stakingBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds)
throw throwable
},

View file

@ -3,9 +3,9 @@ package com.tangem.data.staking.multi
import arrow.core.Option
import arrow.core.some
import com.tangem.data.staking.store.P2PBalancesStore
import com.tangem.data.staking.store.YieldsBalancesStore
import com.tangem.domain.models.staking.YieldBalance
import com.tangem.domain.staking.multi.MultiYieldBalanceProducer
import com.tangem.data.staking.store.StakingBalancesStore
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.staking.multi.MultiStakingBalanceProducer
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -17,28 +17,28 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onEmpty
/**
* Default implementation of [MultiYieldBalanceProducer]
* Default implementation of [MultiStakingBalanceProducer]
*
* Combines yield balances from both StakeKit and P2P providers.
* Combines staking balances from both StakeKit and P2P providers.
*
* @property params params
* @property yieldsBalancesStore StakeKit yields balances store
* @property stakingBalancesStore StakeKit staking balances store
* @property p2pBalancesStore P2P balances store
* @property dispatchers dispatchers
*
[REDACTED_AUTHOR]
*/
internal class DefaultMultiYieldBalanceProducer @AssistedInject constructor(
@Assisted val params: MultiYieldBalanceProducer.Params,
private val yieldsBalancesStore: YieldsBalancesStore,
internal class DefaultMultiStakingBalanceProducer @AssistedInject constructor(
@Assisted val params: MultiStakingBalanceProducer.Params,
private val stakingBalancesStore: StakingBalancesStore,
private val p2pBalancesStore: P2PBalancesStore,
private val dispatchers: CoroutineDispatcherProvider,
) : MultiYieldBalanceProducer {
) : MultiStakingBalanceProducer {
override val fallback: Option<Set<YieldBalance>> = emptySet<YieldBalance>().some()
override val fallback: Option<Set<StakingBalance>> = emptySet<StakingBalance>().some()
override fun produce(): Flow<Set<YieldBalance>> {
val stakeKitFlow = yieldsBalancesStore.get(userWalletId = params.userWalletId)
override fun produce(): Flow<Set<StakingBalance>> {
val stakeKitFlow = stakingBalancesStore.get(userWalletId = params.userWalletId)
val p2pFlow = p2pBalancesStore.get(userWalletId = params.userWalletId)
return combine(stakeKitFlow, p2pFlow) { stakeKitBalances, p2pBalances ->
@ -50,7 +50,7 @@ internal class DefaultMultiYieldBalanceProducer @AssistedInject constructor(
}
@AssistedFactory
interface Factory : MultiYieldBalanceProducer.Factory {
override fun create(params: MultiYieldBalanceProducer.Params): DefaultMultiYieldBalanceProducer
interface Factory : MultiStakingBalanceProducer.Factory {
override fun create(params: MultiStakingBalanceProducer.Params): DefaultMultiStakingBalanceProducer
}
}

View file

@ -0,0 +1,27 @@
package com.tangem.data.staking.single
import arrow.core.Either
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
import com.tangem.domain.staking.single.SingleStakingBalanceFetcher
import javax.inject.Inject
/**
* Default implementation of [SingleStakingBalanceFetcher]
*
* @property multiStakingBalanceFetcher multi staking balance fetcher
*
[REDACTED_AUTHOR]
*/
internal class DefaultSingleStakingBalanceFetcher @Inject constructor(
private val multiStakingBalanceFetcher: MultiStakingBalanceFetcher,
) : SingleStakingBalanceFetcher {
override suspend fun invoke(params: SingleStakingBalanceFetcher.Params): Either<Throwable, Unit> {
return multiStakingBalanceFetcher(
params = MultiStakingBalanceFetcher.Params(
userWalletId = params.userWalletId,
stakingIds = setOf(params.stakingId),
),
)
}
}

View file

@ -4,10 +4,10 @@ import arrow.core.Option
import arrow.core.some
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
import com.tangem.domain.models.staking.YieldBalance
import com.tangem.domain.staking.multi.MultiYieldBalanceProducer
import com.tangem.domain.staking.multi.MultiYieldBalanceSupplier
import com.tangem.domain.staking.single.SingleYieldBalanceProducer
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.staking.multi.MultiStakingBalanceProducer
import com.tangem.domain.staking.multi.MultiStakingBalanceSupplier
import com.tangem.domain.staking.single.SingleStakingBalanceProducer
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.indexOfFirstOrNull
import dagger.assisted.Assisted
@ -20,29 +20,29 @@ import kotlinx.coroutines.flow.mapNotNull
import timber.log.Timber
/**
* Default implementation of [SingleYieldBalanceProducer]
* Default implementation of [SingleStakingBalanceProducer]
*
* @property params params
* @property multiYieldBalanceSupplier multi yield balance supplier
* @property analyticsExceptionHandler analytics exception handler
* @property dispatchers dispatchers
* @property params params
* @property multiStakingBalanceSupplier multi staking balance supplier
* @property analyticsExceptionHandler analytics exception handler
* @property dispatchers dispatchers
*
[REDACTED_AUTHOR]
*/
internal class DefaultSingleYieldBalanceProducer @AssistedInject constructor(
@Assisted private val params: SingleYieldBalanceProducer.Params,
private val multiYieldBalanceSupplier: MultiYieldBalanceSupplier,
internal class DefaultSingleStakingBalanceProducer @AssistedInject constructor(
@Assisted private val params: SingleStakingBalanceProducer.Params,
private val multiStakingBalanceSupplier: MultiStakingBalanceSupplier,
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
private val dispatchers: CoroutineDispatcherProvider,
) : SingleYieldBalanceProducer {
) : SingleStakingBalanceProducer {
override val fallback: Option<YieldBalance> = YieldBalance.Error(stakingId = params.stakingId).some()
override val fallback: Option<StakingBalance> = StakingBalance.Error(stakingId = params.stakingId).some()
override fun produce(): Flow<YieldBalance> {
Timber.i("Producing yield balance for params:\n$params")
override fun produce(): Flow<StakingBalance> {
Timber.i("Producing staking balance for params:\n$params")
return multiYieldBalanceSupplier(
params = MultiYieldBalanceProducer.Params(userWalletId = params.userWalletId),
return multiStakingBalanceSupplier(
params = MultiStakingBalanceProducer.Params(userWalletId = params.userWalletId),
)
.mapNotNull { balances ->
val currentStakingId = params.stakingId
@ -65,7 +65,7 @@ internal class DefaultSingleYieldBalanceProducer @AssistedInject constructor(
currentBalances.joinToString("\n"),
)
val dataIndex = currentBalances.indexOfFirstOrNull { it is YieldBalance.Data }
val dataIndex = currentBalances.indexOfFirstOrNull { it is StakingBalance.Data }
if (dataIndex != null) {
currentBalances[dataIndex]
@ -75,7 +75,7 @@ internal class DefaultSingleYieldBalanceProducer @AssistedInject constructor(
} else {
val balance = currentBalances.firstOrNull() ?: return@mapNotNull null
Timber.i("Yield balance found for $currentStakingId:\n$balance")
Timber.i("Staking balance found for $currentStakingId:\n$balance")
balance
}
}
@ -84,7 +84,7 @@ internal class DefaultSingleYieldBalanceProducer @AssistedInject constructor(
}
@AssistedFactory
interface Factory : SingleYieldBalanceProducer.Factory {
override fun create(params: SingleYieldBalanceProducer.Params): DefaultSingleYieldBalanceProducer
interface Factory : SingleStakingBalanceProducer.Factory {
override fun create(params: SingleStakingBalanceProducer.Params): DefaultSingleStakingBalanceProducer
}
}

View file

@ -1,27 +0,0 @@
package com.tangem.data.staking.single
import arrow.core.Either
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
import javax.inject.Inject
/**
* Default implementation of [MultiYieldBalanceFetcher]
*
* @property multiYieldBalanceFetcher multi yield balance fetcher
*
[REDACTED_AUTHOR]
*/
internal class DefaultSingleYieldBalanceFetcher @Inject constructor(
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
) : SingleYieldBalanceFetcher {
override suspend fun invoke(params: SingleYieldBalanceFetcher.Params): Either<Throwable, Unit> {
return multiYieldBalanceFetcher(
params = MultiYieldBalanceFetcher.Params(
userWalletId = params.userWalletId,
stakingIds = setOf(params.stakingId),
),
)
}
}

View file

@ -1,15 +1,14 @@
package com.tangem.data.staking.store
import androidx.datastore.core.DataStore
import com.tangem.data.staking.converters.ethpool.P2PEthPoolAccountConverter
import com.tangem.data.staking.converters.ethpool.P2PYieldBalanceConverter
import com.tangem.data.staking.converters.ethpool.P2PStakingBalanceConverter
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
import com.tangem.datasource.local.datastore.RuntimeSharedStore
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.staking.YieldBalance
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
import com.tangem.domain.staking.model.StakingIntegrationID
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.CoroutineScope
@ -19,25 +18,22 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import timber.log.Timber
internal typealias WalletIdWithP2PBalances = Map<UserWalletId, Set<YieldBalance>>
internal typealias WalletIdWithP2PStakingBalances = Map<UserWalletId, Set<StakingBalance>>
internal typealias WalletIdWithP2PResponses = Map<String, Set<P2PEthPoolAccountResponse>>
/**
* Default implementation of [P2PBalancesStore]
*
* Stores P2P ETH Pool staking balances with persistence support.
* Stores P2P ETH Pool staking balances.
*
* @property runtimeStore runtime store
* @property persistenceStore persistence store
* @property vaultsProvider provider for vaults
* @param dispatchers coroutine dispatchers
*/
internal class DefaultP2PBalancesStore(
private val runtimeStore: RuntimeSharedStore<WalletIdWithP2PBalances>,
private val runtimeStore: RuntimeSharedStore<WalletIdWithP2PStakingBalances>,
private val persistenceStore: DataStore<WalletIdWithP2PResponses>,
private val vaultsProvider: suspend () -> List<P2PEthPoolVault>,
dispatchers: CoroutineDispatcherProvider,
) : P2PBalancesStore {
@ -46,19 +42,13 @@ internal class DefaultP2PBalancesStore(
init {
scope.launch {
val cachedData = persistenceStore.data.firstOrNull() ?: return@launch
val vaults = vaultsProvider()
runtimeStore.store(
value = cachedData.map { (stringWalletId, responses) ->
val key = UserWalletId(stringWalletId)
val value = responses.mapNotNull { response ->
val vault = vaults.firstOrNull { it.vaultAddress == response.vaultAddress }
?: return@mapNotNull null
val account = P2PEthPoolAccountConverter.convert(response)
P2PYieldBalanceConverter.convert(
account = account,
vault = vault,
address = account.delegatorAddress,
val value = responses.map { response ->
P2PStakingBalanceConverter.convert(
response = response,
source = StatusSource.CACHE,
)
}.toSet()
@ -69,17 +59,17 @@ internal class DefaultP2PBalancesStore(
}
}
override fun get(userWalletId: UserWalletId): Flow<Set<YieldBalance>> {
override fun get(userWalletId: UserWalletId): Flow<Set<StakingBalance>> {
return runtimeStore.get().map { it[userWalletId].orEmpty() }
}
override suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingId: StakingID): YieldBalance? {
override suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingId: StakingID): StakingBalance? {
return runtimeStore.getSyncOrNull()
?.get(userWalletId)
?.firstOrNull { it.stakingId == stakingId }
}
override suspend fun getAllSyncOrNull(userWalletId: UserWalletId): Set<YieldBalance>? {
override suspend fun getAllSyncOrNull(userWalletId: UserWalletId): Set<StakingBalance>? {
return runtimeStore.getSyncOrNull()?.get(userWalletId)
}
@ -104,7 +94,7 @@ internal class DefaultP2PBalancesStore(
updateInRuntime(
userWalletId = userWalletId,
stakingIds = stakingIds,
ifNotFound = ::createErrorYieldBalance,
ifNotFound = ::createErrorStakingBalance,
update = { it.copySealed(source = StatusSource.ONLY_CACHE) },
)
}
@ -117,20 +107,9 @@ internal class DefaultP2PBalancesStore(
}
private suspend fun storeInRuntime(userWalletId: UserWalletId, values: Set<P2PEthPoolAccountResponse>) {
val vaults = vaultsProvider()
val newBalances = values.mapNotNull { response ->
val vault = vaults.firstOrNull { it.vaultAddress == response.vaultAddress }
if (vault == null) {
Timber.w("Vault not found for ${response.vaultAddress}")
return@mapNotNull null
}
val account = P2PEthPoolAccountConverter.convert(response)
P2PYieldBalanceConverter.convert(
account = account,
vault = vault,
address = account.delegatorAddress,
val newBalances = values.map { response ->
P2PStakingBalanceConverter.convert(
response = response,
source = StatusSource.ACTUAL,
)
}.toSet()
@ -173,8 +152,7 @@ internal class DefaultP2PBalancesStore(
current.toMutableMap().apply {
this[userWalletId.stringValue] = this[userWalletId.stringValue].orEmpty()
.filterNot { response ->
val responseIntegrationId = "p2p-ethereum-pooled:${response.vaultAddress}"
responseIntegrationId in integrationIds
StakingIntegrationID.P2P.EthereumPooled.value in integrationIds
}
.toSet()
}
@ -184,8 +162,8 @@ internal class DefaultP2PBalancesStore(
private suspend fun updateInRuntime(
userWalletId: UserWalletId,
stakingIds: Set<StakingID>,
ifNotFound: (StakingID) -> YieldBalance? = { null },
update: (YieldBalance) -> YieldBalance,
ifNotFound: (StakingID) -> StakingBalance? = { null },
update: (StakingBalance) -> StakingBalance,
) {
runtimeStore.update(default = emptyMap()) { stored ->
stored.toMutableMap().apply {
@ -209,5 +187,5 @@ internal class DefaultP2PBalancesStore(
}
}
private fun createErrorYieldBalance(id: StakingID): YieldBalance = YieldBalance.Error(stakingId = id)
private fun createErrorStakingBalance(id: StakingID): StakingBalance = StakingBalance.Error(stakingId = id)
}

View file

@ -3,10 +3,10 @@ package com.tangem.data.staking.store
import androidx.datastore.core.DataStore
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.datasource.local.token.converter.YieldBalanceConverter
import com.tangem.datasource.local.token.converter.StakingBalanceConverter
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.staking.YieldBalance
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.addOrReplace
@ -19,10 +19,10 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
internal typealias WalletIdWithWrappers = Map<String, Set<YieldBalanceWrapperDTO>>
internal typealias WalletIdWithBalances = Map<UserWalletId, Set<YieldBalance>>
internal typealias WalletIdWithStakingBalances = Map<UserWalletId, Set<StakingBalance>>
/**
* Default implementation of [YieldsBalancesStore]
* Default implementation of [StakingBalancesStore]
*
* @property runtimeStore runtime store
* @property persistenceStore persistence store
@ -30,11 +30,11 @@ internal typealias WalletIdWithBalances = Map<UserWalletId, Set<YieldBalance>>
*
[REDACTED_AUTHOR]
*/
internal class DefaultYieldsBalancesStore(
private val runtimeStore: RuntimeSharedStore<WalletIdWithBalances>,
internal class DefaultStakingBalancesStore(
private val runtimeStore: RuntimeSharedStore<WalletIdWithStakingBalances>,
private val persistenceStore: DataStore<WalletIdWithWrappers>,
dispatchers: CoroutineDispatcherProvider,
) : YieldsBalancesStore {
) : StakingBalancesStore {
private val scope = CoroutineScope(context = SupervisorJob() + dispatchers.io)
@ -45,7 +45,7 @@ internal class DefaultYieldsBalancesStore(
runtimeStore.store(
value = cachedStatuses.map { (stringWalletId, wrappers) ->
val key = UserWalletId(stringWalletId)
val value = YieldBalanceConverter(isCached = true).convertSet(input = wrappers)
val value = StakingBalanceConverter(isCached = true).convertSet(input = wrappers)
.filterNotNull()
.toSet()
@ -56,17 +56,17 @@ internal class DefaultYieldsBalancesStore(
}
}
override fun get(userWalletId: UserWalletId): Flow<Set<YieldBalance>> {
override fun get(userWalletId: UserWalletId): Flow<Set<StakingBalance>> {
return runtimeStore.get().map { it[userWalletId].orEmpty() }
}
override suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingId: StakingID): YieldBalance? {
override suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingId: StakingID): StakingBalance? {
return runtimeStore.getSyncOrNull()
?.get(userWalletId)
?.firstOrNull { it.stakingId == stakingId }
}
override suspend fun getAllSyncOrNull(userWalletId: UserWalletId): Set<YieldBalance>? {
override suspend fun getAllSyncOrNull(userWalletId: UserWalletId): Set<StakingBalance>? {
return runtimeStore.getSyncOrNull()?.get(userWalletId)
}
@ -91,7 +91,7 @@ internal class DefaultYieldsBalancesStore(
updateInRuntime(
userWalletId = userWalletId,
stakingIds = stakingIds,
ifNotFound = ::createErrorYieldBalance,
ifNotFound = ::createErrorStakingBalance,
update = { it.copySealed(source = StatusSource.ONLY_CACHE) },
)
}
@ -107,7 +107,7 @@ internal class DefaultYieldsBalancesStore(
}
private suspend fun storeInRuntime(userWalletId: UserWalletId, values: Set<YieldBalanceWrapperDTO>) {
val newBalances = YieldBalanceConverter(isCached = false).convertSet(input = values)
val newBalances = StakingBalanceConverter(isCached = false).convertSet(input = values)
.filterNotNull()
.toSet()
@ -140,8 +140,8 @@ internal class DefaultYieldsBalancesStore(
private suspend fun updateInRuntime(
userWalletId: UserWalletId,
stakingIds: Set<StakingID>,
ifNotFound: (StakingID) -> YieldBalance? = { null },
update: (YieldBalance) -> YieldBalance,
ifNotFound: (StakingID) -> StakingBalance? = { null },
update: (StakingBalance) -> StakingBalance,
) {
runtimeStore.update(default = emptyMap()) { stored ->
stored.toMutableMap().apply {
@ -165,7 +165,7 @@ internal class DefaultYieldsBalancesStore(
}
}
private fun createErrorYieldBalance(id: StakingID): YieldBalance = YieldBalance.Error(stakingId = id)
private fun createErrorStakingBalance(id: StakingID): StakingBalance = StakingBalance.Error(stakingId = id)
private fun YieldBalanceWrapperDTO.getStakingId(): StakingID? {
val integrationId = integrationId

View file

@ -1,8 +1,8 @@
package com.tangem.data.staking.store
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.models.staking.StakingID
import com.tangem.domain.models.staking.YieldBalance
import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.coroutines.flow.Flow
@ -11,11 +11,11 @@ import kotlinx.coroutines.flow.Flow
*/
interface P2PBalancesStore {
fun get(userWalletId: UserWalletId): Flow<Set<YieldBalance>>
fun get(userWalletId: UserWalletId): Flow<Set<StakingBalance>>
suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingId: StakingID): YieldBalance?
suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingId: StakingID): StakingBalance?
suspend fun getAllSyncOrNull(userWalletId: UserWalletId): Set<YieldBalance>?
suspend fun getAllSyncOrNull(userWalletId: UserWalletId): Set<StakingBalance>?
suspend fun refresh(userWalletId: UserWalletId, stakingId: StakingID)

View file

@ -1,39 +1,27 @@
package com.tangem.data.staking.store
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.models.staking.StakingID
import com.tangem.domain.models.staking.YieldBalance
import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.coroutines.flow.Flow
/**
* Store of [YieldBalance]'s set
*
[REDACTED_AUTHOR]
*/
interface YieldsBalancesStore {
/** Store of StakeKit [StakingBalance] */
interface StakingBalancesStore {
/** Get flow of [YieldBalance]'s set by [userWalletId] */
fun get(userWalletId: UserWalletId): Flow<Set<YieldBalance>>
fun get(userWalletId: UserWalletId): Flow<Set<StakingBalance>>
/** Get [YieldBalance] by [userWalletId] and [stakingId] synchronously or null */
suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingId: StakingID): YieldBalance?
suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingId: StakingID): StakingBalance?
/** Get all [YieldBalance] by [userWalletId] synchronously or null */
suspend fun getAllSyncOrNull(userWalletId: UserWalletId): Set<YieldBalance>?
suspend fun getAllSyncOrNull(userWalletId: UserWalletId): Set<StakingBalance>?
/** Refresh balance of [stakingId] by [userWalletId] */
suspend fun refresh(userWalletId: UserWalletId, stakingId: StakingID)
/** Refresh balances of [stakingIds] by [userWalletId] */
suspend fun refresh(userWalletId: UserWalletId, stakingIds: Set<StakingID>)
/** Store actual [values] by [userWalletId] */
suspend fun storeActual(userWalletId: UserWalletId, values: Set<YieldBalanceWrapperDTO>)
/** Store error by [userWalletId] and [stakingIds] */
suspend fun storeError(userWalletId: UserWalletId, stakingIds: Set<StakingID>)
/** Clear balances of [stakingIds] by [userWalletId] */
suspend fun clear(userWalletId: UserWalletId, stakingIds: Set<StakingID>)
}

View file

@ -1,6 +1,6 @@
package com.tangem.data.staking.utils
import com.tangem.data.staking.store.YieldsBalancesStore
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.utils.StakingCleaner
@ -9,13 +9,13 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
/**
* Default implementation of [StakingCleaner].
*
* @property yieldsBalancesStore Store to manage yields balances.
* @property stakingBalancesStore Store to manage staking balances.
* @property dispatchers Coroutine dispatchers provider.
*
[REDACTED_AUTHOR]
*/
internal class DefaultStakingCleaner(
private val yieldsBalancesStore: YieldsBalancesStore,
private val stakingBalancesStore: StakingBalancesStore,
private val dispatchers: CoroutineDispatcherProvider,
) : StakingCleaner {
@ -23,7 +23,7 @@ internal class DefaultStakingCleaner(
if (stakingIds.isEmpty()) return
with(dispatchers.default) {
yieldsBalancesStore.clear(userWalletId, stakingIds)
stakingBalancesStore.clear(userWalletId, stakingIds)
}
}
}

View file

@ -1,28 +1,19 @@
package com.tangem.data.staking
import com.tangem.common.test.data.staking.MockP2PEthPoolAccountResponseFactory
import com.tangem.data.staking.converters.ethpool.P2PEthPoolAccountConverter
import com.tangem.data.staking.converters.ethpool.P2PYieldBalanceConverter
import com.tangem.data.staking.converters.ethpool.P2PStakingBalanceConverter
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.datasource.local.token.converter.YieldBalanceConverter
import com.tangem.datasource.local.token.converter.StakingBalanceConverter
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.staking.YieldBalance
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
import com.tangem.domain.models.staking.StakingBalance
internal fun YieldBalanceWrapperDTO.toDomain(source: StatusSource = StatusSource.CACHE): YieldBalance {
return YieldBalanceConverter(source = source).convert(this)!!
internal fun YieldBalanceWrapperDTO.toDomain(source: StatusSource = StatusSource.CACHE): StakingBalance {
return StakingBalanceConverter(isCached = source == StatusSource.CACHE).convert(this)!!
}
internal fun P2PEthPoolAccountResponse.toDomain(
vault: P2PEthPoolVault = MockP2PEthPoolAccountResponseFactory.createMockVault(vaultAddress = vaultAddress),
source: StatusSource = StatusSource.CACHE,
): YieldBalance {
val account = P2PEthPoolAccountConverter.convert(this)
return P2PYieldBalanceConverter.convert(
account = account,
vault = vault,
address = account.delegatorAddress,
internal fun P2PEthPoolAccountResponse.toDomain(source: StatusSource = StatusSource.CACHE): StakingBalance.Data.P2P {
return P2PStakingBalanceConverter.convert(
response = this,
source = source,
)
}

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.P2PBalancesStore
import com.tangem.data.staking.store.YieldsBalancesStore
import com.tangem.data.staking.store.StakingBalancesStore
import com.tangem.data.staking.utils.YieldBalanceRequestBodyFactory
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.common.response.ApiResponseError
@ -17,7 +17,7 @@ import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.models.staking.StakingID
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
import com.tangem.test.core.assertEitherLeft
import com.tangem.test.core.assertEitherRight
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
@ -31,20 +31,20 @@ import org.junit.jupiter.api.TestInstance
[REDACTED_AUTHOR]
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class DefaultMultiYieldBalanceFetcherTest {
internal class DefaultMultiStakingBalanceFetcherTest {
private val userWalletsStore: UserWalletsStore = mockk()
private val stakingYieldsStore: StakingYieldsStore = mockk()
private val yieldsBalancesStore: YieldsBalancesStore = mockk(relaxUnitFun = true)
private val stakingBalancesStore: StakingBalancesStore = mockk(relaxUnitFun = true)
private val p2pBalancesStore: P2PBalancesStore = mockk(relaxUnitFun = true)
private val stakeKitApi: StakeKitApi = mockk()
private val p2pApi: P2PEthPoolApi = mockk()
private val p2pVaultsStore: P2PEthPoolVaultsStore = mockk()
private val fetcher = DefaultMultiYieldBalanceFetcher(
private val fetcher = DefaultMultiStakingBalanceFetcher(
userWalletsStore = userWalletsStore,
stakingYieldsStore = stakingYieldsStore,
yieldsBalancesStore = yieldsBalancesStore,
stakingBalancesStore = stakingBalancesStore,
p2pBalancesStore = p2pBalancesStore,
stakeKitApi = stakeKitApi,
p2pApi = p2pApi,
@ -54,13 +54,13 @@ internal class DefaultMultiYieldBalanceFetcherTest {
@BeforeEach
fun resetMocks() {
clearMocks(userWalletsStore, stakingYieldsStore, yieldsBalancesStore, stakeKitApi)
clearMocks(userWalletsStore, stakingYieldsStore, stakingBalancesStore, stakeKitApi)
}
@Test
fun `fetch yields balances successfully`() = runTest {
fun `fetch staking balances successfully`() = runTest {
// Arrange
val params = MultiYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
val params = MultiStakingBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
coEvery { userWalletsStore.getSyncOrNull(params.userWalletId) } returns userWallet
@ -81,21 +81,21 @@ internal class DefaultMultiYieldBalanceFetcherTest {
// Assert
coVerifyOrder {
userWalletsStore.getSyncOrNull(params.userWalletId)
yieldsBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingYieldsStore.getSyncWithTimeout()
stakeKitApi.getMultipleYieldBalances(requests)
yieldsBalancesStore.storeActual(userWalletId = userWalletId, values = result)
stakingBalancesStore.storeActual(userWalletId = userWalletId, values = result)
}
coVerify(inverse = true) { yieldsBalancesStore.storeError(any(), any()) }
coVerify(inverse = true) { stakingBalancesStore.storeError(any(), any()) }
assertEitherRight(actual)
}
@Test
fun `fetch yields balances successfully if one of stakingIds is unavailable`() = runTest {
fun `fetch staking balances successfully if one of stakingIds is unavailable`() = runTest {
// Arrange
val params = MultiYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
val params = MultiStakingBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
coEvery { userWalletsStore.getSyncOrNull(params.userWalletId) } returns userWallet
@ -113,20 +113,20 @@ internal class DefaultMultiYieldBalanceFetcherTest {
// Assert
coVerifyOrder {
userWalletsStore.getSyncOrNull(params.userWalletId)
yieldsBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingYieldsStore.getSyncWithTimeout()
yieldsBalancesStore.storeError(userWalletId = userWalletId, stakingIds = setOf(solanaId))
stakingBalancesStore.storeError(userWalletId = userWalletId, stakingIds = setOf(solanaId))
stakeKitApi.getMultipleYieldBalances(requests)
yieldsBalancesStore.storeActual(userWalletId = userWalletId, values = result)
stakingBalancesStore.storeActual(userWalletId = userWalletId, values = result)
}
assertEitherRight(actual)
}
@Test
fun `fetch yields balances failure if user wallet is not supported`() = runTest {
fun `fetch staking balances failure if user wallet is not supported`() = runTest {
// Arrange
val params = MultiYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
val params = MultiStakingBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
val userWallet = MockUserWalletFactory.create().copy(isMultiCurrency = false)
coEvery { userWalletsStore.getSyncOrNull(params.userWalletId) } returns userWallet
@ -138,11 +138,11 @@ internal class DefaultMultiYieldBalanceFetcherTest {
coVerifyOrder { userWalletsStore.getSyncOrNull(params.userWalletId) }
coVerify(inverse = true) {
yieldsBalancesStore.refresh(userWalletId = any(), stakingIds = any())
stakingBalancesStore.refresh(userWalletId = any(), stakingIds = any())
stakingYieldsStore.getSyncWithTimeout()
stakeKitApi.getSingleYieldBalance(integrationId = any(), body = any())
yieldsBalancesStore.storeActual(userWalletId = any(), values = any())
yieldsBalancesStore.storeError(userWalletId = any(), stakingIds = any())
stakingBalancesStore.storeActual(userWalletId = any(), values = any())
stakingBalancesStore.storeError(userWalletId = any(), stakingIds = any())
}
val expected = IllegalStateException("Wallet ${params.userWalletId} is not supported: ${userWallet.toOption()}")
@ -151,9 +151,9 @@ internal class DefaultMultiYieldBalanceFetcherTest {
}
@Test
fun `fetch yields balances failure if userWalletsStore returns null`() = runTest {
fun `fetch staking balances failure if userWalletsStore returns null`() = runTest {
// Arrange
val params = MultiYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
val params = MultiStakingBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
coEvery { userWalletsStore.getSyncOrNull(params.userWalletId) } returns null
@ -164,11 +164,11 @@ internal class DefaultMultiYieldBalanceFetcherTest {
coVerifyOrder { userWalletsStore.getSyncOrNull(params.userWalletId) }
coVerify(inverse = true) {
yieldsBalancesStore.refresh(userWalletId = any(), stakingIds = any())
stakingBalancesStore.refresh(userWalletId = any(), stakingIds = any())
stakingYieldsStore.getSyncWithTimeout()
stakeKitApi.getSingleYieldBalance(integrationId = any(), body = any())
yieldsBalancesStore.storeActual(userWalletId = any(), values = any())
yieldsBalancesStore.storeError(userWalletId = any(), stakingIds = any())
stakingBalancesStore.storeActual(userWalletId = any(), values = any())
stakingBalancesStore.storeError(userWalletId = any(), stakingIds = any())
}
val expected = IllegalStateException("Wallet ${params.userWalletId} is not supported: ${null.toOption()}")
@ -177,9 +177,9 @@ internal class DefaultMultiYieldBalanceFetcherTest {
}
@Test
fun `fetch yields balances failure if stakingYieldsStore getSyncWithTimeout returns null`() = runTest {
fun `fetch staking balances failure if stakingYieldsStore getSyncWithTimeout returns null`() = runTest {
// Arrange
val params = MultiYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
val params = MultiStakingBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
coEvery { userWalletsStore.getSyncOrNull(params.userWalletId) } returns userWallet
coEvery { stakingYieldsStore.getSyncWithTimeout() } returns null
@ -190,14 +190,14 @@ internal class DefaultMultiYieldBalanceFetcherTest {
// Assert
coVerifyOrder {
userWalletsStore.getSyncOrNull(params.userWalletId)
yieldsBalancesStore.refresh(params.userWalletId, tonAndSolanaIds)
stakingBalancesStore.refresh(params.userWalletId, tonAndSolanaIds)
stakingYieldsStore.getSyncWithTimeout()
yieldsBalancesStore.storeError(userWalletId, tonAndSolanaIds)
stakingBalancesStore.storeError(userWalletId, tonAndSolanaIds)
}
coVerify(inverse = true) {
stakeKitApi.getMultipleYieldBalances(any())
yieldsBalancesStore.storeActual(userWalletId = any(), values = any())
stakingBalancesStore.storeActual(userWalletId = any(), values = any())
}
val expected = IllegalStateException("No enabled yields for ${params.userWalletId}")
@ -206,9 +206,9 @@ internal class DefaultMultiYieldBalanceFetcherTest {
}
@Test
fun `fetch yields balances failure if stakingYieldsStore getSyncWithTimeout returns empty list`() = runTest {
fun `fetch staking balances failure if stakingYieldsStore getSyncWithTimeout returns empty list`() = runTest {
// Arrange
val params = MultiYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
val params = MultiStakingBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
coEvery { userWalletsStore.getSyncOrNull(params.userWalletId) } returns userWallet
coEvery { stakingYieldsStore.getSyncWithTimeout() } returns emptyList()
@ -219,14 +219,14 @@ internal class DefaultMultiYieldBalanceFetcherTest {
// Assert
coVerifyOrder {
userWalletsStore.getSyncOrNull(params.userWalletId)
yieldsBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingYieldsStore.getSyncWithTimeout()
yieldsBalancesStore.storeError(userWalletId, tonAndSolanaIds)
stakingBalancesStore.storeError(userWalletId, tonAndSolanaIds)
}
coVerify(inverse = true) {
stakeKitApi.getMultipleYieldBalances(any())
yieldsBalancesStore.storeActual(userWalletId = any(), values = any())
stakingBalancesStore.storeActual(userWalletId = any(), values = any())
}
val expected = IllegalStateException("No enabled yields for ${params.userWalletId}")
@ -235,9 +235,9 @@ internal class DefaultMultiYieldBalanceFetcherTest {
}
@Test
fun `fetch yields balances failure if yields converting is failed`() = runTest {
fun `fetch staking balances failure if yields converting is failed`() = runTest {
// Arrange
val params = MultiYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
val params = MultiStakingBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
coEvery { userWalletsStore.getSyncOrNull(params.userWalletId) } returns userWallet
@ -253,14 +253,14 @@ internal class DefaultMultiYieldBalanceFetcherTest {
// Assert
coVerifyOrder {
userWalletsStore.getSyncOrNull(params.userWalletId)
yieldsBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingYieldsStore.getSyncWithTimeout()
yieldsBalancesStore.storeError(userWalletId, tonAndSolanaIds)
stakingBalancesStore.storeError(userWalletId, tonAndSolanaIds)
}
coVerify(inverse = true) {
stakeKitApi.getMultipleYieldBalances(any())
yieldsBalancesStore.storeActual(userWalletId = any(), values = any())
stakingBalancesStore.storeActual(userWalletId = any(), values = any())
}
val expected = IllegalStateException("No enabled yields for ${params.userWalletId}")
@ -269,9 +269,9 @@ internal class DefaultMultiYieldBalanceFetcherTest {
}
@Test
fun `fetch yields balances failure if available yields does not contain ids from params`() = runTest {
fun `fetch staking balances failure if available yields does not contain ids from params`() = runTest {
// Arrange
val params = MultiYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
val params = MultiStakingBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
coEvery { userWalletsStore.getSyncOrNull(params.userWalletId) } returns userWallet
@ -284,14 +284,14 @@ internal class DefaultMultiYieldBalanceFetcherTest {
// Assert
coVerifyOrder {
userWalletsStore.getSyncOrNull(params.userWalletId)
yieldsBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingYieldsStore.getSyncWithTimeout()
yieldsBalancesStore.storeError(userWalletId, tonAndSolanaIds)
stakingBalancesStore.storeError(userWalletId, tonAndSolanaIds)
}
coVerify(inverse = true) {
stakeKitApi.getMultipleYieldBalances(any())
yieldsBalancesStore.storeActual(userWalletId = any(), values = any())
stakingBalancesStore.storeActual(userWalletId = any(), values = any())
}
val expected = IllegalStateException(
@ -306,9 +306,9 @@ internal class DefaultMultiYieldBalanceFetcherTest {
}
@Test
fun `fetch yields balances failure if stakeKitApi getMultipleYieldBalances is failed`() = runTest {
fun `fetch staking balances failure if stakeKitApi getMultipleYieldBalances is failed`() = runTest {
// Arrange
val params = MultiYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
val params = MultiStakingBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
coEvery { userWalletsStore.getSyncOrNull(params.userWalletId) } returns userWallet
@ -329,13 +329,13 @@ internal class DefaultMultiYieldBalanceFetcherTest {
// Assert
coVerifyOrder {
userWalletsStore.getSyncOrNull(params.userWalletId)
yieldsBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingBalancesStore.refresh(userWalletId = params.userWalletId, stakingIds = tonAndSolanaIds)
stakingYieldsStore.getSyncWithTimeout()
stakeKitApi.getMultipleYieldBalances(requests)
yieldsBalancesStore.storeError(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
stakingBalancesStore.storeError(userWalletId = userWalletId, stakingIds = tonAndSolanaIds)
}
coVerify(inverse = true) { yieldsBalancesStore.storeActual(userWalletId = any(), values = any()) }
coVerify(inverse = true) { stakingBalancesStore.storeActual(userWalletId = any(), values = any()) }
val expected = ApiResponseError.NetworkException()

View file

@ -4,12 +4,13 @@ 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.P2PBalancesStore
import com.tangem.data.staking.store.YieldsBalancesStore
import com.tangem.data.staking.store.StakingBalancesStore
import com.tangem.data.staking.toDomain
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.staking.*
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.staking.multi.MultiYieldBalanceProducer
import com.tangem.domain.staking.model.StakingIntegrationID
import com.tangem.domain.staking.multi.MultiStakingBalanceProducer
import com.tangem.test.core.getEmittedValues
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.every
@ -22,17 +23,17 @@ import org.junit.Test
/**
[REDACTED_AUTHOR]
*/
internal class DefaultMultiYieldBalanceProducerTest {
internal class DefaultMultiStakingBalanceProducerTest {
private val params = MultiYieldBalanceProducer.Params(userWalletId = UserWalletId("011"))
private val params = MultiStakingBalanceProducer.Params(userWalletId = UserWalletId("011"))
private val yieldsBalancesStore = mockk<YieldsBalancesStore>()
private val stakingBalancesStore = mockk<StakingBalancesStore>()
private val p2pBalancesStore = mockk<P2PBalancesStore>()
private val dispatchers = TestingCoroutineDispatcherProvider()
private val producer = DefaultMultiYieldBalanceProducer(
private val producer = DefaultMultiStakingBalanceProducer(
params = params,
yieldsBalancesStore = yieldsBalancesStore,
stakingBalancesStore = stakingBalancesStore,
p2pBalancesStore = p2pBalancesStore,
dispatchers = dispatchers,
)
@ -46,13 +47,13 @@ internal class DefaultMultiYieldBalanceProducerTest {
val networksStatusesFlow = flowOf(balances)
every { yieldsBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
every { p2pBalancesStore.get(params.userWalletId) } returns flowOf(emptySet())
val actual = producer.produce()
// check after producer.produce()
verify { yieldsBalancesStore.get(params.userWalletId) }
verify { stakingBalancesStore.get(params.userWalletId) }
verify { p2pBalancesStore.get(params.userWalletId) }
val values = getEmittedValues(flow = actual)
@ -63,15 +64,15 @@ internal class DefaultMultiYieldBalanceProducerTest {
@Test
fun `test that flow is updated if balances are updated`() = runTest {
val networksStatusesFlow = MutableSharedFlow<Set<YieldBalance>>(replay = 2)
val networksStatusesFlow = MutableSharedFlow<Set<StakingBalance>>(replay = 2)
every { yieldsBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
every { p2pBalancesStore.get(params.userWalletId) } returns flowOf(emptySet())
val actual = producer.produce()
// check after producer.produce()
verify { yieldsBalancesStore.get(params.userWalletId) }
verify { stakingBalancesStore.get(params.userWalletId) }
verify { p2pBalancesStore.get(params.userWalletId) }
// first emit
@ -104,15 +105,15 @@ internal class DefaultMultiYieldBalanceProducerTest {
@Test
fun `test that flow is filtered the same balance`() = runTest {
val networksStatusesFlow = MutableSharedFlow<Set<YieldBalance>>(replay = 2)
val networksStatusesFlow = MutableSharedFlow<Set<StakingBalance>>(replay = 2)
every { yieldsBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
every { p2pBalancesStore.get(params.userWalletId) } returns flowOf(emptySet())
val actual = producer.produce()
// check after producer.produce()
verify { yieldsBalancesStore.get(params.userWalletId) }
verify { stakingBalancesStore.get(params.userWalletId) }
verify { p2pBalancesStore.get(params.userWalletId) }
// first emit
@ -155,19 +156,19 @@ internal class DefaultMultiYieldBalanceProducerTest {
}
.buffer(capacity = 5)
every { yieldsBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
every { p2pBalancesStore.get(params.userWalletId) } returns flowOf(emptySet())
val actual = producer.produceWithFallback()
// check after producer.produce()
verify { yieldsBalancesStore.get(params.userWalletId) }
verify { stakingBalancesStore.get(params.userWalletId) }
verify { p2pBalancesStore.get(params.userWalletId) }
val values1 = getEmittedValues(flow = actual)
Truth.assertThat(values1.size).isEqualTo(1)
Truth.assertThat(values1).isEqualTo(listOf(emptySet<YieldBalance>()))
Truth.assertThat(values1).isEqualTo(listOf(emptySet<StakingBalance>()))
innerFlow.emit(value = true)
@ -179,19 +180,19 @@ internal class DefaultMultiYieldBalanceProducerTest {
@Test
fun `test that flow is empty`() = runTest {
every { yieldsBalancesStore.get(params.userWalletId) } returns emptyFlow()
every { stakingBalancesStore.get(params.userWalletId) } returns emptyFlow()
every { p2pBalancesStore.get(params.userWalletId) } returns emptyFlow()
val actual = producer.produce()
// check after producer.produce()
verify { yieldsBalancesStore.get(params.userWalletId) }
verify { stakingBalancesStore.get(params.userWalletId) }
verify { p2pBalancesStore.get(params.userWalletId) }
val values = getEmittedValues(flow = actual)
Truth.assertThat(values.size).isEqualTo(1)
Truth.assertThat(values).isEqualTo(listOf(emptySet<YieldBalance>()))
Truth.assertThat(values).isEqualTo(listOf(emptySet<StakingBalance>()))
}
@Test
@ -199,13 +200,13 @@ internal class DefaultMultiYieldBalanceProducerTest {
val stakeKitBalances = createStakeKitBalances()
val p2pBalances = createP2PBalances()
every { yieldsBalancesStore.get(params.userWalletId) } returns flowOf(stakeKitBalances)
every { stakingBalancesStore.get(params.userWalletId) } returns flowOf(stakeKitBalances)
every { p2pBalancesStore.get(params.userWalletId) } returns flowOf(p2pBalances)
val actual = producer.produce()
// check after producer.produce()
verify { yieldsBalancesStore.get(params.userWalletId) }
verify { stakingBalancesStore.get(params.userWalletId) }
verify { p2pBalancesStore.get(params.userWalletId) }
val values = getEmittedValues(flow = actual)
@ -217,15 +218,15 @@ internal class DefaultMultiYieldBalanceProducerTest {
@Test
fun `test that P2P balances are updated independently from StakeKit`() = runTest {
val stakeKitBalances = createStakeKitBalancesWithTonOnly()
val p2pFlow = MutableSharedFlow<Set<YieldBalance>>(replay = 2)
val p2pFlow = MutableSharedFlow<Set<StakingBalance>>(replay = 2)
every { yieldsBalancesStore.get(params.userWalletId) } returns flowOf(stakeKitBalances)
every { stakingBalancesStore.get(params.userWalletId) } returns flowOf(stakeKitBalances)
every { p2pBalancesStore.get(params.userWalletId) } returns p2pFlow
val actual = producer.produce()
// check after producer.produce()
verify { yieldsBalancesStore.get(params.userWalletId) }
verify { stakingBalancesStore.get(params.userWalletId) }
verify { p2pBalancesStore.get(params.userWalletId) }
// first emit - empty P2P
@ -254,24 +255,24 @@ internal class DefaultMultiYieldBalanceProducerTest {
address = "0x1",
)
val p2pEthereumId = StakingID(
integrationId = "p2p-ethereum-pooled",
integrationId = StakingIntegrationID.P2P.EthereumPooled.value,
address = "0x5aa711F440Eb6d4361148bBD89d03464628ace84",
)
fun createStakeKitBalances(): Set<YieldBalance> {
fun createStakeKitBalances(): Set<StakingBalance> {
return setOf(
MockYieldBalanceWrapperDTOFactory.createWithBalance(tonId).toDomain(),
MockYieldBalanceWrapperDTOFactory.createWithBalance(solanaId).toDomain(),
)
}
fun createStakeKitBalancesWithTonOnly(): Set<YieldBalance> {
fun createStakeKitBalancesWithTonOnly(): Set<StakingBalance> {
return setOf(
MockYieldBalanceWrapperDTOFactory.createWithBalance(tonId).toDomain(),
)
}
fun createP2PBalances(): Set<YieldBalance> {
fun createP2PBalances(): Set<StakingBalance> {
return setOf(
MockP2PEthPoolAccountResponseFactory.createWithBalance(stakingId = p2pEthereumId).toDomain(
source = StatusSource.ACTUAL,

View file

@ -5,8 +5,8 @@ import arrow.core.right
import com.google.common.truth.Truth
import com.tangem.common.test.data.staking.MockYieldBalanceWrapperDTOFactory
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
import com.tangem.domain.staking.single.SingleStakingBalanceFetcher
import io.mockk.clearMocks
import io.mockk.coEvery
import io.mockk.coVerify
@ -20,32 +20,32 @@ import org.junit.jupiter.api.TestInstance
[REDACTED_AUTHOR]
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class DefaultSingleYieldBalanceFetcherTest {
internal class DefaultSingleStakingBalanceFetcherTest {
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher = mockk()
private val multiStakingBalanceFetcher: MultiStakingBalanceFetcher = mockk()
private val fetcher = DefaultSingleYieldBalanceFetcher(
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
private val fetcher = DefaultSingleStakingBalanceFetcher(
multiStakingBalanceFetcher = multiStakingBalanceFetcher,
)
@BeforeEach
fun resetMocks() {
clearMocks(multiYieldBalanceFetcher)
clearMocks(multiStakingBalanceFetcher)
}
@Test
fun `fetch yield balance successfully`() = runTest {
fun `fetch staking balance successfully`() = runTest {
// Arrange
val params = SingleYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingId = tonId)
val params = SingleStakingBalanceFetcher.Params(userWalletId = userWalletId, stakingId = tonId)
val multiParams = MultiYieldBalanceFetcher.Params(
val multiParams = MultiStakingBalanceFetcher.Params(
userWalletId = userWalletId,
stakingIds = setOf(tonId),
)
val multiResult = Unit.right()
coEvery { multiYieldBalanceFetcher(params = multiParams) } returns multiResult
coEvery { multiStakingBalanceFetcher(params = multiParams) } returns multiResult
// Act
val actual = fetcher.invoke(params).isRight()
@ -53,26 +53,26 @@ internal class DefaultSingleYieldBalanceFetcherTest {
// Assert
Truth.assertThat(actual).isTrue()
coVerify { multiYieldBalanceFetcher(params = multiParams) }
coVerify { multiStakingBalanceFetcher(params = multiParams) }
}
@Test
fun `fetch yield balance failure`() = runTest {
fun `fetch staking balance failure`() = runTest {
// Arrange
val params = SingleYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingId = tonId)
val params = SingleStakingBalanceFetcher.Params(userWalletId = userWalletId, stakingId = tonId)
val multiParams = MultiYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = setOf(tonId))
val multiParams = MultiStakingBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = setOf(tonId))
val multiResult = IllegalStateException().left()
coEvery { multiYieldBalanceFetcher(params = multiParams) } returns multiResult
coEvery { multiStakingBalanceFetcher(params = multiParams) } returns multiResult
// Act
val actual = fetcher.invoke(params)
// Assert
Truth.assertThat(actual).isEqualTo(multiResult)
coVerify { multiYieldBalanceFetcher(params = multiParams) }
coVerify { multiStakingBalanceFetcher(params = multiParams) }
}
private companion object {

View file

@ -4,12 +4,12 @@ import com.google.common.truth.Truth
import com.tangem.common.test.data.staking.MockYieldBalanceWrapperDTOFactory
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.data.staking.toDomain
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.models.staking.StakingID
import com.tangem.domain.models.staking.YieldBalance
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.staking.multi.MultiYieldBalanceProducer
import com.tangem.domain.staking.multi.MultiYieldBalanceSupplier
import com.tangem.domain.staking.single.SingleYieldBalanceProducer
import com.tangem.domain.staking.multi.MultiStakingBalanceProducer
import com.tangem.domain.staking.multi.MultiStakingBalanceSupplier
import com.tangem.domain.staking.single.SingleStakingBalanceProducer
import com.tangem.test.core.getEmittedValues
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.clearMocks
@ -26,20 +26,20 @@ import org.junit.jupiter.api.TestInstance
[REDACTED_AUTHOR]
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class DefaultSingleYieldBalanceProducerTest {
internal class DefaultSingleStakingBalanceProducerTest {
private val params = SingleYieldBalanceProducer.Params(
private val params = SingleStakingBalanceProducer.Params(
userWalletId = UserWalletId(stringValue = "011"),
stakingId = tonId,
)
private val multiNetworkStatusSupplier = mockk<MultiYieldBalanceSupplier>()
private val multiNetworkStatusSupplier = mockk<MultiStakingBalanceSupplier>()
private val analyticsExceptionHandler = mockk<AnalyticsExceptionHandler>(relaxUnitFun = true)
private val dispatchers = TestingCoroutineDispatcherProvider()
private val producer = DefaultSingleYieldBalanceProducer(
private val producer = DefaultSingleStakingBalanceProducer(
params = params,
multiYieldBalanceSupplier = multiNetworkStatusSupplier,
multiStakingBalanceSupplier = multiNetworkStatusSupplier,
analyticsExceptionHandler = analyticsExceptionHandler,
dispatchers = dispatchers,
)
@ -61,7 +61,7 @@ internal class DefaultSingleYieldBalanceProducerTest {
),
)
val multiParams = MultiYieldBalanceProducer.Params(userWalletId = params.userWalletId)
val multiParams = MultiStakingBalanceProducer.Params(userWalletId = params.userWalletId)
every { multiNetworkStatusSupplier(multiParams) } returns multiFlow
// Act
@ -74,17 +74,17 @@ internal class DefaultSingleYieldBalanceProducerTest {
}
@Test
fun `flow is updated if yield balance is updated`() = runTest {
fun `flow is updated if staking balance is updated`() = runTest {
// Arrange
val multiFlow = MutableSharedFlow<Set<YieldBalance>>(replay = 2, extraBufferCapacity = 1)
val multiFlow = MutableSharedFlow<Set<StakingBalance>>(replay = 2, extraBufferCapacity = 1)
val multiParams = MultiYieldBalanceProducer.Params(userWalletId = params.userWalletId)
val multiParams = MultiStakingBalanceProducer.Params(userWalletId = params.userWalletId)
every { multiNetworkStatusSupplier(multiParams) } returns multiFlow
val producerFlow = producer.produceWithFallback()
val balance = MockYieldBalanceWrapperDTOFactory.createWithBalance(tonId).toDomain()
val updatedBalance = YieldBalance.Error(stakingId = tonId)
val updatedBalance = StakingBalance.Error(stakingId = tonId)
// Act (first emit)
multiFlow.emit(value = setOf(balance))
@ -108,9 +108,9 @@ internal class DefaultSingleYieldBalanceProducerTest {
@Test
fun `flow is filtered the same status`() = runTest {
// Arrange
val multiFlow = MutableSharedFlow<Set<YieldBalance>>(replay = 2, extraBufferCapacity = 1)
val multiFlow = MutableSharedFlow<Set<StakingBalance>>(replay = 2, extraBufferCapacity = 1)
val multiParams = MultiYieldBalanceProducer.Params(userWalletId = params.userWalletId)
val multiParams = MultiStakingBalanceProducer.Params(userWalletId = params.userWalletId)
every { multiNetworkStatusSupplier(multiParams) } returns multiFlow
val producerFlow = producer.produceWithFallback()
@ -153,7 +153,7 @@ internal class DefaultSingleYieldBalanceProducerTest {
}
.buffer(capacity = 5)
val multiParams = MultiYieldBalanceProducer.Params(userWalletId = params.userWalletId)
val multiParams = MultiStakingBalanceProducer.Params(userWalletId = params.userWalletId)
every { multiNetworkStatusSupplier(multiParams) } returns multiFlow
val producerFlow = producer.produceWithFallback()
@ -162,7 +162,7 @@ internal class DefaultSingleYieldBalanceProducerTest {
val actual1 = getEmittedValues(flow = producerFlow)
// Assert (first emit)
val fallbackStatus = YieldBalance.Error(stakingId = tonId.copy(address = "0x1"))
val fallbackStatus = StakingBalance.Error(stakingId = tonId.copy(address = "0x1"))
Truth.assertThat(actual1).hasSize(1)
Truth.assertThat(actual1).containsExactly(fallbackStatus)
@ -184,7 +184,7 @@ internal class DefaultSingleYieldBalanceProducerTest {
val multiFlow = flowOf(setOf(balance))
val multiParams = MultiYieldBalanceProducer.Params(userWalletId = params.userWalletId)
val multiParams = MultiStakingBalanceProducer.Params(userWalletId = params.userWalletId)
every { multiNetworkStatusSupplier(multiParams) } returns multiFlow
val producerFlow = producer.produce()

View file

@ -5,7 +5,7 @@ import com.tangem.common.test.data.staking.MockYieldBalanceWrapperDTOFactory
import com.tangem.common.test.datastore.MockStateDataStore
import com.tangem.data.staking.toDomain
import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.domain.models.staking.YieldBalance
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.test.core.getEmittedValues
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
@ -15,12 +15,12 @@ import org.junit.Test
/**
[REDACTED_AUTHOR]
*/
internal class YieldsBalancesStoreGetMethodTest {
internal class StakingBalancesStoreGetMethodTest {
private val runtimeStore = RuntimeSharedStore<WalletIdWithBalances>()
private val runtimeStore = RuntimeSharedStore<WalletIdWithStakingBalances>()
private val persistenceStore = MockStateDataStore<WalletIdWithWrappers>(default = emptyMap())
private val store = DefaultYieldsBalancesStore(
private val store = DefaultStakingBalancesStore(
runtimeStore = runtimeStore,
persistenceStore = persistenceStore,
dispatchers = TestingCoroutineDispatcherProvider(),
@ -32,7 +32,7 @@ internal class YieldsBalancesStoreGetMethodTest {
val values = getEmittedValues(flow = actual)
val expected = listOf(emptySet<YieldBalance>())
val expected = listOf(emptySet<StakingBalance>())
Truth.assertThat(values).isEqualTo(expected)
}
@ -44,7 +44,7 @@ internal class YieldsBalancesStoreGetMethodTest {
val values = getEmittedValues(flow = actual)
val expected = listOf(emptySet<YieldBalance>())
val expected = listOf(emptySet<StakingBalance>())
Truth.assertThat(values).isEqualTo(expected)
}
@ -59,7 +59,7 @@ internal class YieldsBalancesStoreGetMethodTest {
val values = getEmittedValues(flow = actual)
Truth.assertThat(values.size).isEqualTo(1)
Truth.assertThat(values).isEqualTo(listOf(emptySet<YieldBalance>()))
Truth.assertThat(values).isEqualTo(listOf(emptySet<StakingBalance>()))
}
@Test

View file

@ -6,7 +6,7 @@ import com.tangem.common.test.data.staking.MockYieldBalanceWrapperDTOFactory
import com.tangem.common.test.datastore.MockStateDataStore
import com.tangem.data.staking.toDomain
import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.domain.models.staking.YieldBalance
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.every
@ -18,16 +18,16 @@ import org.junit.Test
/**
[REDACTED_AUTHOR]
*/
internal class YieldsBalancesStoreInitializationTest {
internal class StakingBalancesStoreInitializationTest {
@Test
fun `test initialization if cache store is empty`() = runTest {
val runtimeStore = RuntimeSharedStore<WalletIdWithBalances>()
val runtimeStore = RuntimeSharedStore<WalletIdWithStakingBalances>()
val persistenceStore: DataStore<WalletIdWithWrappers> = mockk()
every { persistenceStore.data } returns emptyFlow()
DefaultYieldsBalancesStore(
DefaultStakingBalancesStore(
runtimeStore = runtimeStore,
persistenceStore = persistenceStore,
dispatchers = TestingCoroutineDispatcherProvider(),
@ -38,21 +38,21 @@ internal class YieldsBalancesStoreInitializationTest {
@Test
fun `test initialization if cache store contains empty map`() = runTest {
val runtimeStore = RuntimeSharedStore<WalletIdWithBalances>()
val runtimeStore = RuntimeSharedStore<WalletIdWithStakingBalances>()
val persistenceStore = MockStateDataStore<WalletIdWithWrappers>(default = emptyMap())
DefaultYieldsBalancesStore(
DefaultStakingBalancesStore(
runtimeStore = runtimeStore,
persistenceStore = persistenceStore,
dispatchers = TestingCoroutineDispatcherProvider(),
)
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(emptyMap<String, Set<YieldBalance>>())
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(emptyMap<String, Set<StakingBalance>>())
}
@Test
fun `test initialization if cache store is not empty`() = runTest {
val runtimeStore = RuntimeSharedStore<WalletIdWithBalances>()
val runtimeStore = RuntimeSharedStore<WalletIdWithStakingBalances>()
val persistenceStore = MockStateDataStore<WalletIdWithWrappers>(default = emptyMap())
val wrapper = MockYieldBalanceWrapperDTOFactory.createWithBalance()
@ -63,7 +63,7 @@ internal class YieldsBalancesStoreInitializationTest {
}
}
DefaultYieldsBalancesStore(
DefaultStakingBalancesStore(
runtimeStore = runtimeStore,
persistenceStore = persistenceStore,
dispatchers = TestingCoroutineDispatcherProvider(),

View file

@ -7,8 +7,8 @@ import com.tangem.data.staking.toDomain
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.datasource.local.datastore.RuntimeSharedStore
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.staking.YieldBalance
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import kotlinx.coroutines.flow.firstOrNull
@ -18,12 +18,12 @@ import org.junit.Test
/**
[REDACTED_AUTHOR]
*/
internal class YieldsBalancesStoreUpdateMethodsTest {
internal class StakingBalancesStoreUpdateMethodsTest {
private val runtimeStore = RuntimeSharedStore<WalletIdWithBalances>()
private val runtimeStore = RuntimeSharedStore<WalletIdWithStakingBalances>()
private val persistenceStore = MockStateDataStore<WalletIdWithWrappers>(default = emptyMap())
private val store = DefaultYieldsBalancesStore(
private val store = DefaultStakingBalancesStore(
runtimeStore = runtimeStore,
persistenceStore = persistenceStore,
dispatchers = TestingCoroutineDispatcherProvider(),
@ -33,7 +33,7 @@ internal class YieldsBalancesStoreUpdateMethodsTest {
fun `refresh the single id if runtime store is empty`() = runTest {
store.refresh(userWalletId = userWalletId, stakingId = stakingId)
val runtimeExpected = mapOf(userWalletId to emptySet<YieldBalance>())
val runtimeExpected = mapOf(userWalletId to emptySet<StakingBalance>())
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap<String, Set<YieldBalanceWrapperDTO>>())
@ -63,7 +63,7 @@ internal class YieldsBalancesStoreUpdateMethodsTest {
fun `refresh the multi ids if runtime store is empty`() = runTest {
store.refresh(userWalletId = userWalletId, stakingIds = stakingIds)
val runtimeExpected = mapOf(userWalletId to emptySet<YieldBalance>())
val runtimeExpected = mapOf(userWalletId to emptySet<StakingBalance>())
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap<String, Set<YieldBalanceWrapperDTO>>())
@ -129,7 +129,7 @@ internal class YieldsBalancesStoreUpdateMethodsTest {
store.storeError(userWalletId = userWalletId, stakingIds = setOf(stakingId))
val runtimeExpected = mapOf(
userWalletId to setOf(YieldBalance.Error(stakingId)),
userWalletId to setOf(StakingBalance.Error(stakingId)),
)
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)

View file

@ -1,6 +1,6 @@
package com.tangem.data.staking.utils
import com.tangem.data.staking.store.YieldsBalancesStore
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.model.StakingIntegrationID
@ -16,9 +16,9 @@ import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DefaultStakingCleanerTest {
private val yieldsBalancesStore = mockk<YieldsBalancesStore>(relaxed = true)
private val stakingBalancesStore = mockk<StakingBalancesStore>(relaxed = true)
private val cleaner = DefaultStakingCleaner(
yieldsBalancesStore = yieldsBalancesStore,
stakingBalancesStore = stakingBalancesStore,
dispatchers = TestingCoroutineDispatcherProvider(),
)
private val userWalletId = UserWalletId("011")
@ -28,7 +28,7 @@ class DefaultStakingCleanerTest {
@BeforeEach
fun setUp() {
clearMocks(yieldsBalancesStore)
clearMocks(stakingBalancesStore)
}
@Test
@ -38,7 +38,7 @@ class DefaultStakingCleanerTest {
// Assert
coVerifyOrder {
yieldsBalancesStore.clear(userWalletId = userWalletId, stakingIds = stakingIds)
stakingBalancesStore.clear(userWalletId = userWalletId, stakingIds = stakingIds)
}
}
@ -49,7 +49,7 @@ class DefaultStakingCleanerTest {
// Assert
coVerifyOrder(inverse = true) {
yieldsBalancesStore.clear(userWalletId = any(), stakingIds = any())
stakingBalancesStore.clear(userWalletId = any(), stakingIds = any())
}
}
}