diff --git a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt index 790bb01a7a..2a6fb034b9 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt @@ -1,10 +1,6 @@ package com.tangem.tap.di.domain -import com.tangem.domain.settings.* -import com.tangem.domain.staking.GetStakingAvailabilityUseCase -import com.tangem.domain.staking.GetStakingEntryInfoUseCase -import com.tangem.domain.staking.FetchStakingTokensUseCase -import com.tangem.domain.staking.GetYieldUseCase +import com.tangem.domain.staking.* import com.tangem.domain.staking.repositories.StakingRepository import dagger.Module import dagger.Provides @@ -47,4 +43,16 @@ internal object StakingDomainModule { stakingRepository = stakingRepository, ) } + + @Provides + @Singleton + fun provideFetchStakingYieldBalanceUseCase(stakingRepository: StakingRepository): FetchStakingYieldBalanceUseCase { + return FetchStakingYieldBalanceUseCase(stakingRepository = stakingRepository) + } + + @Provides + @Singleton + fun provideGetStakingYieldBalanceUseCase(stakingRepository: StakingRepository): GetStakingYieldBalanceUseCase { + return GetStakingYieldBalanceUseCase(stakingRepository = stakingRepository) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/UnknownEnumMoshiAdapter.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/UnknownEnumMoshiAdapter.kt index 2d46248977..2e6adf8fe7 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/UnknownEnumMoshiAdapter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/UnknownEnumMoshiAdapter.kt @@ -1,8 +1,11 @@ package com.tangem.datasource.api.common.adapter -import com.squareup.moshi.* +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi import com.squareup.moshi.adapters.EnumJsonAdapter -import com.tangem.datasource.api.stakekit.models.response.model.* +import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO +import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO +import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionTypeDTO import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionStatusDTO @@ -24,8 +27,7 @@ fun Moshi.Builder.addStakeKitEnumFallbackAdapters(): Moshi.Builder { NetworkTypeDTO::class.java to NetworkTypeDTO.UNKNOWN, StakingActionTypeDTO::class.java to StakingActionTypeDTO.UNKNOWN, YieldDTO.RewardTypeDTO::class.java to YieldDTO.RewardTypeDTO.UNKNOWN, - YieldBalanceWrapperDTO.BalanceDTO.BalanceType::class.java to - YieldBalanceWrapperDTO.BalanceDTO.BalanceType.UNKNOWN, + BalanceDTO.BalanceType::class.java to BalanceDTO.BalanceType.UNKNOWN, StakingTransactionTypeDTO::class.java to StakingTransactionTypeDTO.UNKNOWN, StakingTransactionStatusDTO::class.java to StakingTransactionStatusDTO.UNKNOWN, StakingActionStatusDTO::class.java to StakingActionStatusDTO.UNKNOWN, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt index 9818ec8742..17e656903f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt @@ -4,6 +4,7 @@ import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.stakekit.models.request.* import com.tangem.datasource.api.stakekit.models.response.EnabledYieldsResponse import com.tangem.datasource.api.stakekit.models.response.EnterActionResponse +import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionDTO import com.tangem.datasource.api.stakekit.models.response.model.TokenWithYieldDTO import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO @@ -29,16 +30,16 @@ interface StakeKitApi { @Query("ledgerWalletAPICompatible") ledgerWalletAPICompatible: Boolean = false, ): ApiResponse - @GET("yields/balances") + @POST("yields/balances") suspend fun getMultipleYieldBalances( @Body body: List, ): ApiResponse> - @GET("yields/{integrationId}/balances") + @POST("yields/{integrationId}/balances") suspend fun getSingleYieldBalance( @Path("integrationId") integrationId: String, @Body body: YieldBalanceRequestBody, - ): ApiResponse + ): ApiResponse> @GET("tokens") suspend fun getTokens(): ApiResponse> diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/request/EnterActionRequestBody.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/request/EnterActionRequestBody.kt index 677ff1a33f..6d06f46f5c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/request/EnterActionRequestBody.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/request/EnterActionRequestBody.kt @@ -1,8 +1,8 @@ package com.tangem.datasource.api.stakekit.models.request import com.squareup.moshi.Json +import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO -import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO data class EnterActionRequestBody( @Json(name = "integrationId") @@ -27,7 +27,7 @@ data class EnterActionRequestBody( @Json(name = "duration") val duration: String? = null, @Json(name = "nfts") - val nfts: List? = null, + val nfts: List? = null, @Json(name = "ledgerWalletAPICompatible") val ledgerWalletAPICompatible: Boolean? = null, @Json(name = "tronResource") diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldBalanceWrapperDTO.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldBalanceWrapperDTO.kt index 2fc44ede6e..5be46fc967 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldBalanceWrapperDTO.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldBalanceWrapperDTO.kt @@ -12,130 +12,129 @@ data class YieldBalanceWrapperDTO( val balances: List, @Json(name = "integrationId") val integrationId: String?, +) + +@JsonClass(generateAdapter = true) +data class BalanceDTO( + @Json(name = "groupId") + val groupId: String, + @Json(name = "type") + val type: BalanceType, + @Json(name = "amount") + val amount: BigDecimal, + @Json(name = "date") + val date: DateTime?, + @Json(name = "pricePerShare") + val pricePerShare: BigDecimal, + @Json(name = "pendingActions") + val pendingActions: List, + @Json(name = "token") + val tokenDTO: TokenDTO, + @Json(name = "validatorAddress") + val validatorAddress: String?, + @Json(name = "validatorAddresses") + val validatorAddresses: List?, + @Json(name = "providerId") + val providerId: String?, ) { + enum class BalanceType { + @Json(name = "available") + AVAILABLE, + + @Json(name = "staked") + STAKED, + + @Json(name = "unstaking") + UNSTAKING, + + @Json(name = "unstaked") + UNSTAKED, + + @Json(name = "preparing") + PREPARING, + + @Json(name = "rewards") + REWARDS, + + @Json(name = "locked") + LOCKED, + + @Json(name = "unlocking") + UNLOCKING, + + UNKNOWN, + } + @JsonClass(generateAdapter = true) - data class BalanceDTO( - @Json(name = "groupId") - val groupId: String, + data class PendingAction( @Json(name = "type") - val type: BalanceType, - @Json(name = "amount") - val amount: BigDecimal, - @Json(name = "date") - val date: DateTime?, - @Json(name = "pricePerShare") - val pricePerShare: BigDecimal, - @Json(name = "pendingActions") - val pendingActions: List, - @Json(name = "token") - val tokenDTO: TokenDTO, - @Json(name = "validatorAddress") - val validatorAddress: String?, - @Json(name = "validatorAddresses") - val validatorAddresses: List?, - @Json(name = "providerId") - val providerId: String?, + val type: StakingActionTypeDTO, + @Json(name = "passthrough") + val passthrough: String, + @Json(name = "args") + val args: PendingActionArgs?, ) { - - enum class BalanceType { - @Json(name = "available") - AVAILABLE, - - @Json(name = "staked") - STAKED, - - @Json(name = "unstaking") - UNSTAKING, - - @Json(name = "unstaked") - UNSTAKED, - - @Json(name = "preparing") - PREPARING, - - @Json(name = "rewards") - REWARDS, - - @Json(name = "locked") - LOCKED, - - @Json(name = "unlocking") - UNLOCKING, - - UNKNOWN, - } - @JsonClass(generateAdapter = true) - data class PendingAction( - @Json(name = "type") - val type: StakingActionTypeDTO, - @Json(name = "passthrough") - val passthrough: String, - @Json(name = "args") - val args: PendingActionArgs?, + data class PendingActionArgs( + @Json(name = "amount") + val amount: Amount?, + @Json(name = "duration") + val duration: Duration?, + @Json(name = "validatorAddress") + val validatorAddress: Required?, + @Json(name = "validatorAddresses") + val validatorAddresses: Required?, + @Json(name = "nfts") + val nfts: List?, + @Json(name = "tronResource") + val tronResource: TronResource?, + @Json(name = "signatureVerification") + val signatureVerification: Required?, ) { @JsonClass(generateAdapter = true) - data class PendingActionArgs( - @Json(name = "amount") - val amount: Amount?, - @Json(name = "duration") - val duration: Duration?, - @Json(name = "validatorAddress") - val validatorAddress: Required?, - @Json(name = "validatorAddresses") - val validatorAddresses: Required?, - @Json(name = "nfts") - val nfts: List?, - @Json(name = "tronResource") - val tronResource: TronResource?, - @Json(name = "signatureVerification") - val signatureVerification: Required?, - ) { - @JsonClass(generateAdapter = true) - data class Amount( - @Json(name = "required") - val required: Boolean, - @Json(name = "minimum") - val minimum: BigDecimal?, - @Json(name = "maximum") - val maximum: BigDecimal?, - ) + data class Amount( + @Json(name = "required") + val required: Boolean, + @Json(name = "minimum") + val minimum: BigDecimal?, + @Json(name = "maximum") + val maximum: BigDecimal?, + ) - @JsonClass(generateAdapter = true) - data class Duration( - @Json(name = "required") - val required: Boolean, - @Json(name = "minimum") - val minimum: Int?, - @Json(name = "maximum") - val maximum: Int?, - ) + @JsonClass(generateAdapter = true) + data class Duration( + @Json(name = "required") + val required: Boolean, + @Json(name = "minimum") + val minimum: Int?, + @Json(name = "maximum") + val maximum: Int?, + ) - @JsonClass(generateAdapter = true) - data class Nft( - @Json(name = "baycId") - val baycId: Required?, - @Json(name = "maycId") - val maycId: Required?, - @Json(name = "bakcId") - val bakcId: Required?, - ) + @JsonClass(generateAdapter = true) + data class Nft( + @Json(name = "baycId") + val baycId: Required?, + @Json(name = "maycId") + val maycId: Required?, + @Json(name = "bakcId") + val bakcId: Required?, + ) - @JsonClass(generateAdapter = true) - data class TronResource( - @Json(name = "required") - val required: Boolean, - @Json(name = "options") - val options: List, - ) - } + @JsonClass(generateAdapter = true) + data class TronResource( + @Json(name = "required") + val required: Boolean, + @Json(name = "options") + val options: List, + ) } - - @JsonClass(generateAdapter = true) - data class Required( - @Json(name = "required") - val required: Boolean, - ) } + + @JsonClass(generateAdapter = true) + data class Required( + @Json(name = "required") + val required: Boolean, + ) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/StakingBalanceStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/StakingBalanceStoreModule.kt new file mode 100644 index 0000000000..21d78318c1 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/StakingBalanceStoreModule.kt @@ -0,0 +1,21 @@ +package com.tangem.datasource.di + +import com.tangem.datasource.local.datastore.RuntimeDataStore +import com.tangem.datasource.local.token.DefaultStakingBalanceStore +import com.tangem.datasource.local.token.StakingBalanceStore +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object StakingBalanceStoreModule { + + @Provides + @Singleton + fun provideStakingBalanceStore(): StakingBalanceStore { + return DefaultStakingBalanceStore(dataStore = RuntimeDataStore()) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt new file mode 100644 index 0000000000..c88970b238 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt @@ -0,0 +1,51 @@ +package com.tangem.datasource.local.token + +import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO +import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO +import com.tangem.datasource.local.datastore.core.StringKeyDataStore +import com.tangem.utils.extensions.addOrReplace +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +internal class DefaultStakingBalanceStore( + private val dataStore: StringKeyDataStore>, +) : StakingBalanceStore { + + override fun get(): Flow> { + return dataStore.get(STAKING_BALANCE_KEY) + } + + override suspend fun getSyncOrNull(): List? { + return dataStore.getSyncOrNull(STAKING_BALANCE_KEY) + } + + override suspend fun store(items: List) { + return dataStore.store(STAKING_BALANCE_KEY, items) + } + + override fun get(integrationId: String): Flow> { + return dataStore.get(STAKING_BALANCE_KEY) + .map { balances -> + balances.filter { it.integrationId == integrationId } + .flatMap { it.balances } + } + } + + override suspend fun getSyncOrNull(integrationId: String): List? { + return dataStore.getSyncOrNull(STAKING_BALANCE_KEY) + ?.firstOrNull { it.integrationId == integrationId }?.balances + } + + override suspend fun store(integrationId: String, item: YieldBalanceWrapperDTO) { + val balances = dataStore.getSyncOrNull(STAKING_BALANCE_KEY) + ?.toMutableList() + ?.addOrReplace(item) { item.integrationId == integrationId } + ?: listOf(item) + + return dataStore.store(STAKING_BALANCE_KEY, balances) + } + + companion object { + private const val STAKING_BALANCE_KEY = "STAKING_BALANCE_KEY" + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingBalanceStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingBalanceStore.kt new file mode 100644 index 0000000000..0a9ea06c9e --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingBalanceStore.kt @@ -0,0 +1,20 @@ +package com.tangem.datasource.local.token + +import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO +import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO +import kotlinx.coroutines.flow.Flow + +interface StakingBalanceStore { + + fun get(): Flow> + + suspend fun getSyncOrNull(): List? + + suspend fun store(items: List) + + fun get(integrationId: String): Flow> + + suspend fun getSyncOrNull(integrationId: String): List? + + suspend fun store(integrationId: String, item: YieldBalanceWrapperDTO) +} \ No newline at end of file diff --git a/data/staking/build.gradle.kts b/data/staking/build.gradle.kts index 186d53da6e..e0b3458636 100644 --- a/data/staking/build.gradle.kts +++ b/data/staking/build.gradle.kts @@ -12,10 +12,17 @@ android { dependencies { + /** Core modules */ implementation(projects.core.datasource) implementation(projects.core.utils) + + /** Common modules */ + implementation(projects.data.common) + + /** Domain modules */ implementation(projects.domain.tokens.models) implementation(projects.domain.staking) + implementation(projects.domain.wallets.models) // region DI diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index f0e5a33941..379877723a 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -2,23 +2,33 @@ package com.tangem.data.staking import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.toCoinId -import com.tangem.data.staking.converters.StakingNetworkTypeConverter -import com.tangem.data.staking.converters.TokenConverter -import com.tangem.data.staking.converters.YieldConverter +import com.tangem.data.common.cache.CacheRegistry +import com.tangem.data.staking.converters.* import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.stakekit.StakeKitApi +import com.tangem.datasource.api.stakekit.models.request.Address +import com.tangem.datasource.api.stakekit.models.request.YieldBalanceRequestBody +import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO +import com.tangem.datasource.local.token.StakingBalanceStore import com.tangem.datasource.local.token.StakingYieldsStore -import com.tangem.domain.staking.model.StakingAvailability -import com.tangem.domain.staking.model.StakingEntryInfo -import com.tangem.domain.staking.model.Yield +import com.tangem.domain.staking.model.* import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.cancellable +import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext internal class DefaultStakingRepository( private val stakeKitApi: StakeKitApi, private val stakingYieldsStore: StakingYieldsStore, + private val stakingBalanceStore: StakingBalanceStore, + private val cacheRegistry: CacheRegistry, private val dispatchers: CoroutineDispatcherProvider, ) : StakingRepository { @@ -31,6 +41,10 @@ internal class DefaultStakingRepository( tokenConverter = tokenConverter, ) + private val yieldBalanceConverter = YieldBalanceConverter() + + private val yieldBalanceListConverter = YieldBalanceListConverter() + override fun isStakingSupported(currencyId: String): Boolean { return integrationIds.contains(currencyId) } @@ -94,9 +108,93 @@ internal class DefaultStakingRepository( } } + override suspend fun fetchSingleYieldBalance( + userWalletId: UserWalletId, + networkStatus: NetworkStatus, + integrationId: String, + refresh: Boolean, + ) = withContext(dispatchers.io) { + cacheRegistry.invokeOnExpire( + key = getYieldBalancesKey(userWalletId), + skipCache = refresh, + block = { + val result = stakeKitApi.getSingleYieldBalance( + integrationId = integrationId, + body = getBalanceRequestData(networkStatus), + ).getOrThrow() + + stakingBalanceStore.store( + integrationId, + YieldBalanceWrapperDTO( + balances = result, + integrationId = integrationId, + ), + ) + }, + ) + } + + override fun getSingleYieldBalanceFlow( + userWalletId: UserWalletId, + networkStatus: NetworkStatus, + integrationId: String, + ): Flow> = channelFlow { + launch(dispatchers.io) { + stakingBalanceStore.get(integrationId) + .collectLatest { + send(yieldBalanceConverter.convertList(it)) + } + } + + withContext(dispatchers.io) { + fetchSingleYieldBalance( + userWalletId, + networkStatus, + integrationId, + ) + } + }.cancellable() + + override suspend fun fetchMultiYieldBalance( + userWalletId: UserWalletId, + networks: Set, + integrationId: String, + refresh: Boolean, + ) = withContext(dispatchers.io) { + cacheRegistry.invokeOnExpire( + key = getYieldBalancesKey(userWalletId), + skipCache = refresh, + block = { + val result = stakeKitApi.getMultipleYieldBalances( + networks.map(::getBalanceRequestData), + ).getOrThrow() + + stakingBalanceStore.store(result) + }, + ) + } + + override fun getMultiYieldBalanceFlow( + userWalletId: UserWalletId, + networks: Set, + integrationId: String, + ): Flow> = channelFlow { + launch(dispatchers.io) { + stakingBalanceStore.get() + .collectLatest { send(yieldBalanceListConverter.convertList(it)) } + } + + withContext(dispatchers.io) { + fetchMultiYieldBalance( + userWalletId, + networks, + integrationId, + ) + } + }.cancellable() + private fun findPrefetchedYield(yields: List, currencyId: String, symbol: String): Yield? { - return yields - .find { it.token.coinGeckoId == currencyId && it.token.symbol == symbol } + return yields.find { it.token.coinGeckoId == currencyId && it.token.symbol == symbol } } private suspend fun getEnabledYields(): List? { @@ -104,6 +202,28 @@ internal class DefaultStakingRepository( return yields.map { yieldConverter.convert(it) } } + private fun getBalanceRequestData(networkStatus: NetworkStatus): YieldBalanceRequestBody { + val networkAddress = when (val network = networkStatus.value) { + NetworkStatus.MissedDerivation -> null + is NetworkStatus.NoAccount -> network.address + is NetworkStatus.Unreachable -> network.address + is NetworkStatus.Verified -> network.address + } + val address = networkAddress?.defaultAddress?.value.orEmpty() + return YieldBalanceRequestBody( + addresses = Address( + address = address, + additionalAddresses = null, // todo fill additional addresses metadata if needed + explorerUrl = "", // todo fill exporer url + ), + args = YieldBalanceRequestBody.YieldBalanceRequestArgs( + validatorAddresses = listOf(), // todo add validator addresses + ), + ) + } + + private fun getYieldBalancesKey(userWalletId: UserWalletId) = "yield_balance_${userWalletId.stringValue}" + companion object { private val integrationIds = setOf( Blockchain.Solana.toCoinId(), diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceConverter.kt new file mode 100644 index 0000000000..7a3182cc87 --- /dev/null +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceConverter.kt @@ -0,0 +1,16 @@ +package com.tangem.data.staking.converters + +import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO +import com.tangem.domain.staking.model.BalanceType +import com.tangem.domain.staking.model.YieldBalance +import com.tangem.utils.converter.Converter + +internal class YieldBalanceConverter : Converter { + override fun convert(value: BalanceDTO): YieldBalance { + return YieldBalance( + type = BalanceType.valueOf(value.type.name), + amount = value.amount, + pricePerShare = value.pricePerShare, + ) + } +} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceListConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceListConverter.kt new file mode 100644 index 0000000000..d0c4eea878 --- /dev/null +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceListConverter.kt @@ -0,0 +1,18 @@ +package com.tangem.data.staking.converters + +import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO +import com.tangem.domain.staking.model.YieldBalanceList +import com.tangem.utils.converter.Converter + +internal class YieldBalanceListConverter : Converter { + + internal val converter by lazy(LazyThreadSafetyMode.NONE) { + YieldBalanceConverter() + } + + override fun convert(value: YieldBalanceWrapperDTO): YieldBalanceList { + return YieldBalanceList( + balances = converter.convertList(value.balances), + ) + } +} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt index 0b47b925d1..16c1bed364 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt @@ -1,7 +1,9 @@ package com.tangem.data.staking.di +import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.staking.DefaultStakingRepository import com.tangem.datasource.api.stakekit.StakeKitApi +import com.tangem.datasource.local.token.StakingBalanceStore import com.tangem.datasource.local.token.StakingYieldsStore import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -20,12 +22,16 @@ internal object StakingDataModule { fun provideStakingRepository( stakeKitApi: StakeKitApi, stakingTokenStore: StakingYieldsStore, + stakingBalanceStore: StakingBalanceStore, dispatchers: CoroutineDispatcherProvider, + cacheRegistry: CacheRegistry, ): StakingRepository { return DefaultStakingRepository( stakeKitApi = stakeKitApi, stakingYieldsStore = stakingTokenStore, + stakingBalanceStore = stakingBalanceStore, dispatchers = dispatchers, + cacheRegistry = cacheRegistry, ) } } \ No newline at end of file diff --git a/domain/staking/build.gradle.kts b/domain/staking/build.gradle.kts index fc6ced152c..38ec70430c 100644 --- a/domain/staking/build.gradle.kts +++ b/domain/staking/build.gradle.kts @@ -17,4 +17,5 @@ dependencies { implementation(deps.kotlin.serialization) implementation(projects.domain.tokens.models) + implementation(projects.domain.wallets.models) } \ No newline at end of file diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/YieldBalance.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/YieldBalance.kt new file mode 100644 index 0000000000..817743ac7c --- /dev/null +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/YieldBalance.kt @@ -0,0 +1,21 @@ +package com.tangem.domain.staking.model + +import java.math.BigDecimal + +data class YieldBalance( + val type: BalanceType, + val amount: BigDecimal, + val pricePerShare: BigDecimal, +) + +enum class BalanceType { + AVAILABLE, + STAKED, + UNSTAKING, + UNSTAKED, + PREPARING, + REWARDS, + LOCKED, + UNLOCKING, + UNKNOWN, +} \ No newline at end of file diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/YieldBalanceList.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/YieldBalanceList.kt new file mode 100644 index 0000000000..931e8fa879 --- /dev/null +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/YieldBalanceList.kt @@ -0,0 +1,5 @@ +package com.tangem.domain.staking.model + +data class YieldBalanceList( + val balances: List, +) \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingYieldBalanceUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingYieldBalanceUseCase.kt new file mode 100644 index 0000000000..46f0fdf101 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingYieldBalanceUseCase.kt @@ -0,0 +1,25 @@ +package com.tangem.domain.staking + +import arrow.core.Either +import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.wallets.models.UserWalletId + +class FetchStakingYieldBalanceUseCase( + private val stakingRepository: StakingRepository, +) { + + suspend operator fun invoke( + userWalletId: UserWalletId, + networkStatus: NetworkStatus, + integrationId: String, + refresh: Boolean = false, + ): Either = Either.catch { + stakingRepository.fetchSingleYieldBalance( + userWalletId = userWalletId, + networkStatus = networkStatus, + integrationId = integrationId, + refresh = refresh, + ) + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingYieldBalanceUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingYieldBalanceUseCase.kt new file mode 100644 index 0000000000..8821767a15 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingYieldBalanceUseCase.kt @@ -0,0 +1,30 @@ +package com.tangem.domain.staking + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.domain.core.utils.EitherFlow +import com.tangem.domain.staking.model.YieldBalance +import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map + +class GetStakingYieldBalanceUseCase( + private val stakingRepository: StakingRepository, +) { + + operator fun invoke( + userWalletId: UserWalletId, + networkStatus: NetworkStatus, + integrationId: String, + ): EitherFlow> { + return stakingRepository.getSingleYieldBalanceFlow( + userWalletId = userWalletId, + networkStatus = networkStatus, + integrationId = integrationId, + ).map, Either>> { it.right() } + .catch { emit(it.left()) } + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt index 2e1fe82f5d..5eb34854c5 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt @@ -1,9 +1,10 @@ package com.tangem.domain.staking.repositories -import com.tangem.domain.staking.model.StakingAvailability -import com.tangem.domain.staking.model.StakingEntryInfo -import com.tangem.domain.staking.model.Yield +import com.tangem.domain.staking.model.* import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow interface StakingRepository { @@ -19,4 +20,30 @@ interface StakingRepository { cryptoCurrencyId: CryptoCurrency.ID, symbol: String, ): StakingAvailability + + suspend fun fetchSingleYieldBalance( + userWalletId: UserWalletId, + networkStatus: NetworkStatus, + integrationId: String, + refresh: Boolean = false, + ) + + fun getSingleYieldBalanceFlow( + userWalletId: UserWalletId, + networkStatus: NetworkStatus, + integrationId: String, + ): Flow> + + suspend fun fetchMultiYieldBalance( + userWalletId: UserWalletId, + networks: Set, + integrationId: String, + refresh: Boolean = false, + ) + + fun getMultiYieldBalanceFlow( + userWalletId: UserWalletId, + networks: Set, + integrationId: String, + ): Flow> } \ No newline at end of file