Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-03 11:52:19 +01:00
parent c7941ee1f7
commit 2b327fd3e9
32 changed files with 853 additions and 0 deletions

1
domain/earn/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,14 @@
plugins {
alias(deps.plugins.kotlin.jvm)
alias(deps.plugins.kotlin.serialization)
id("configuration")
}
dependencies {
api(projects.domain.core)
api(projects.domain.models)
api(projects.core.pagination)
implementation(projects.domain.common)
implementation(projects.domain.networks)
implementation(deps.kotlin.serialization)
}

View file

@ -0,0 +1,8 @@
package com.tangem.domain.earn
import com.tangem.domain.models.earn.EarnError
interface EarnErrorResolver {
fun resolve(throwable: Throwable?): EarnError
}

View file

@ -0,0 +1,17 @@
package com.tangem.domain.earn.model
import kotlinx.serialization.Serializable
/**
* Config of request earn tokens.
*
* @param type of Earn (ex: yield, staking).
* @param network networkId.
* @param isForEarn flag to load mostly used tokens.
*/
@Serializable
data class EarnTokensListConfig(
val type: String,
val network: String,
val isForEarn: Boolean,
)

View file

@ -0,0 +1,9 @@
package com.tangem.domain.earn.model
import com.tangem.domain.models.earn.EarnTokenWithCurrency
import com.tangem.pagination.BatchFlow
import com.tangem.pagination.BatchingContext
typealias EarnTokensBatchingContext = BatchingContext<Int, EarnTokensListConfig, Nothing>
typealias EarnTokensBatchFlow = BatchFlow<Int, List<EarnTokenWithCurrency>, Nothing>

View file

@ -0,0 +1,26 @@
package com.tangem.domain.earn.repository
import com.tangem.domain.earn.model.EarnTokensBatchFlow
import com.tangem.domain.earn.model.EarnTokensBatchingContext
import com.tangem.domain.models.earn.EarnNetworks
import com.tangem.domain.models.earn.EarnTopToken
import kotlinx.coroutines.flow.Flow
interface EarnRepository {
fun getEarnTokensBatchFlow(context: EarnTokensBatchingContext, batchSize: Int): EarnTokensBatchFlow
/**
* Load all networks for Earn and store it in data store.
*/
suspend fun fetchEarnNetworks(type: String)
fun observeEarnNetworks(): Flow<EarnNetworks>
/**
* Fetch top N earn tokens by isForEarn = true and hold it in runtime store.
*/
suspend fun fetchTopEarnTokens(limit: Int)
fun observeTopEarnTokens(): Flow<EarnTopToken>
}

View file

@ -0,0 +1,15 @@
package com.tangem.domain.earn.usecase
import arrow.core.Either
import com.tangem.domain.earn.repository.EarnRepository
class FetchEarnNetworksUseCase(
private val repository: EarnRepository,
) {
suspend operator fun invoke(type: String): Either<Throwable, Unit> {
return Either.catch {
repository.fetchEarnNetworks(type = type)
}
}
}

View file

@ -0,0 +1,21 @@
package com.tangem.domain.earn.usecase
import arrow.core.Either
import com.tangem.domain.earn.repository.EarnRepository
class FetchTopEarnTokensUseCase(
private val repository: EarnRepository,
) {
suspend operator fun invoke(limit: Int = DEFAULT_LIMIT): Either<Throwable, Unit> {
return Either.catch {
repository.fetchTopEarnTokens(
limit = limit,
)
}
}
private companion object {
private const val DEFAULT_LIMIT = 5
}
}

View file

@ -0,0 +1,58 @@
package com.tangem.domain.earn.usecase
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.earn.repository.EarnRepository
import com.tangem.domain.models.earn.EarnNetwork
import com.tangem.domain.models.earn.EarnNetworks
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.networks.multi.MultiNetworkStatusProducer
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
/**
* Observes earn networks with [EarnNetwork.isAdded] enriched from user's wallets
* via [multiNetworkStatusSupplier]. Single entry point for all/mine filtering.
*/
class GetEarnNetworksUseCase(
private val earnRepository: EarnRepository,
private val userWalletsListRepository: UserWalletsListRepository,
private val multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
) {
operator fun invoke(): Flow<EarnNetworks> {
return combine(
earnRepository.observeEarnNetworks(),
observeMyNetworkIds(),
) { earn, myNetworkIds ->
earn.map { earnNetworks ->
earnNetworks.map { network ->
network.copy(isAdded = network.networkId in myNetworkIds)
}
}
}.distinctUntilChanged()
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun observeMyNetworkIds(): Flow<Set<String>> {
return userWalletsListRepository.userWallets
.map { it.orEmpty() }
.flatMapLatest { wallets ->
val activeWallets = wallets
.filterNot(UserWallet::isLocked)
.filter(UserWallet::isMultiCurrency)
if (activeWallets.isEmpty()) {
flowOf(emptySet())
} else {
val flows = activeWallets.map { wallet ->
multiNetworkStatusSupplier(
MultiNetworkStatusProducer.Params(userWalletId = wallet.walletId),
).map { statuses -> statuses.map { it.network.backendId }.toSet() }
}
combine(flows) { arrays -> arrays.flatMap { it }.toSet() }
}
}
}
}

View file

@ -0,0 +1,24 @@
package com.tangem.domain.earn.usecase
import com.tangem.domain.earn.model.EarnTokensBatchFlow
import com.tangem.domain.earn.model.EarnTokensBatchingContext
import com.tangem.domain.earn.repository.EarnRepository
/**
* Returns BatchFlow of Earn-tokens.
*/
class GetEarnTokensBatchFlowUseCase(
private val repository: EarnRepository,
) {
operator fun invoke(context: EarnTokensBatchingContext, batchSize: Int = DEFAULT_BATCH_SIZE): EarnTokensBatchFlow {
return repository.getEarnTokensBatchFlow(
context = context,
batchSize = batchSize,
)
}
private companion object {
private const val DEFAULT_BATCH_SIZE = 20
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.domain.earn.usecase
import com.tangem.domain.earn.repository.EarnRepository
import com.tangem.domain.models.earn.EarnTopToken
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
class GetTopEarnTokensUseCase(
private val repository: EarnRepository,
) {
operator fun invoke(): Flow<EarnTopToken> {
return repository.observeTopEarnTokens().distinctUntilChanged()
}
}