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 0280493a9a..fcf1233a1b 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 @@ -100,12 +100,12 @@ internal object StakingDomainModule { @Singleton fun provideFetchStakingOptionsUseCase( stakeKitRepository: StakeKitRepository, - p2pRepository: P2PEthPoolRepository, + p2pEthPoolRepository: P2PEthPoolRepository, stakingErrorResolver: StakingErrorResolver, ): FetchStakingOptionsUseCase { return FetchStakingOptionsUseCase( stakeKitRepository = stakeKitRepository, - p2pRepository = p2pRepository, + p2pEthPoolRepository = p2pEthPoolRepository, stakingErrorResolver = stakingErrorResolver, ) } @@ -240,8 +240,9 @@ internal object StakingDomainModule { @Singleton fun provideStakingApyFlowUseCase( stakeKitRepository: StakeKitRepository, + p2pEthPoolRepository: P2PEthPoolRepository, stakingFeatureToggles: StakingFeatureToggles, ): StakingApyFlowUseCase { - return StakingApyFlowUseCase(stakeKitRepository, stakingFeatureToggles) + return StakingApyFlowUseCase(stakeKitRepository, p2pEthPoolRepository, stakingFeatureToggles) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/auth/DefaultP2PEthPoolAuthProvider.kt b/app/src/main/java/com/tangem/tap/network/auth/DefaultP2PEthPoolAuthProvider.kt index 5f54236082..93b0595647 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/DefaultP2PEthPoolAuthProvider.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/DefaultP2PEthPoolAuthProvider.kt @@ -1,7 +1,7 @@ package com.tangem.tap.network.auth import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage -import com.tangem.domain.staking.model.ethpool.P2PStakingConfig +import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig import com.tangem.lib.auth.P2PEthPoolAuthProvider internal class DefaultP2PEthPoolAuthProvider( @@ -12,6 +12,6 @@ internal class DefaultP2PEthPoolAuthProvider( val keys = environmentConfigStorage.getConfigSync().p2pApiKey ?: error("No P2P api keys provided") - return if (P2PStakingConfig.USE_TESTNET) keys.hoodi else keys.mainnet + return if (P2PEthPoolStakingConfig.USE_TESTNET) keys.hoodi else keys.mainnet } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 715bf51463..99c8e414b9 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -311,7 +311,7 @@ internal class ChildFactory @Inject constructor( params = StakingComponent.Params( userWalletId = route.userWalletId, cryptoCurrency = route.cryptoCurrency, - yieldId = route.yieldId, + integrationId = route.integrationId, ), componentFactory = stakingComponentFactory, ) diff --git a/common/routing/build.gradle.kts b/common/routing/build.gradle.kts index 9fe3f2a5e4..b34c827c74 100644 --- a/common/routing/build.gradle.kts +++ b/common/routing/build.gradle.kts @@ -19,7 +19,7 @@ dependencies { implementation(projects.domain.models) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) - implementation(projects.domain.staking.models) + implementation(projects.domain.staking) implementation(projects.domain.markets.models) implementation(projects.domain.onramp.models) implementation(projects.domain.appCurrency.models) diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index c2d8b480d7..0c771f5609 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -7,6 +7,7 @@ import android.os.Bundle import com.tangem.common.routing.bundle.RouteBundleParams import com.tangem.common.routing.bundle.bundle import com.tangem.common.routing.entity.InitScreenLaunchMode +import com.tangem.domain.staking.model.StakingIntegrationID import com.tangem.core.decompose.navigation.Route import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.feedback.models.WalletMetaInfo @@ -208,8 +209,8 @@ sealed class AppRoute(val path: String) : Route { data class Staking( val userWalletId: UserWalletId, val cryptoCurrency: CryptoCurrency, - val yieldId: String, - ) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrency.id.value}/$yieldId") + val integrationId: StakingIntegrationID, + ) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrency.id.value}/${integrationId.value}") @Serializable data class PushNotification( diff --git a/common/test/src/main/java/com/tangem/common/test/data/staking/MockP2PEthPoolAccountResponseFactory.kt b/common/test/src/main/java/com/tangem/common/test/data/staking/MockP2PEthPoolAccountResponseFactory.kt index 1e814c5d9f..2eafba7703 100644 --- a/common/test/src/main/java/com/tangem/common/test/data/staking/MockP2PEthPoolAccountResponseFactory.kt +++ b/common/test/src/main/java/com/tangem/common/test/data/staking/MockP2PEthPoolAccountResponseFactory.kt @@ -8,7 +8,7 @@ import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault import java.math.BigDecimal /** - * Factory for creating mock P2P ETH Pool account responses for testing + * Factory for creating mock P2PEthPool account responses for testing */ object MockP2PEthPoolAccountResponseFactory { diff --git a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt index 8408463879..f572961610 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt @@ -22,8 +22,10 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.yieldSupplyKey import com.tangem.domain.models.staking.StakingBalance +import com.tangem.domain.staking.model.StakingTarget import com.tangem.domain.staking.model.isStakingSupported -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.common.RewardInfo +import com.tangem.domain.staking.model.common.RewardType import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.StringsSigns.DASH_SIGN @@ -44,7 +46,7 @@ import java.math.BigDecimal class TokenItemStateConverter( private val appCurrency: AppCurrency, private val yieldModuleApyMap: Map = emptyMap(), - private val stakingApyMap: Map> = emptyMap(), + private val stakingApyMap: Map> = emptyMap(), private val yieldSupplyPromoBannerKey: String? = null, private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = { CryptoCurrencyToIconStateConverter().convert(it) @@ -177,7 +179,7 @@ class TokenItemStateConverter( private fun createTitleState( currencyStatus: CryptoCurrencyStatus, yieldModuleApyMap: Map, - stakingApyMap: Map>, + stakingApyMap: Map>, onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)?, ): TokenItemState.TitleState { return when (val value = currencyStatus.value) { @@ -217,7 +219,7 @@ class TokenItemStateConverter( private fun resolveEarnApy( cryptoCurrencyStatus: CryptoCurrencyStatus, yieldModuleApyMap: Map, - stakingApyMap: Map>, + stakingApyMap: Map>, ): EarnApyInfo? { val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token if (token != null && yieldModuleApyMap.isNotEmpty()) { @@ -247,9 +249,9 @@ class TokenItemStateConverter( stakingApyMap = stakingApyMap, ) val rewardTypeRes = when (stakingInfo.rewardType) { - Yield.RewardType.APR -> R.string.staking_apr_earn_badge - Yield.RewardType.UNKNOWN, - Yield.RewardType.APY, + RewardType.APR -> R.string.staking_apr_earn_badge + RewardType.UNKNOWN, + RewardType.APY, null, -> R.string.yield_module_earn_badge } @@ -272,49 +274,35 @@ class TokenItemStateConverter( private fun findStakingRate( currencyStatus: CryptoCurrencyStatus, - stakingApyMap: Map>, + stakingApyMap: Map>, ): StakingLocalInfo { val stakingKey = currencyStatus.currency.stakingKey() - val validators = stakingApyMap[stakingKey] + val targets = stakingApyMap[stakingKey] ?: return StakingLocalInfo(rate = null, isActive = false, rewardType = null) val stakingBalance = currencyStatus.value.stakingBalance as? StakingBalance.Data val stakeKitBalance = stakingBalance as? StakingBalance.Data.StakeKit - val rateInfo: Pair? = if (stakeKitBalance != null) { + val rewardInfo: RewardInfo? = if (stakeKitBalance != null) { // StakeKit-specific: try to find rate from validator address - val validatorsByAddress = validators.associateBy { it.address } + val targetsByAddress = targets.associateBy { it.address } stakeKitBalance.balance.items .mapNotNull { it.validatorAddress } - .firstNotNullOfOrNull { address -> - val validator = validatorsByAddress[address] - validator?.rewardInfo?.rate?.let { rate -> - rate to validator.rewardInfo?.type - } - } - ?: validators - .filter { it.preferred } - .mapNotNull { validator -> - validator.rewardInfo?.rate?.let { rate -> rate to validator.rewardInfo?.type } - } - .maxByOrNull { it.first } + .firstNotNullOfOrNull { address -> targetsByAddress[address]?.rewardInfo } + ?: targets + .filter { it.isPreferred } + .mapNotNull { it.rewardInfo } + .maxByOrNull { it.rate } } else { - // P2P or no balance: use preferred validators - // TODO p2p - validators - .filter { it.preferred } - .mapNotNull { validator -> - validator.rewardInfo?.rate?.let { rate -> - rate to validator.rewardInfo?.type - } - } - .maxByOrNull { it.first } + targets + .mapNotNull { it.rewardInfo } + .maxByOrNull { it.rate } } return StakingLocalInfo( - rate = rateInfo?.first, + rate = rewardInfo?.rate, isActive = stakingBalance != null, - rewardType = rateInfo?.second, + rewardType = rewardInfo?.type, ) } @@ -470,7 +458,7 @@ class TokenItemStateConverter( private data class StakingLocalInfo( val rate: BigDecimal?, val isActive: Boolean, - val rewardType: Yield.RewardType?, + val rewardType: RewardType?, ) private data class EarnApyInfo( diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/P2PEthPool.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/P2PEthPool.kt index aa1b28c629..0dbc3bb4bb 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/P2PEthPool.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/P2PEthPool.kt @@ -1,7 +1,7 @@ package com.tangem.datasource.api.common.config import com.tangem.datasource.BuildConfig -import com.tangem.domain.staking.model.ethpool.P2PStakingConfig +import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig import com.tangem.lib.auth.P2PEthPoolAuthProvider import com.tangem.utils.ProviderSuspend @@ -23,7 +23,7 @@ internal class P2PEthPool( private fun getInitialEnvironment(): ApiEnvironment { return when (BuildConfig.BUILD_TYPE) { MOCKED_BUILD_TYPE -> ApiEnvironment.MOCK - else -> if (P2PStakingConfig.USE_TESTNET) ApiEnvironment.DEV else ApiEnvironment.PROD + else -> if (P2PEthPoolStakingConfig.USE_TESTNET) ApiEnvironment.DEV else ApiEnvironment.PROD } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolBroadcastResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolBroadcastResponse.kt index 20ec7b67f8..1970814c8c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolBroadcastResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolBroadcastResponse.kt @@ -29,7 +29,7 @@ data class P2PEthPoolBroadcastResponse( ) /** - * Transaction status from P2P API + * Transaction status from P2PEthPool API */ @JsonClass(generateAdapter = false) enum class P2PEthPoolTxStatusDTO { diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolErrorResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolErrorResponse.kt index 88235092a1..c99f726d91 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolErrorResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolErrorResponse.kt @@ -4,9 +4,9 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * Error response structure for P2P.org API + * Error response structure for P2P.org eth pooled API * - * All P2P API endpoints return errors in this format + * All P2PEthPool API endpoints return errors in this format */ @JsonClass(generateAdapter = true) data class P2PEthPoolErrorResponse( diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolResponse.kt index e9fe22fd51..d871ebbea8 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolResponse.kt @@ -6,7 +6,7 @@ import com.squareup.moshi.JsonClass /** * Unified response wrapper for all P2P.org API responses * - * All P2P API endpoints return responses in this format: + * All P2PEthPool API endpoints return responses in this format: * ```json * { * "error": null | { code, message, name, errors }, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolVaultsResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolVaultsResponse.kt index 5c825c6346..1e3bde7a5a 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolVaultsResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolVaultsResponse.kt @@ -15,7 +15,7 @@ data class P2PEthPoolVaultsResponse( ) /** - * Network identifier in P2P API + * Network identifier in P2PEthPool API */ @JsonClass(generateAdapter = false) enum class P2PEthPoolNetworkDTO { diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/StakingStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/StakingStoreModule.kt index aeb4d1b8d9..d4bd6b3e65 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/StakingStoreModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/StakingStoreModule.kt @@ -80,7 +80,7 @@ internal object StakingStoreModule { @Provides @Singleton - fun provideP2PBalancesPersistenceStore( + fun provideP2PEthPoolBalancesPersistenceStore( @NetworkMoshi moshi: Moshi, @ApplicationContext context: Context, dispatchers: CoroutineDispatcherProvider, @@ -91,7 +91,7 @@ internal object StakingStoreModule { types = mapWithStringKeyTypes(valueTypes = setTypes()), defaultValue = emptyMap(), ), - produceFile = { context.dataStoreFile(fileName = "p2p_balances") }, + produceFile = { context.dataStoreFile(fileName = "p2p_eth_pool_balances") }, scope = CoroutineScope(context = dispatchers.io + SupervisorJob()), ) } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/P2PEthPoolVaultsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/P2PEthPoolVaultsStore.kt index a99eba1730..e8e507ff08 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/P2PEthPoolVaultsStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/P2PEthPoolVaultsStore.kt @@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.Flow * (similar to StakingYieldsStore for StakeKit yields) * * Vault is ETH-specific concept for pooled staking. - * For other blockchains, P2P may use different structures. + * For other blockchains, P2PEthPool may use different structures. */ interface P2PEthPoolVaultsStore { @@ -23,7 +23,7 @@ interface P2PEthPoolVaultsStore { suspend fun getSync(): List /** - * Store vaults from P2P API + * Store vaults from P2PEthPool API */ suspend fun store(vaults: List) } \ No newline at end of file diff --git a/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt b/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt index e176d0b768..29e697fc8a 100644 --- a/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt +++ b/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt @@ -12,7 +12,7 @@ import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_ import com.tangem.datasource.api.common.config.ApiConfig.Companion.RELEASE_BUILD_TYPE import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.BLOCK_AID_API_KEY import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.TANGEM_API_KEY -import com.tangem.domain.staking.model.ethpool.P2PStakingConfig +import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig import com.tangem.lib.auth.ExpressAuthProvider import com.tangem.lib.auth.P2PEthPoolAuthProvider import com.tangem.lib.auth.StakeKitAuthProvider @@ -300,7 +300,7 @@ internal class ProdApiConfigsManagerTest { } private fun createP2PModel(): TestModel { - val (environment, baseUrl) = if (P2PStakingConfig.USE_TESTNET) { + val (environment, baseUrl) = if (P2PEthPoolStakingConfig.USE_TESTNET) { ApiEnvironment.DEV to "https://api-test.p2p.org/" } else { ApiEnvironment.PROD to "https://api.p2p.org/" diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultP2PEthPoolRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultP2PEthPoolRepository.kt index 85f3038652..33ea700fb0 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultP2PEthPoolRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultP2PEthPoolRepository.kt @@ -25,10 +25,10 @@ import kotlinx.coroutines.withContext import timber.log.Timber /** - * P2P staking repository implementation + * P2PEthPool staking repository implementation */ internal class DefaultP2PEthPoolRepository( - private val p2pApi: P2PEthPoolApi, + private val p2pEthPoolApi: P2PEthPoolApi, private val p2pEthPoolVaultsStore: P2PEthPoolVaultsStore, private val dispatchers: CoroutineDispatcherProvider, ) : P2PEthPoolRepository { @@ -41,7 +41,7 @@ internal class DefaultP2PEthPoolRepository( override suspend fun fetchVaults(network: P2PEthPoolNetwork) { val vaults = getVaults(network).getOrElse { error -> - Timber.e("Error fetching P2P vaults: $error") + Timber.e("Error fetching P2PEthPool vaults: $error") emptyList() } p2pEthPoolVaultsStore.store(vaults) @@ -49,7 +49,7 @@ internal class DefaultP2PEthPoolRepository( override suspend fun getVaults(network: P2PEthPoolNetwork): Either> = either { withContext(dispatchers.io) { - val response = p2pApi.getVaults(network.value) + val response = p2pEthPoolApi.getVaults(network.value) when (response) { is ApiResponse.Success -> { val data = response.data @@ -76,7 +76,7 @@ internal class DefaultP2PEthPoolRepository( vaultAddress = vaultAddress, amount = amount.toDoubleOrNull() ?: raise(StakingError.InvalidAmount("Invalid amount format: $amount")), ) - val response = p2pApi.createDepositTransaction(network.value, requestBody) + val response = p2pEthPoolApi.createDepositTransaction(network.value, requestBody) when (response) { is ApiResponse.Success -> { val data = response.data @@ -101,7 +101,7 @@ internal class DefaultP2PEthPoolRepository( stakerPublicKey = stakerPublicKey, stakeTransactionHash = stakeTransactionHash, ) - val response = p2pApi.createUnstakeTransaction(network.value, requestBody) + val response = p2pEthPoolApi.createUnstakeTransaction(network.value, requestBody) when (response) { is ApiResponse.Success -> { val data = response.data @@ -133,7 +133,7 @@ internal class DefaultP2PEthPoolRepository( ): Either = either { withContext(dispatchers.io) { val requestBody = P2PEthPoolWithdrawRequest(stakerAddress = stakerAddress) - val response = p2pApi.createWithdrawTransaction(network.value, requestBody) + val response = p2pEthPoolApi.createWithdrawTransaction(network.value, requestBody) when (response) { is ApiResponse.Success -> { val data = response.data @@ -154,7 +154,7 @@ internal class DefaultP2PEthPoolRepository( ): Either = either { withContext(dispatchers.io) { val requestBody = P2PEthPoolBroadcastRequest(signedTransaction = signedTransaction) - val response = p2pApi.broadcastTransaction(network.value, requestBody) + val response = p2pEthPoolApi.broadcastTransaction(network.value, requestBody) when (response) { is ApiResponse.Success -> { val data = response.data @@ -175,7 +175,7 @@ internal class DefaultP2PEthPoolRepository( vaultAddress: String, ): Either = either { withContext(dispatchers.io) { - val response = p2pApi.getAccountInfo(network.value, delegatorAddress, vaultAddress) + val response = p2pEthPoolApi.getAccountInfo(network.value, delegatorAddress, vaultAddress) when (response) { is ApiResponse.Success -> { val data = response.data @@ -197,7 +197,7 @@ internal class DefaultP2PEthPoolRepository( period: Int?, ): Either> = either { withContext(dispatchers.io) { - val response = p2pApi.getRewards( + val response = p2pEthPoolApi.getRewards( network = network.value, delegatorAddress = delegatorAddress, vaultAddress = vaultAddress, @@ -217,6 +217,10 @@ internal class DefaultP2PEthPoolRepository( } } + override fun getVaultsFlow(): Flow> { + return p2pEthPoolVaultsStore.get() + } + override fun getStakingAvailability(): Flow { return getVaultsFlow() .distinctUntilChanged() @@ -224,7 +228,7 @@ internal class DefaultP2PEthPoolRepository( if (vaults.isEmpty()) { return@map StakingAvailability.TemporaryUnavailable } else { - StakingAvailability.Available(StakingOption.P2P(vaults)) + StakingAvailability.Available(StakingOption.P2PEthPool(vaults)) } } } @@ -234,15 +238,11 @@ internal class DefaultP2PEthPoolRepository( return if (vaults.isEmpty()) { StakingAvailability.TemporaryUnavailable } else { - StakingAvailability.Available(StakingOption.P2P(vaults)) + StakingAvailability.Available(StakingOption.P2PEthPool(vaults)) } } private suspend fun getVaultsSync(): List { return p2pEthPoolVaultsStore.getSync() } - - private fun getVaultsFlow(): Flow> { - return p2pEthPoolVaultsStore.get() - } } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakeKitRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakeKitRepository.kt index 418992342d..5eedc18e16 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakeKitRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakeKitRepository.kt @@ -364,6 +364,7 @@ internal class DefaultStakeKitRepository( } override fun getStakingAvailability( + integrationId: StakingIntegrationID.StakeKit, rawCurrencyId: CryptoCurrency.RawID, symbol: String, ): Flow { @@ -381,7 +382,7 @@ internal class DefaultStakeKitRepository( ) if (prefetchedYield != null) { - StakingAvailability.Available(StakingOption.StakeKit(prefetchedYield)) + StakingAvailability.Available(StakingOption.StakeKit(integrationId, prefetchedYield)) } else { StakingAvailability.TemporaryUnavailable } @@ -389,6 +390,7 @@ internal class DefaultStakeKitRepository( } override suspend fun getStakingAvailabilitySync( + integrationId: StakingIntegrationID.StakeKit, rawCurrencyId: CryptoCurrency.RawID, symbol: String, ): StakingAvailability { @@ -404,7 +406,7 @@ internal class DefaultStakeKitRepository( ) return if (prefetchedYield != null) { - StakingAvailability.Available(StakingOption.StakeKit(prefetchedYield)) + StakingAvailability.Available(StakingOption.StakeKit(integrationId, prefetchedYield)) } else { StakingAvailability.TemporaryUnavailable } diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingErrorResolver.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingErrorResolver.kt index 99dbdf2927..7cb5404966 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingErrorResolver.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingErrorResolver.kt @@ -29,12 +29,12 @@ internal class DefaultStakingErrorResolver( is StakingError.DomainError -> { analyticsEventHandler.send(StakingAnalyticsEvent.DomainError(error)) } - // P2P errors + // P2PEthPool errors is StakingError.InvalidAmount, is StakingError.DataError, is StakingError.UnknownError, -> { - // P2P errors - no specific analytics event yet + // P2PEthPool errors - no specific analytics event yet } } 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 2d73b39384..343fae5888 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 @@ -61,8 +61,9 @@ internal class DefaultStakingRepository( val stakingIntegration = StakingIntegrationID.create(currencyId = cryptoCurrency.id) val availabilityFlow = when (stakingIntegration) { - is StakingIntegrationID.P2P -> p2pEthPoolRepository.getStakingAvailability() + StakingIntegrationID.P2PEthPool -> p2pEthPoolRepository.getStakingAvailability() is StakingIntegrationID.StakeKit -> stakeKitRepository.getStakingAvailability( + stakingIntegration, rawCurrencyId, cryptoCurrency.symbol, ) @@ -94,8 +95,9 @@ internal class DefaultStakingRepository( ?: return StakingAvailability.Unavailable return when (stakingIntegration) { - is StakingIntegrationID.P2P -> p2pEthPoolRepository.getStakingAvailabilitySync() + StakingIntegrationID.P2PEthPool -> p2pEthPoolRepository.getStakingAvailabilitySync() is StakingIntegrationID.StakeKit -> stakeKitRepository.getStakingAvailabilitySync( + stakingIntegration, rawCurrencyId, cryptoCurrency.symbol, ) diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt index cc663e792b..9766779759 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt @@ -5,6 +5,8 @@ import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.MetadataDTO.RewardScheduleDTO import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.ValidatorDTO.ValidatorStatusDTO import com.tangem.datasource.local.token.converter.YieldTokenConverter +import com.tangem.domain.staking.model.common.RewardInfo +import com.tangem.domain.staking.model.common.RewardType import com.tangem.domain.staking.model.stakekit.AddressArgument import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.Yield.Metadata.RewardSchedule @@ -124,7 +126,7 @@ internal object YieldConverter : Converter { ) } - private fun convertValidator(validatorDTO: YieldDTO.ValidatorDTO, rewardType: Yield.RewardType): Yield.Validator { + private fun convertValidator(validatorDTO: YieldDTO.ValidatorDTO, rewardType: RewardType): Yield.Validator { val address = validatorDTO.address.asMandatory("address") return Yield.Validator( @@ -142,7 +144,7 @@ internal object YieldConverter : Converter { ) } - private fun createRewardInfo(validatorDTO: YieldDTO.ValidatorDTO, rewardType: Yield.RewardType): Yield.RewardInfo? { + private fun createRewardInfo(validatorDTO: YieldDTO.ValidatorDTO, rewardType: RewardType): RewardInfo? { val aprOrApy = validatorDTO.apr val commission = validatorDTO.commission // gross = net / (1 - commission) @@ -162,17 +164,17 @@ internal object YieldConverter : Converter { } else { netApy } - grossAprOrApy?.let { Yield.RewardInfo(rate = it, type = rewardType) } + grossAprOrApy?.let { RewardInfo(rate = it, type = rewardType) } } catch (_: Exception) { - aprOrApy?.let { Yield.RewardInfo(rate = it, type = rewardType) } + aprOrApy?.let { RewardInfo(rate = it, type = rewardType) } } } - private fun convertRewardType(rewardTypeDTO: YieldDTO.RewardTypeDTO): Yield.RewardType { + private fun convertRewardType(rewardTypeDTO: YieldDTO.RewardTypeDTO): RewardType { return when (rewardTypeDTO) { - YieldDTO.RewardTypeDTO.APY -> Yield.RewardType.APY - YieldDTO.RewardTypeDTO.APR -> Yield.RewardType.APR - else -> Yield.RewardType.UNKNOWN + YieldDTO.RewardTypeDTO.APY -> RewardType.APY + YieldDTO.RewardTypeDTO.APR -> RewardType.APR + else -> RewardType.UNKNOWN } } diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolAccountConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolAccountConverter.kt index a19708587c..3fd0209523 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolAccountConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolAccountConverter.kt @@ -9,7 +9,7 @@ import com.tangem.utils.converter.Converter import org.joda.time.Instant /** - * Converter from P2P Account Info Response to Domain model + * Converter from P2PEthPool Account Info Response to Domain model */ internal object P2PEthPoolAccountConverter : Converter { diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolBroadcastResultConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolBroadcastResultConverter.kt index 6f85db1475..13e58961c9 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolBroadcastResultConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolBroadcastResultConverter.kt @@ -8,7 +8,7 @@ import com.tangem.utils.converter.Converter import java.math.BigDecimal /** - * Converter from P2P Broadcast Transaction Response to Domain model + * Converter from P2PEthPool Broadcast Transaction Response to Domain model */ internal object P2PEthPoolBroadcastResultConverter : Converter { diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolErrorConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolErrorConverter.kt index baf0e6a718..97f3d1080a 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolErrorConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolErrorConverter.kt @@ -6,7 +6,7 @@ import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.utils.converter.Converter /** - * Converter from P2P Error Response to Domain StakingError + * Converter from P2PEthPool Error Response to Domain StakingError */ @Suppress("MagicNumber") internal object P2PEthPoolErrorConverter : Converter { diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolRewardConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolRewardConverter.kt index da6432d026..0efd436ce7 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolRewardConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolRewardConverter.kt @@ -5,7 +5,7 @@ import com.tangem.domain.staking.model.ethpool.P2PEthPoolReward import com.tangem.utils.converter.Converter /** - * Converter from P2P Reward Entry DTO to Domain model + * Converter from P2PEthPool Reward Entry DTO to Domain model */ internal object P2PEthPoolRewardConverter : Converter { diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PStakingBalanceConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolStakingBalanceConverter.kt similarity index 76% rename from data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PStakingBalanceConverter.kt rename to data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolStakingBalanceConverter.kt index 83f9d0d2f7..61962eae94 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PStakingBalanceConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolStakingBalanceConverter.kt @@ -9,16 +9,16 @@ 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 { +/** Converts P2PEthPool API response to [StakingBalance.Data.P2PEthPool] */ +internal object P2PEthPoolStakingBalanceConverter { - fun convert(response: P2PEthPoolAccountResponse, source: StatusSource): StakingBalance.Data.P2P { + fun convert(response: P2PEthPoolAccountResponse, source: StatusSource): StakingBalance.Data.P2PEthPool { val stakingId = StakingID( - integrationId = StakingIntegrationID.P2P.EthereumPooled.value, + integrationId = StakingIntegrationID.P2PEthPool.value, address = response.delegatorAddress, ) - val account = P2PStakingAccount( + val account = P2PEthPoolStakingAccount( delegatorAddress = response.delegatorAddress, vaultAddress = response.vaultAddress, stake = convertStake(response.stake), @@ -27,29 +27,29 @@ internal object P2PStakingBalanceConverter { exitQueue = convertExitQueue(response.exitQueue), ) - return StakingBalance.Data.P2P( + return StakingBalance.Data.P2PEthPool( stakingId = stakingId, source = source, account = account, ) } - private fun convertStake(dto: P2PEthPoolStakeDTO): P2PStake { - return P2PStake( + private fun convertStake(dto: P2PEthPoolStakeDTO): P2PEthPoolStake { + return P2PEthPoolStake( assets = dto.assets, totalEarnedAssets = dto.totalEarnedAssets, ) } - private fun convertExitQueue(dto: P2PEthPoolExitQueueDTO): P2PExitQueue { - return P2PExitQueue( + private fun convertExitQueue(dto: P2PEthPoolExitQueueDTO): P2PEthPoolExitQueue { + return P2PEthPoolExitQueue( total = dto.total.toBigDecimal(), requests = dto.requests.map(::convertExitRequest), ) } - private fun convertExitRequest(dto: P2PEthPoolExitRequestDTO): P2PExitRequest { - return P2PExitRequest( + private fun convertExitRequest(dto: P2PEthPoolExitRequestDTO): P2PEthPoolExitRequest { + return P2PEthPoolExitRequest( ticket = dto.ticket, totalAssets = dto.totalAssets.toBigDecimal(), timestamp = Instant.fromEpochSeconds(dto.timestamp), diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolUnsignedTxConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolUnsignedTxConverter.kt index 98c7f7caf7..e82f2d7df5 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolUnsignedTxConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolUnsignedTxConverter.kt @@ -6,7 +6,7 @@ import com.tangem.utils.converter.Converter import java.math.BigDecimal /** - * Converter from P2P Unsigned Transaction DTO to Domain model + * Converter from P2PEthPool Unsigned Transaction DTO to Domain model */ internal object P2PEthPoolUnsignedTxConverter : Converter { diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolVaultConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolVaultConverter.kt index cc010e8faa..75f9a6a470 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolVaultConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PEthPoolVaultConverter.kt @@ -5,7 +5,7 @@ import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault import com.tangem.utils.converter.Converter /** - * Converter from P2P Vault DTO to Domain model + * Converter from P2PEthPool Vault DTO to Domain model */ internal object P2PEthPoolVaultConverter : Converter { diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PYieldBalanceConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PYieldBalanceConverter.kt deleted file mode 100644 index 373a5de22f..0000000000 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/ethpool/P2PYieldBalanceConverter.kt +++ /dev/null @@ -1,92 +0,0 @@ -package com.tangem.data.staking.converters.ethpool - -import com.tangem.domain.models.StatusSource -import com.tangem.domain.models.staking.* -import com.tangem.domain.staking.model.ethpool.P2PEthPoolAccount -import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault -import java.math.BigDecimal - -/** - * tmp solution before facade implementation - */ -internal object P2PYieldBalanceConverter { - - private const val ETH_DECIMALS = 18 - private const val ETH_SYMBOL = "ETH" - private const val ETH_NAME = "Ethereum" - private const val ETH_COINGECKO_ID = "ethereum" - - fun convert( - account: P2PEthPoolAccount, - vault: P2PEthPoolVault, - address: String, - source: StatusSource, - ): YieldBalance { - val integrationId = "p2p-ethereum-pooled" - val stakingId = StakingID( - integrationId = integrationId, - address = address, - ) - - val balanceItems = buildBalanceItems(account, vault) - - return if (balanceItems.isEmpty()) { - YieldBalance.Empty(stakingId = stakingId, source = source) - } else { - YieldBalance.Data( - stakingId = stakingId, - source = source, - balance = YieldBalanceItem( - items = balanceItems, - integrationId = integrationId, - ), - ) - } - } - - private fun buildBalanceItems(account: P2PEthPoolAccount, vault: P2PEthPoolVault): List = buildList { - if (account.stake.assets > BigDecimal.ZERO) { - add( - createBalanceItem( - groupId = "p2p-staked", - amount = account.stake.assets, - type = BalanceType.STAKED, - validatorAddress = vault.vaultAddress, - ), - ) - } - } - - private fun createBalanceItem( - groupId: String, - amount: BigDecimal, - type: BalanceType, - validatorAddress: String, - ): BalanceItem { - return BalanceItem( - groupId = groupId, - token = createEthToken(), - type = type, - amount = amount, - rawCurrencyId = ETH_COINGECKO_ID, - validatorAddress = validatorAddress, - date = null, - pendingActions = emptyList(), - pendingActionsConstraints = emptyList(), - isPending = false, - ) - } - - private fun createEthToken(): YieldToken { - return YieldToken( - name = ETH_NAME, - network = NetworkType.ETHEREUM, - symbol = ETH_SYMBOL, - decimals = ETH_DECIMALS, - address = null, - coinGeckoId = ETH_COINGECKO_ID, - logoURI = null, - isPoints = false, - ) - } -} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/di/StakingBalanceSupplierModule.kt b/data/staking/src/main/java/com/tangem/data/staking/di/StakingBalanceSupplierModule.kt index 26afc00827..0b50257936 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/di/StakingBalanceSupplierModule.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/di/StakingBalanceSupplierModule.kt @@ -1,9 +1,9 @@ package com.tangem.data.staking.di import androidx.datastore.core.DataStore -import com.tangem.data.staking.store.DefaultP2PBalancesStore +import com.tangem.data.staking.store.DefaultP2PEthPoolBalancesStore import com.tangem.data.staking.store.DefaultStakingBalancesStore -import com.tangem.data.staking.store.P2PBalancesStore +import com.tangem.data.staking.store.P2PEthPoolBalancesStore 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 @@ -38,11 +38,11 @@ internal object StakingBalanceSupplierModule { @Provides @Singleton - fun provideP2PBalancesStore( + fun provideP2PEthPoolBalancesStore( persistenceStore: DataStore>>, dispatchers: CoroutineDispatcherProvider, - ): P2PBalancesStore { - return DefaultP2PBalancesStore( + ): P2PEthPoolBalancesStore { + return DefaultP2PEthPoolBalancesStore( runtimeStore = RuntimeSharedStore(), persistenceStore = persistenceStore, dispatchers = dispatchers, 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 22164269fd..fd25f57d71 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 @@ -75,12 +75,12 @@ internal object StakingDataModule { @Provides @Singleton fun provideP2PEthPoolRepository( - p2pApi: P2PEthPoolApi, + p2pEthPoolApi: P2PEthPoolApi, p2pEthPoolVaultsStore: P2PEthPoolVaultsStore, dispatchers: CoroutineDispatcherProvider, ): P2PEthPoolRepository { return DefaultP2PEthPoolRepository( - p2pApi = p2pApi, + p2pEthPoolApi = p2pEthPoolApi, p2pEthPoolVaultsStore = p2pEthPoolVaultsStore, dispatchers = dispatchers, ) diff --git a/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiStakingBalanceFetcher.kt b/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiStakingBalanceFetcher.kt index 8a2411fc11..60c12b3b12 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiStakingBalanceFetcher.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiStakingBalanceFetcher.kt @@ -5,7 +5,7 @@ import arrow.core.left 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.P2PEthPoolBalancesStore import com.tangem.data.staking.store.StakingBalancesStore import com.tangem.data.staking.utils.YieldBalanceRequestBodyFactory import com.tangem.datasource.api.common.response.ApiResponse @@ -24,7 +24,8 @@ import com.tangem.domain.models.wallet.UserWallet 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.P2PStakingConfig +import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig +import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.runSuspendCatching @@ -38,16 +39,16 @@ import javax.inject.Inject /** * Default implementation of [MultiStakingBalanceFetcher] * - * Supports both StakeKit and P2P staking providers. + * Supports both StakeKit and P2PEthPool staking providers. * - * @property userWalletsStore user wallets store - * @property stakingYieldsStore staking yields store - * @property stakingBalancesStore staking balances store (StakeKit) - * @property p2pBalancesStore P2P balances store - * @property stakeKitApi stake kit API - * @property p2pApi P2P ETH Pool API - * @property p2pVaultsStore P2P vaults store - * @property dispatchers dispatchers + * @property userWalletsStore user wallets store + * @property stakingYieldsStore staking yields store + * @property stakingBalancesStore staking balances store (StakeKit) + * @property p2PEthPoolBalancesStore P2PEthPool balances store + * @property stakeKitApi stake kit API + * @property p2pEthPoolApi P2PEthPool API + * @property p2pEthPoolVaultsStore P2PEthPool vaults store + * @property dispatchers dispatchers * [REDACTED_AUTHOR] */ @@ -56,10 +57,10 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor( private val userWalletsStore: UserWalletsStore, private val stakingYieldsStore: StakingYieldsStore, private val stakingBalancesStore: StakingBalancesStore, - private val p2pBalancesStore: P2PBalancesStore, + private val p2PEthPoolBalancesStore: P2PEthPoolBalancesStore, private val stakeKitApi: StakeKitApi, - private val p2pApi: P2PEthPoolApi, - private val p2pVaultsStore: P2PEthPoolVaultsStore, + private val p2pEthPoolApi: P2PEthPoolApi, + private val p2pEthPoolVaultsStore: P2PEthPoolVaultsStore, private val dispatchers: CoroutineDispatcherProvider, ) : MultiStakingBalanceFetcher { @@ -75,7 +76,7 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor( return it.left() } - val (stakeKitIds, p2pIds) = stakingIds.partition { stakingId -> + val (stakeKitIds, p2pEthPoolIds) = stakingIds.partition { stakingId -> val stakingIntegrationID = StakingIntegrationID.entries.find { it.value == stakingId.integrationId } @@ -86,7 +87,7 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor( """ Staking IDs to fetch: - StakeKit: ${stakeKitIds.joinToString()} - - P2P: ${p2pIds.joinToString()} + - P2PEthPool: ${p2pEthPoolIds.joinToString()} """.trimIndent(), ) @@ -96,8 +97,8 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor( launch { fetchStakeKitBalances(params.userWalletId, stakeKitIds.toSet()) } } - if (p2pIds.isNotEmpty()) { - launch { fetchP2PBalances(params.userWalletId, p2pIds.toSet()) } + if (p2pEthPoolIds.isNotEmpty()) { + launch { fetchP2PBalances(params.userWalletId, p2pEthPoolIds.toSet()) } } } } @@ -110,8 +111,11 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor( stakingIds = stakeKitIds.toSet(), ) } - if (p2pIds.isNotEmpty()) { - p2pBalancesStore.storeError(userWalletId = params.userWalletId, stakingIds = p2pIds.toSet()) + if (p2pEthPoolIds.isNotEmpty()) { + p2PEthPoolBalancesStore.storeError( + userWalletId = params.userWalletId, + stakingIds = p2pEthPoolIds.toSet(), + ) } } } @@ -128,12 +132,12 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor( } private suspend fun fetchP2PBalances(userWalletId: UserWalletId, stakingIds: Set) { - p2pBalancesStore.refresh(userWalletId = userWalletId, stakingIds = stakingIds) + p2PEthPoolBalancesStore.refresh(userWalletId = userWalletId, stakingIds = stakingIds) - val vaults = runSuspendCatching { p2pVaultsStore.getSync() }.getOrNull().orEmpty() + val vaults = runSuspendCatching { p2pEthPoolVaultsStore.getSync() }.getOrNull().orEmpty() if (vaults.isEmpty()) { - Timber.w("No P2P vaults available for $userWalletId") - p2pBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds) + Timber.w("No P2PEthPool vaults available for $userWalletId") + p2PEthPoolBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds) return } @@ -143,59 +147,17 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor( private suspend fun fetchFromP2P( userWalletId: UserWalletId, stakingIds: Set, - vaults: List, + vaults: List, ) { safeApiCall( call = { val addresses = stakingIds.map { it.address }.toSet() + val responses = fetchP2PAccountResponses(vaults = vaults, addresses = addresses) - val responses = mutableSetOf() - - for (vault in vaults) { - for (address in addresses) { - runSuspendCatching { - val response = p2pApi.getAccountInfo( - network = P2PStakingConfig.activeNetwork.value, - delegatorAddress = address, - vaultAddress = vault.vaultAddress, - ) - - when (response) { - is ApiResponse.Success -> { - val data = response.data - if (data.error != null) { - Timber.w( - "P2P API returned error for vault ${vault.vaultAddress}, " + - "address $address: ${data.error ?: "error"}", - ) - } else { - val result = requireNotNull(data.result) { - "Result is null in successful response" - } - responses.add(result) - } - } - is ApiResponse.Error -> { - Timber.w( - response.cause, - "Failed to fetch P2P balance for vault ${vault.vaultAddress}, " + - "address $address", - ) - } - } - }.onFailure { error -> - Timber.w( - error, - "Failed to fetch P2P balance for vault ${vault.vaultAddress}, address $address", - ) - } - } - } - - Timber.i("Successfully fetched ${responses.size} P2P balances for $userWalletId") + Timber.i("Successfully fetched ${responses.size} P2PEthPool balances for $userWalletId") if (responses.isNotEmpty()) { - p2pBalancesStore.storeActual(userWalletId = userWalletId, values = responses) + p2PEthPoolBalancesStore.storeActual(userWalletId = userWalletId, values = responses) val missingStakingIds = stakingIds.filter { stakingId -> responses.none { response -> @@ -205,23 +167,76 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor( if (missingStakingIds.isNotEmpty()) { Timber.i("Missing responses for ${missingStakingIds.size} staking IDs: $missingStakingIds") - p2pBalancesStore.storeError(userWalletId = userWalletId, stakingIds = missingStakingIds.toSet()) + p2PEthPoolBalancesStore.storeError( + userWalletId = userWalletId, + stakingIds = missingStakingIds.toSet(), + ) } } else { - Timber.i("No P2P responses received for $userWalletId") - p2pBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds) + Timber.i("No P2PEthPool responses received for $userWalletId") + p2PEthPoolBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds) } }, onError = { throwable -> - Timber.e(throwable, "Unable to fetch P2P balances $userWalletId") + Timber.e(throwable, "Unable to fetch P2PEthPool balances $userWalletId") - p2pBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds) + p2PEthPoolBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds) throw throwable }, ) } + private suspend fun fetchP2PAccountResponses( + vaults: List, + addresses: Set, + ): Set { + val responses = mutableSetOf() + + for (vault in vaults) { + for (address in addresses) { + runSuspendCatching { + val response = p2pEthPoolApi.getAccountInfo( + network = P2PEthPoolStakingConfig.activeNetwork.value, + delegatorAddress = address, + vaultAddress = vault.vaultAddress, + ) + + when (response) { + is ApiResponse.Success -> { + val data = response.data + if (data.error != null) { + Timber.w( + "P2PEthPool API returned error for vault ${vault.vaultAddress}, " + + "address $address: ${data.error ?: "error"}", + ) + } else { + val result = requireNotNull(data.result) { + "Result is null in successful response" + } + responses.add(result) + } + } + is ApiResponse.Error -> { + Timber.w( + response.cause, + "Failed to fetch P2PEthPool balance for vault ${vault.vaultAddress}, " + + "address $address", + ) + } + } + }.onFailure { error -> + Timber.w( + error, + "Failed to fetch P2PEthPool balance for vault ${vault.vaultAddress}, address $address", + ) + } + } + } + + return responses + } + private inline fun checkIsSupportedByWalletOrElse(userWalletId: UserWalletId, ifNotSupported: (Throwable) -> Unit) { val maybeUserWallet = userWalletsStore.getSyncOrNull(key = userWalletId).toOption() diff --git a/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiStakingBalanceProducer.kt b/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiStakingBalanceProducer.kt index 2ab3c5ad8c..0ce6065dae 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiStakingBalanceProducer.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiStakingBalanceProducer.kt @@ -2,7 +2,7 @@ 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.P2PEthPoolBalancesStore import com.tangem.data.staking.store.StakingBalancesStore import com.tangem.domain.models.staking.StakingBalance import com.tangem.domain.staking.multi.MultiStakingBalanceProducer @@ -19,11 +19,11 @@ import kotlinx.coroutines.flow.onEmpty /** * Default implementation of [MultiStakingBalanceProducer] * - * Combines staking balances from both StakeKit and P2P providers. + * Combines staking balances from both StakeKit and P2PEthPool providers. * * @property params params * @property stakingBalancesStore StakeKit staking balances store - * @property p2pBalancesStore P2P balances store + * @property p2PEthPoolBalancesStore P2PEthPool balances store * @property dispatchers dispatchers * [REDACTED_AUTHOR] @@ -31,7 +31,7 @@ import kotlinx.coroutines.flow.onEmpty internal class DefaultMultiStakingBalanceProducer @AssistedInject constructor( @Assisted val params: MultiStakingBalanceProducer.Params, private val stakingBalancesStore: StakingBalancesStore, - private val p2pBalancesStore: P2PBalancesStore, + private val p2PEthPoolBalancesStore: P2PEthPoolBalancesStore, private val dispatchers: CoroutineDispatcherProvider, ) : MultiStakingBalanceProducer { @@ -39,10 +39,10 @@ internal class DefaultMultiStakingBalanceProducer @AssistedInject constructor( override fun produce(): Flow> { val stakeKitFlow = stakingBalancesStore.get(userWalletId = params.userWalletId) - val p2pFlow = p2pBalancesStore.get(userWalletId = params.userWalletId) + val p2pEthPoolFlow = p2PEthPoolBalancesStore.get(userWalletId = params.userWalletId) - return combine(stakeKitFlow, p2pFlow) { stakeKitBalances, p2pBalances -> - stakeKitBalances + p2pBalances + return combine(stakeKitFlow, p2pEthPoolFlow) { stakeKitBalances, p2pEthPoolBalances -> + stakeKitBalances + p2pEthPoolBalances } .distinctUntilChanged() .onEmpty { emit(value = hashSetOf()) } diff --git a/data/staking/src/main/java/com/tangem/data/staking/store/DefaultP2PBalancesStore.kt b/data/staking/src/main/java/com/tangem/data/staking/store/DefaultP2PEthPoolBalancesStore.kt similarity index 91% rename from data/staking/src/main/java/com/tangem/data/staking/store/DefaultP2PBalancesStore.kt rename to data/staking/src/main/java/com/tangem/data/staking/store/DefaultP2PEthPoolBalancesStore.kt index d95a730a9f..2be492ef3a 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/store/DefaultP2PBalancesStore.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/store/DefaultP2PEthPoolBalancesStore.kt @@ -1,7 +1,7 @@ package com.tangem.data.staking.store import androidx.datastore.core.DataStore -import com.tangem.data.staking.converters.ethpool.P2PStakingBalanceConverter +import com.tangem.data.staking.converters.ethpool.P2PEthPoolStakingBalanceConverter import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse import com.tangem.datasource.local.datastore.RuntimeSharedStore import com.tangem.domain.models.StatusSource @@ -20,22 +20,22 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch internal typealias WalletIdWithP2PStakingBalances = Map> -internal typealias WalletIdWithP2PResponses = Map> +internal typealias WalletIdWithP2PEthPoolResponses = Map> /** - * Default implementation of [P2PBalancesStore] + * Default implementation of [P2PEthPoolBalancesStore] * - * Stores P2P ETH Pool staking balances. + * Stores P2PEthPool staking balances. * * @property runtimeStore runtime store * @property persistenceStore persistence store * @param dispatchers coroutine dispatchers */ -internal class DefaultP2PBalancesStore( +internal class DefaultP2PEthPoolBalancesStore( private val runtimeStore: RuntimeSharedStore, - private val persistenceStore: DataStore, + private val persistenceStore: DataStore, dispatchers: CoroutineDispatcherProvider, -) : P2PBalancesStore { +) : P2PEthPoolBalancesStore { private val scope = CoroutineScope(context = SupervisorJob() + dispatchers.io) @@ -47,7 +47,7 @@ internal class DefaultP2PBalancesStore( value = cachedData.map { (stringWalletId, responses) -> val key = UserWalletId(stringWalletId) val value = responses.map { response -> - P2PStakingBalanceConverter.convert( + P2PEthPoolStakingBalanceConverter.convert( response = response, source = StatusSource.CACHE, ) @@ -108,7 +108,7 @@ internal class DefaultP2PBalancesStore( private suspend fun storeInRuntime(userWalletId: UserWalletId, values: Set) { val newBalances = values.map { response -> - P2PStakingBalanceConverter.convert( + P2PEthPoolStakingBalanceConverter.convert( response = response, source = StatusSource.ACTUAL, ) @@ -152,7 +152,7 @@ internal class DefaultP2PBalancesStore( current.toMutableMap().apply { this[userWalletId.stringValue] = this[userWalletId.stringValue].orEmpty() .filterNot { response -> - StakingIntegrationID.P2P.EthereumPooled.value in integrationIds + StakingIntegrationID.P2PEthPool.value in integrationIds } .toSet() } diff --git a/data/staking/src/main/java/com/tangem/data/staking/store/P2PBalancesStore.kt b/data/staking/src/main/java/com/tangem/data/staking/store/P2PEthPoolBalancesStore.kt similarity index 92% rename from data/staking/src/main/java/com/tangem/data/staking/store/P2PBalancesStore.kt rename to data/staking/src/main/java/com/tangem/data/staking/store/P2PEthPoolBalancesStore.kt index 02479075c3..72891d6a1b 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/store/P2PBalancesStore.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/store/P2PEthPoolBalancesStore.kt @@ -7,9 +7,9 @@ import com.tangem.domain.models.wallet.UserWalletId import kotlinx.coroutines.flow.Flow /** - * Store for P2P ETH Pool staking balances + * Store for P2PEthPool staking balances */ -interface P2PBalancesStore { +interface P2PEthPoolBalancesStore { fun get(userWalletId: UserWalletId): Flow> diff --git a/data/staking/src/test/kotlin/com/tangem/data/staking/StakingBalanceExt.kt b/data/staking/src/test/kotlin/com/tangem/data/staking/StakingBalanceExt.kt index a0bacfe34f..ee45a232fe 100644 --- a/data/staking/src/test/kotlin/com/tangem/data/staking/StakingBalanceExt.kt +++ b/data/staking/src/test/kotlin/com/tangem/data/staking/StakingBalanceExt.kt @@ -1,6 +1,6 @@ package com.tangem.data.staking -import com.tangem.data.staking.converters.ethpool.P2PStakingBalanceConverter +import com.tangem.data.staking.converters.ethpool.P2PEthPoolStakingBalanceConverter 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.StakingBalanceConverter @@ -11,8 +11,10 @@ internal fun YieldBalanceWrapperDTO.toDomain(source: StatusSource = StatusSource return StakingBalanceConverter(isCached = source == StatusSource.CACHE).convert(this)!! } -internal fun P2PEthPoolAccountResponse.toDomain(source: StatusSource = StatusSource.CACHE): StakingBalance.Data.P2P { - return P2PStakingBalanceConverter.convert( +internal fun P2PEthPoolAccountResponse.toDomain( + source: StatusSource = StatusSource.CACHE, +): StakingBalance.Data.P2PEthPool { + return P2PEthPoolStakingBalanceConverter.convert( response = this, source = source, ) diff --git a/data/staking/src/test/kotlin/com/tangem/data/staking/multi/DefaultMultiStakingBalanceFetcherTest.kt b/data/staking/src/test/kotlin/com/tangem/data/staking/multi/DefaultMultiStakingBalanceFetcherTest.kt index e380157061..30f1ed325f 100644 --- a/data/staking/src/test/kotlin/com/tangem/data/staking/multi/DefaultMultiStakingBalanceFetcherTest.kt +++ b/data/staking/src/test/kotlin/com/tangem/data/staking/multi/DefaultMultiStakingBalanceFetcherTest.kt @@ -4,7 +4,7 @@ import arrow.core.toOption 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.P2PEthPoolBalancesStore import com.tangem.data.staking.store.StakingBalancesStore import com.tangem.data.staking.utils.YieldBalanceRequestBodyFactory import com.tangem.datasource.api.common.response.ApiResponse @@ -36,19 +36,19 @@ internal class DefaultMultiStakingBalanceFetcherTest { private val userWalletsStore: UserWalletsStore = mockk() private val stakingYieldsStore: StakingYieldsStore = mockk() private val stakingBalancesStore: StakingBalancesStore = mockk(relaxUnitFun = true) - private val p2pBalancesStore: P2PBalancesStore = mockk(relaxUnitFun = true) + private val p2PEthPoolBalancesStore: P2PEthPoolBalancesStore = mockk(relaxUnitFun = true) private val stakeKitApi: StakeKitApi = mockk() - private val p2pApi: P2PEthPoolApi = mockk() - private val p2pVaultsStore: P2PEthPoolVaultsStore = mockk() + private val p2pEthPoolApi: P2PEthPoolApi = mockk() + private val p2pEthPoolVaultsStore: P2PEthPoolVaultsStore = mockk() private val fetcher = DefaultMultiStakingBalanceFetcher( userWalletsStore = userWalletsStore, stakingYieldsStore = stakingYieldsStore, stakingBalancesStore = stakingBalancesStore, - p2pBalancesStore = p2pBalancesStore, + p2PEthPoolBalancesStore = p2PEthPoolBalancesStore, stakeKitApi = stakeKitApi, - p2pApi = p2pApi, - p2pVaultsStore = p2pVaultsStore, + p2pEthPoolApi = p2pEthPoolApi, + p2pEthPoolVaultsStore = p2pEthPoolVaultsStore, dispatchers = TestingCoroutineDispatcherProvider(), ) diff --git a/data/staking/src/test/kotlin/com/tangem/data/staking/multi/DefaultMultiStakingBalanceProducerTest.kt b/data/staking/src/test/kotlin/com/tangem/data/staking/multi/DefaultMultiStakingBalanceProducerTest.kt index faf28b5731..e33aa4cd4f 100644 --- a/data/staking/src/test/kotlin/com/tangem/data/staking/multi/DefaultMultiStakingBalanceProducerTest.kt +++ b/data/staking/src/test/kotlin/com/tangem/data/staking/multi/DefaultMultiStakingBalanceProducerTest.kt @@ -3,7 +3,7 @@ package com.tangem.data.staking.multi 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.P2PEthPoolBalancesStore import com.tangem.data.staking.store.StakingBalancesStore import com.tangem.data.staking.toDomain import com.tangem.domain.models.StatusSource @@ -28,13 +28,13 @@ internal class DefaultMultiStakingBalanceProducerTest { private val params = MultiStakingBalanceProducer.Params(userWalletId = UserWalletId("011")) private val stakingBalancesStore = mockk() - private val p2pBalancesStore = mockk() + private val p2PEthPoolBalancesStore = mockk() private val dispatchers = TestingCoroutineDispatcherProvider() private val producer = DefaultMultiStakingBalanceProducer( params = params, stakingBalancesStore = stakingBalancesStore, - p2pBalancesStore = p2pBalancesStore, + p2PEthPoolBalancesStore = p2PEthPoolBalancesStore, dispatchers = dispatchers, ) @@ -48,13 +48,13 @@ internal class DefaultMultiStakingBalanceProducerTest { val networksStatusesFlow = flowOf(balances) every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow - every { p2pBalancesStore.get(params.userWalletId) } returns flowOf(emptySet()) + every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns flowOf(emptySet()) val actual = producer.produce() // check after producer.produce() verify { stakingBalancesStore.get(params.userWalletId) } - verify { p2pBalancesStore.get(params.userWalletId) } + verify { p2PEthPoolBalancesStore.get(params.userWalletId) } val values = getEmittedValues(flow = actual) @@ -67,13 +67,13 @@ internal class DefaultMultiStakingBalanceProducerTest { val networksStatusesFlow = MutableSharedFlow>(replay = 2) every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow - every { p2pBalancesStore.get(params.userWalletId) } returns flowOf(emptySet()) + every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns flowOf(emptySet()) val actual = producer.produce() // check after producer.produce() verify { stakingBalancesStore.get(params.userWalletId) } - verify { p2pBalancesStore.get(params.userWalletId) } + verify { p2PEthPoolBalancesStore.get(params.userWalletId) } // first emit val balances = setOf( @@ -108,13 +108,13 @@ internal class DefaultMultiStakingBalanceProducerTest { val networksStatusesFlow = MutableSharedFlow>(replay = 2) every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow - every { p2pBalancesStore.get(params.userWalletId) } returns flowOf(emptySet()) + every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns flowOf(emptySet()) val actual = producer.produce() // check after producer.produce() verify { stakingBalancesStore.get(params.userWalletId) } - verify { p2pBalancesStore.get(params.userWalletId) } + verify { p2PEthPoolBalancesStore.get(params.userWalletId) } // first emit val wrappers = setOf( @@ -157,13 +157,13 @@ internal class DefaultMultiStakingBalanceProducerTest { .buffer(capacity = 5) every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow - every { p2pBalancesStore.get(params.userWalletId) } returns flowOf(emptySet()) + every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns flowOf(emptySet()) val actual = producer.produceWithFallback() // check after producer.produce() verify { stakingBalancesStore.get(params.userWalletId) } - verify { p2pBalancesStore.get(params.userWalletId) } + verify { p2PEthPoolBalancesStore.get(params.userWalletId) } val values1 = getEmittedValues(flow = actual) @@ -181,13 +181,13 @@ internal class DefaultMultiStakingBalanceProducerTest { @Test fun `test that flow is empty`() = runTest { every { stakingBalancesStore.get(params.userWalletId) } returns emptyFlow() - every { p2pBalancesStore.get(params.userWalletId) } returns emptyFlow() + every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns emptyFlow() val actual = producer.produce() // check after producer.produce() verify { stakingBalancesStore.get(params.userWalletId) } - verify { p2pBalancesStore.get(params.userWalletId) } + verify { p2PEthPoolBalancesStore.get(params.userWalletId) } val values = getEmittedValues(flow = actual) @@ -198,53 +198,53 @@ internal class DefaultMultiStakingBalanceProducerTest { @Test fun `test that StakeKit and P2P balances are combined`() = runTest { val stakeKitBalances = createStakeKitBalances() - val p2pBalances = createP2PBalances() + val p2pEthPoolBalances = createP2PEthPoolBalances() every { stakingBalancesStore.get(params.userWalletId) } returns flowOf(stakeKitBalances) - every { p2pBalancesStore.get(params.userWalletId) } returns flowOf(p2pBalances) + every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns flowOf(p2pEthPoolBalances) val actual = producer.produce() // check after producer.produce() verify { stakingBalancesStore.get(params.userWalletId) } - verify { p2pBalancesStore.get(params.userWalletId) } + verify { p2PEthPoolBalancesStore.get(params.userWalletId) } val values = getEmittedValues(flow = actual) Truth.assertThat(values.size).isEqualTo(1) - Truth.assertThat(values.first()).isEqualTo(stakeKitBalances + p2pBalances) + Truth.assertThat(values.first()).isEqualTo(stakeKitBalances + p2pEthPoolBalances) } @Test fun `test that P2P balances are updated independently from StakeKit`() = runTest { val stakeKitBalances = createStakeKitBalancesWithTonOnly() - val p2pFlow = MutableSharedFlow>(replay = 2) + val p2pEthPoolFlow = MutableSharedFlow>(replay = 2) every { stakingBalancesStore.get(params.userWalletId) } returns flowOf(stakeKitBalances) - every { p2pBalancesStore.get(params.userWalletId) } returns p2pFlow + every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns p2pEthPoolFlow val actual = producer.produce() // check after producer.produce() verify { stakingBalancesStore.get(params.userWalletId) } - verify { p2pBalancesStore.get(params.userWalletId) } + verify { p2PEthPoolBalancesStore.get(params.userWalletId) } - // first emit - empty P2P - p2pFlow.emit(emptySet()) + // first emit - empty P2PEthPool + p2pEthPoolFlow.emit(emptySet()) val values1 = getEmittedValues(flow = actual) Truth.assertThat(values1.size).isEqualTo(1) Truth.assertThat(values1.first()).isEqualTo(stakeKitBalances) - // second emit - with P2P balance - val p2pBalances = createP2PBalances() - p2pFlow.emit(p2pBalances) + // second emit - with P2PEthPool balance + val p2pEthPoolBalances = createP2PEthPoolBalances() + p2pEthPoolFlow.emit(p2pEthPoolBalances) val values2 = getEmittedValues(flow = actual) Truth.assertThat(values2.size).isEqualTo(2) - Truth.assertThat(values2.last()).isEqualTo(stakeKitBalances + p2pBalances) + Truth.assertThat(values2.last()).isEqualTo(stakeKitBalances + p2pEthPoolBalances) } private companion object { @@ -255,7 +255,7 @@ internal class DefaultMultiStakingBalanceProducerTest { address = "0x1", ) val p2pEthereumId = StakingID( - integrationId = StakingIntegrationID.P2P.EthereumPooled.value, + integrationId = StakingIntegrationID.P2PEthPool.value, address = "0x5aa711F440Eb6d4361148bBD89d03464628ace84", ) @@ -272,7 +272,7 @@ internal class DefaultMultiStakingBalanceProducerTest { ) } - fun createP2PBalances(): Set { + fun createP2PEthPoolBalances(): Set { return setOf( MockP2PEthPoolAccountResponseFactory.createWithBalance(stakingId = p2pEthereumId).toDomain( source = StatusSource.ACTUAL, diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/staking/P2PStakingAccount.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/staking/P2PEthPoolStakingAccount.kt similarity index 70% rename from domain/models/src/main/kotlin/com/tangem/domain/models/staking/P2PStakingAccount.kt rename to domain/models/src/main/kotlin/com/tangem/domain/models/staking/P2PEthPoolStakingAccount.kt index edc489e496..d196a17cbc 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/staking/P2PStakingAccount.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/staking/P2PEthPoolStakingAccount.kt @@ -4,31 +4,31 @@ import com.tangem.domain.models.serialization.SerializedBigDecimal import kotlinx.datetime.Instant import kotlinx.serialization.Serializable -/** P2P.org staking account */ +/** P2P.org eth pooled staking account */ @Serializable -data class P2PStakingAccount( +data class P2PEthPoolStakingAccount( val delegatorAddress: String, val vaultAddress: String, - val stake: P2PStake, + val stake: P2PEthPoolStake, val availableToUnstake: SerializedBigDecimal, val availableToWithdraw: SerializedBigDecimal, - val exitQueue: P2PExitQueue, + val exitQueue: P2PEthPoolExitQueue, ) @Serializable -data class P2PStake( +data class P2PEthPoolStake( val assets: SerializedBigDecimal, val totalEarnedAssets: SerializedBigDecimal, ) @Serializable -data class P2PExitQueue( +data class P2PEthPoolExitQueue( val total: SerializedBigDecimal, - val requests: List, + val requests: List, ) @Serializable -data class P2PExitRequest( +data class P2PEthPoolExitRequest( val ticket: String, val totalAssets: SerializedBigDecimal, val timestamp: Instant, diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/staking/StakingBalance.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/staking/StakingBalance.kt index a8493d22f4..a83dd1b1e2 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/staking/StakingBalance.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/staking/StakingBalance.kt @@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable import java.math.BigDecimal /** - * Staking balance facade covering StakeKit and P2P balances + * Staking balance facade covering StakeKit and P2PEthPool balances */ @Serializable sealed interface StakingBalance { @@ -50,10 +50,10 @@ sealed interface StakingBalance { } @Serializable - data class P2P( + data class P2PEthPool( override val stakingId: StakingID, override val source: StatusSource, - val account: P2PStakingAccount, + val account: P2PEthPoolStakingAccount, ) : Data { override val totalStaked: BigDecimal @@ -93,7 +93,7 @@ sealed interface StakingBalance { fun copySealed(source: StatusSource): StakingBalance { return when (this) { is Data.StakeKit -> copy(source = source) - is Data.P2P -> copy(source = source) + is Data.P2PEthPool -> copy(source = source) is Empty -> copy(source = source) is Error -> this } diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/staking/YieldToken.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/staking/YieldToken.kt index 18954a5c1f..92248c9b61 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/staking/YieldToken.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/staking/YieldToken.kt @@ -12,4 +12,17 @@ data class YieldToken( val coinGeckoId: String?, val logoURI: String?, val isPoints: Boolean?, -) \ No newline at end of file +) { + companion object { + val ETH = YieldToken( // TODO p2p + name = "Ethereum", + network = NetworkType.ETHEREUM, + symbol = "ETH", + decimals = 18, + address = null, + coinGeckoId = "ethereum", + logoURI = null, + isPoints = false, + ) + } +} \ No newline at end of file diff --git a/domain/staking/build.gradle.kts b/domain/staking/build.gradle.kts index 24a4cc4ae2..587e19d32d 100644 --- a/domain/staking/build.gradle.kts +++ b/domain/staking/build.gradle.kts @@ -29,8 +29,6 @@ dependencies { implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) - implementation(projects.features.staking.api) - implementation(tangemDeps.blockchain) { exclude(module = "joda-time") } diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingTarget.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingTarget.kt new file mode 100644 index 0000000000..c6c99e7fab --- /dev/null +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingTarget.kt @@ -0,0 +1,66 @@ +package com.tangem.domain.staking.model + +import com.tangem.domain.staking.model.common.RewardInfo +import com.tangem.domain.staking.model.common.RewardType +import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault +import com.tangem.domain.staking.model.stakekit.Yield + +/** + * Represents either a StakeKit Validator or a P2P ETH Pool Vault. + */ +sealed interface StakingTarget { + + /** Unique identifier (validator address or vault address) */ + val address: String + + /** Display name */ + val name: String + + /** Reward info (rate and type) */ + val rewardInfo: RewardInfo? + + /** Whether this target is preferred/recommended */ + val isPreferred: Boolean + + /** Whether this target is active and available for staking */ + val isActive: Boolean + + /** Image URL for display (validator logo or vault icon) */ + val image: String? + + /** Whether this is a strategic partner (shows special badge in UI) */ + val isStrategicPartner: Boolean + + /** + * StakeKit Validator wrapper + */ + data class Validator(val delegate: Yield.Validator) : StakingTarget { + override val address: String = delegate.address + override val name: String = delegate.name + override val rewardInfo: RewardInfo? = delegate.rewardInfo + override val isPreferred: Boolean = delegate.preferred + override val isActive: Boolean = delegate.status == Yield.Validator.ValidatorStatus.ACTIVE + override val image: String? = delegate.image + override val isStrategicPartner: Boolean = delegate.isStrategicPartner + } + + /** + * P2P ETH Pool Vault wrapper + */ + data class Vault(val vault: P2PEthPoolVault) : StakingTarget { + override val address: String = vault.vaultAddress + override val name: String = vault.displayName + override val rewardInfo = RewardInfo( + rate = vault.apy, + type = RewardType.APY, + ) + override val isPreferred: Boolean = true + override val isActive: Boolean = true + override val image: String? = null + override val isStrategicPartner: Boolean = true + } +} + +fun Yield.Validator.toStakingTarget(): StakingTarget = StakingTarget.Validator(this) + +fun P2PEthPoolVault.toStakingTarget(): StakingTarget = StakingTarget.Vault(this) \ No newline at end of file diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/common/RewardInfo.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/common/RewardInfo.kt new file mode 100644 index 0000000000..b1534a9473 --- /dev/null +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/common/RewardInfo.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.staking.model.common + +import com.tangem.domain.models.serialization.SerializedBigDecimal +import kotlinx.serialization.Serializable + +@Serializable +data class RewardInfo( + val rate: SerializedBigDecimal, + val type: RewardType, +) \ No newline at end of file diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/common/RewardType.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/common/RewardType.kt new file mode 100644 index 0000000000..d444e512ea --- /dev/null +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/common/RewardType.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.staking.model.common + +import kotlinx.serialization.Serializable + +@Serializable +enum class RewardType { + APY, // compound rate + APR, // simple rate + UNKNOWN, +} \ No newline at end of file diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolAccount.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolAccount.kt index 491c28a775..56476007cb 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolAccount.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolAccount.kt @@ -4,7 +4,7 @@ import com.tangem.domain.models.serialization.SerializedBigDecimal import org.joda.time.Instant /** - * P2P.org account staking information + * Account staking information * Contains detailed balance and exit queue information */ data class P2PEthPoolAccount( diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolAction.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolAction.kt index 5e5f10593f..00bfdcec8b 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolAction.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolAction.kt @@ -22,7 +22,7 @@ data class P2PEthPoolAction( ) /** - * Types of P2P staking actions + * Types of P2PEthPool staking actions */ @Serializable enum class P2PEthPoolActionType { @@ -33,7 +33,7 @@ enum class P2PEthPoolActionType { } /** - * Status of P2P staking action + * Status of P2PEthPool staking action */ @Serializable enum class P2PEthPoolActionStatus { @@ -46,7 +46,7 @@ enum class P2PEthPoolActionStatus { } /** - * Transaction details for P2P staking action + * Transaction details for P2PEthPool staking action */ data class P2PEthPoolStakingTransaction( val id: String?, diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolBalance.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolBalance.kt index c6ea6b9477..9f5b90ee4a 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolBalance.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolBalance.kt @@ -4,7 +4,7 @@ import com.tangem.domain.models.serialization.SerializedBigDecimal import org.joda.time.Instant /** - * P2P.org staking balance information (similar to StakeKit's YieldBalanceItem) + * Staking balance information (similar to StakeKit's YieldBalanceItem) * Contains staked amounts, rewards, and pending actions */ data class P2PEthPoolStakingBalance( @@ -15,7 +15,7 @@ data class P2PEthPoolStakingBalance( ) /** - * Individual balance item for P2P staking + * Individual balance item for P2PEthPool staking */ data class P2PEthPoolBalanceItem( val type: P2PEthPoolBalanceType, @@ -26,7 +26,7 @@ data class P2PEthPoolBalanceItem( ) /** - * Types of balances in P2P staking + * Types of balances in P2PEthPool staking */ enum class P2PEthPoolBalanceType { STAKED, diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolNetwork.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolNetwork.kt index dfbed790f9..db60654ee8 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolNetwork.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolNetwork.kt @@ -61,7 +61,7 @@ enum class P2PEthPoolNetwork( } /** - * Check if chain ID is supported for P2P staking + * Check if chain ID is supported for P2PEthPool staking * * @param chainId Ethereum chain ID * @return true if supported, false otherwise diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolReward.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolReward.kt index 70af95ac9a..b4672d3f90 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolReward.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolReward.kt @@ -4,7 +4,7 @@ import com.tangem.domain.models.serialization.SerializedBigDecimal import org.joda.time.DateTime /** - * P2P.org rewards history entry + * Rewards history entry * Historical reward information for account */ data class P2PEthPoolReward( diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolStaking.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolStaking.kt index b3da05663c..92dc1222cc 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolStaking.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolStaking.kt @@ -61,7 +61,7 @@ data class P2PEthPoolStaking( } /** - * Detailed vault information for P2P staking + * Detailed vault information for P2PEthPool staking */ data class P2PEthPoolVaultDetails( val vaultAddress: String, diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PStakingConfig.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolStakingConfig.kt similarity index 76% rename from domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PStakingConfig.kt rename to domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolStakingConfig.kt index 52c0f311e8..1e2949326b 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PStakingConfig.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolStakingConfig.kt @@ -1,11 +1,11 @@ package com.tangem.domain.staking.model.ethpool /** - * Configuration for P2P Ethereum staking network. + * Configuration for P2PEthPool Ethereum staking network. * * Change [USE_TESTNET] to switch between testnet and mainnet. */ -object P2PStakingConfig { +object P2PEthPoolStakingConfig { const val USE_TESTNET: Boolean = true diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolUnsignedTx.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolUnsignedTx.kt index 5a801b205a..c454b15ac4 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolUnsignedTx.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolUnsignedTx.kt @@ -3,7 +3,7 @@ package com.tangem.domain.staking.model.ethpool import com.tangem.domain.models.serialization.SerializedBigDecimal /** - * P2P.org unsigned transaction ready for signing + * Unsigned transaction ready for signing * Contains all necessary data for transaction signing */ data class P2PEthPoolUnsignedTx( diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/Yield.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/Yield.kt index fc79ef7a9d..e1cf32955b 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/Yield.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/Yield.kt @@ -2,6 +2,8 @@ package com.tangem.domain.staking.model.stakekit import com.tangem.domain.models.serialization.SerializedBigDecimal import com.tangem.domain.models.staking.YieldToken +import com.tangem.domain.staking.model.common.RewardInfo +import com.tangem.domain.staking.model.common.RewardType import kotlinx.serialization.Serializable @Serializable @@ -138,18 +140,6 @@ data class Yield( UNKNOWN, } } - - enum class RewardType { - APY, // compound rate - APR, // simple rate - UNKNOWN, - } - - @Serializable - data class RewardInfo( - val rate: SerializedBigDecimal, - val type: RewardType, - ) } @Serializable diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingOptionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingOptionsUseCase.kt index 3c5a7b4fe1..2573289397 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingOptionsUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingOptionsUseCase.kt @@ -12,11 +12,11 @@ import kotlinx.coroutines.launch /** * Use case for fetching all staking options from all providers - * Fetches both StakeKit yields and P2P vaults + * Fetches both StakeKit yields and P2PEthPool vaults */ class FetchStakingOptionsUseCase( private val stakeKitRepository: StakeKitRepository, - private val p2pRepository: P2PEthPoolRepository, + private val p2pEthPoolRepository: P2PEthPoolRepository, private val stakingErrorResolver: StakingErrorResolver, ) { suspend operator fun invoke(): Either { @@ -25,7 +25,7 @@ class FetchStakingOptionsUseCase( block = { coroutineScope { launch { stakeKitRepository.fetchYields() } - launch { p2pRepository.fetchVaults() } + launch { p2pEthPoolRepository.fetchVaults() } } }, catch = { stakingErrorResolver.resolve(it) }, diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingEntryInfoUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingEntryInfoUseCase.kt index ef77b03abb..fd42a7e74f 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingEntryInfoUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingEntryInfoUseCase.kt @@ -30,7 +30,7 @@ class GetStakingEntryInfoUseCase( symbol = symbol, ) } - is StakingOption.P2P -> { + is StakingOption.P2PEthPool -> { StakingEntryInfo( tokenSymbol = "ETH", ) diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/model/P2PEthPoolIntegration.kt b/domain/staking/src/main/java/com/tangem/domain/staking/model/P2PEthPoolIntegration.kt new file mode 100644 index 0000000000..97fc1c11cf --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/model/P2PEthPoolIntegration.kt @@ -0,0 +1,62 @@ +package com.tangem.domain.staking.model + +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.staking.YieldToken +import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault +import com.tangem.domain.staking.model.stakekit.Yield +import java.math.BigDecimal + +/** + * StakingIntegration implementation for P2PEthPool pooled staking. + * Converts P2PEthPoolVault data to the common StakingIntegration interface. + */ +class P2PEthPoolIntegration( + override val integrationId: StakingIntegrationID, + vaults: List, +) : StakingIntegration { + + // Basic + + override val token: YieldToken = YieldToken.ETH + + override val tokens: List = listOf(token) + + // Targets (vaults) + + override val targets: List = vaults.map { vault -> + vault.toStakingTarget() + } + + override val preferredTargets: List = targets + + override val areAllTargetsFull: Boolean = false + + // Enter/Exit Args + + override val isPartialAmountDisabled: Boolean = false + + override val enterMinimumAmount: BigDecimal = DEFAULT_MINIMUM_STAKE + + override val exitMinimumAmount: BigDecimal? = null + + override val enterArgs: Yield.Args.Enter? = null + + override val exitArgs: Yield.Args.Enter? = null + + // Metadata + + override val warmupPeriodDays: Int = 0 + + override val cooldownPeriodDays: Int = DEFAULT_COOLDOWN_DAYS + + override val rewardSchedule: Yield.Metadata.RewardSchedule = Yield.Metadata.RewardSchedule.DAY + + override val rewardClaiming: Yield.Metadata.RewardClaiming = Yield.Metadata.RewardClaiming.AUTO + + override fun getCurrentToken(rawCurrencyId: CryptoCurrency.RawID?): YieldToken = token + + companion object { + private const val DEFAULT_COOLDOWN_DAYS = 7 + private val DEFAULT_MINIMUM_STAKE = BigDecimal("0.01") + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/model/StakeKitIntegration.kt b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakeKitIntegration.kt new file mode 100644 index 0000000000..d6b3ad49e5 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakeKitIntegration.kt @@ -0,0 +1,60 @@ +package com.tangem.domain.staking.model + +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.staking.YieldToken +import com.tangem.domain.staking.model.stakekit.Yield +import java.math.BigDecimal + +/** + * StakingIntegration implementation for StakeKit. + * Delegates all calls to the underlying Yield object. + */ +class StakeKitIntegration( + override val integrationId: StakingIntegrationID, + private val yield: Yield, +) : StakingIntegration { + + // Basic + + override val token: YieldToken = yield.token + + override val tokens: List = yield.tokens + + // Targets (validators) + + override val targets: List = yield.validators.map { it.toStakingTarget() } + + override val preferredTargets: List = yield.preferredValidators.map { it.toStakingTarget() } + + override val areAllTargetsFull: Boolean = yield.allValidatorsFull + + // Enter/Exit Args + + override val isPartialAmountDisabled: Boolean = yield.args.enter.isPartialAmountDisabled + + override val enterMinimumAmount: BigDecimal? = + yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum + + override val exitMinimumAmount: BigDecimal? = + yield.args.exit?.args + ?.get(Yield.Args.ArgType.AMOUNT)?.minimum + + override val enterArgs: Yield.Args.Enter = yield.args.enter + + override val exitArgs: Yield.Args.Enter? = yield.args.exit + + // Metadata + + override val warmupPeriodDays: Int = yield.metadata.warmupPeriod.days + + override val cooldownPeriodDays: Int? = yield.metadata.cooldownPeriod?.days + + override val rewardSchedule: Yield.Metadata.RewardSchedule = yield.metadata.rewardSchedule + + override val rewardClaiming: Yield.Metadata.RewardClaiming = yield.metadata.rewardClaiming + + // Basic + + override fun getCurrentToken(rawCurrencyId: CryptoCurrency.RawID?): YieldToken = + tokens.firstOrNull { rawCurrencyId?.value == it.coinGeckoId } ?: token +} \ No newline at end of file diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingAvailability.kt b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingAvailability.kt similarity index 100% rename from domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingAvailability.kt rename to domain/staking/src/main/java/com/tangem/domain/staking/model/StakingAvailability.kt diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegration.kt b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegration.kt new file mode 100644 index 0000000000..a20e1d6ebb --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegration.kt @@ -0,0 +1,56 @@ +package com.tangem.domain.staking.model + +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.staking.YieldToken +import com.tangem.domain.staking.model.stakekit.Yield +import java.math.BigDecimal + +/** + * Strategy interface for staking integrations. + * Abstracts over StakeKit and P2PEthPool staking providers. + */ +// TODO p2p get rid of stakekit-specific models in StakingIntegration and implementors +interface StakingIntegration { + + // Basic + + val integrationId: StakingIntegrationID + + val token: YieldToken + + val tokens: List + + // Targets (validators or vaults) + + val targets: List + + val preferredTargets: List + + val areAllTargetsFull: Boolean + + // Enter/Exit Args + + val isPartialAmountDisabled: Boolean + + val enterMinimumAmount: BigDecimal? + + val exitMinimumAmount: BigDecimal? + + val enterArgs: Yield.Args.Enter? + + val exitArgs: Yield.Args.Enter? + + // Metadata + + val warmupPeriodDays: Int + + val cooldownPeriodDays: Int? + + val rewardSchedule: Yield.Metadata.RewardSchedule + + val rewardClaiming: Yield.Metadata.RewardClaiming + + // Basic + + fun getCurrentToken(rawCurrencyId: CryptoCurrency.RawID?): YieldToken +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegrationID.kt b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegrationID.kt index 1bf895cbcc..db33de7982 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegrationID.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegrationID.kt @@ -5,7 +5,7 @@ import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.blockchainsdk.utils.toMigratedCoinId import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network -import com.tangem.domain.staking.model.ethpool.P2PStakingConfig +import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig /** * Represents a staking integration identifier. @@ -97,16 +97,14 @@ sealed interface StakingIntegrationID { } /** - * Represents P2P staking integrations + * Represents P2PEthPool staking integration */ - enum class P2P : StakingIntegrationID { - EthereumPooled { - override val value: String = "p2p-ethereum-pooled" - override val blockchain: Blockchain - get() = if (P2PStakingConfig.USE_TESTNET) Blockchain.EthereumTestnet else Blockchain.Ethereum - override val networkId: String - get() = P2PStakingConfig.activeNetwork.stakingNetworkId - }, + object P2PEthPool : StakingIntegrationID { + override val value: String = "p2p-ethereum-pooled" + override val blockchain: Blockchain + get() = if (P2PEthPoolStakingConfig.USE_TESTNET) Blockchain.EthereumTestnet else Blockchain.Ethereum + override val networkId: String + get() = P2PEthPoolStakingConfig.activeNetwork.stakingNetworkId } // Polkadot { @@ -138,7 +136,7 @@ sealed interface StakingIntegrationID { /** List of all native staking integration IDs */ val entries: List by lazy { - StakeKit.Coin.entries + StakeKit.EthereumToken.entries + P2P.entries + StakeKit.Coin.entries + StakeKit.EthereumToken.entries + listOf(P2PEthPool) } /** @@ -152,9 +150,12 @@ sealed interface StakingIntegrationID { val blockchain = Blockchain.fromId(id = currencyId.rawNetworkId) return if (currencyId.contractAddress.isNullOrBlank()) { - // Order is not important — either P2P or Stakekit.Coin can be in any order - P2P.entries.firstOrNull { it.blockchain == blockchain } - ?: StakeKit.Coin.entries.firstOrNull { it.blockchain == blockchain } + // Order is not important — either P2PEthPool or Stakekit.Coin can be in any order + if (P2PEthPool.blockchain == blockchain) { + P2PEthPool + } else { + StakeKit.Coin.entries.firstOrNull { it.blockchain == blockchain } + } } else { StakeKit.EthereumToken.entries.firstOrNull { token -> token.blockchain == blockchain && diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingOption.kt b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingOption.kt similarity index 53% rename from domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingOption.kt rename to domain/staking/src/main/java/com/tangem/domain/staking/model/StakingOption.kt index 60f9674e3f..80702d4a7c 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingOption.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingOption.kt @@ -1,19 +1,18 @@ package com.tangem.domain.staking.model import com.tangem.domain.models.serialization.SerializedBigDecimal -import com.tangem.domain.models.staking.NetworkType import com.tangem.domain.models.staking.YieldToken import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault import com.tangem.domain.staking.model.stakekit.Yield /** * Represents a staking option from any provider - * Unified abstraction over StakeKit and P2P staking integrations + * Unified abstraction over StakeKit and P2PEthPool staking integrations */ sealed interface StakingOption { /** Unique identifier for the staking option */ - val integrationId: String + val integrationId: StakingIntegrationID /** Annual Percentage Yield */ val apy: SerializedBigDecimal @@ -28,34 +27,23 @@ sealed interface StakingOption { * StakeKit staking option * Wraps StakeKit Yield with all validator and metadata information */ - data class StakeKit(val yield: Yield) : StakingOption { - override val integrationId: String = yield.id + data class StakeKit( + override val integrationId: StakingIntegrationID.StakeKit, + val yield: Yield, + ) : StakingOption { override val apy: SerializedBigDecimal = yield.apy override val token: YieldToken = yield.token override val isAvailable: Boolean = yield.isAvailable } /** - * P2P pooled staking option - * Wraps P2P ETH Pool vault information + * P2PEthPool staking option + * Wraps P2PEthPool vault information */ - data class P2P(val vaults: List) : StakingOption { - override val integrationId: String = "p2p-ethereum-pooled" + data class P2PEthPool(val vaults: List) : StakingOption { + override val integrationId: StakingIntegrationID = StakingIntegrationID.P2PEthPool override val apy: SerializedBigDecimal = vaults.maxOf { it.apy } - override val token: YieldToken = createEthToken() + override val token: YieldToken = YieldToken.ETH override val isAvailable: Boolean = vaults.isNotEmpty() - - private fun createEthToken(): YieldToken { // TODO - return YieldToken( - name = "Ethereum", - network = NetworkType.ETHEREUM, - symbol = "ETH", - decimals = 18, - address = null, // Native token - coinGeckoId = "ethereum", - logoURI = null, - isPoints = false, - ) - } } } \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/P2PEthPoolRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/P2PEthPoolRepository.kt index 8a116ff9b6..2b3811a26a 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/P2PEthPoolRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/P2PEthPoolRepository.kt @@ -8,7 +8,7 @@ import com.tangem.domain.staking.model.ethpool.P2PEthPoolNetwork import com.tangem.domain.staking.model.ethpool.P2PEthPoolReward import com.tangem.domain.staking.model.ethpool.P2PEthPoolUnsignedTx import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault -import com.tangem.domain.staking.model.ethpool.P2PStakingConfig +import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig import com.tangem.domain.staking.model.stakekit.StakingError import kotlinx.coroutines.flow.Flow @@ -17,24 +17,24 @@ interface P2PEthPoolRepository { /** * Fetch and store available staking vaults * - * @param network P2P network (MAINNET or TESTNET) + * @param network P2PEthPool network (MAINNET or TESTNET) */ - suspend fun fetchVaults(network: P2PEthPoolNetwork = P2PStakingConfig.activeNetwork) + suspend fun fetchVaults(network: P2PEthPoolNetwork = P2PEthPoolStakingConfig.activeNetwork) /** * Get list of available staking vaults * - * @param network P2P network (MAINNET or TESTNET) + * @param network P2PEthPool network (MAINNET or TESTNET) * @return Either error or list of vaults with APY, capacity, fees */ suspend fun getVaults( - network: P2PEthPoolNetwork = P2PStakingConfig.activeNetwork, + network: P2PEthPoolNetwork = P2PEthPoolStakingConfig.activeNetwork, ): Either> /** * Create unsigned transaction for depositing ETH into a vault * - * @param network P2P network (MAINNET or TESTNET) + * @param network P2PEthPool network (MAINNET or TESTNET) * @param delegatorAddress User's wallet address * @param vaultAddress Vault contract address * @param amount Amount of ETH to deposit @@ -53,7 +53,7 @@ interface P2PEthPoolRepository { * Unstaking adds funds to exit queue. After ~1-4 days, use [createWithdrawTransaction] * to withdraw the funds. * - * @param network P2P network (MAINNET or TESTNET) + * @param network P2PEthPool network (MAINNET or TESTNET) * @param stakerPublicKey Staker's public key (note: API doc may have Bitcoin terminology) * @param stakeTransactionHash Original stake transaction hash * @return Either error or unsigned transaction @@ -69,7 +69,7 @@ interface P2PEthPoolRepository { * * Only works when funds are available (after exit queue wait period). * - * @param network P2P network (MAINNET or TESTNET) + * @param network P2PEthPool network (MAINNET or TESTNET) * @param stakerAddress User's wallet address * @return Either error or unsigned transaction with withdrawal tickets */ @@ -81,7 +81,7 @@ interface P2PEthPoolRepository { /** * Broadcast signed transaction to blockchain * - * @param network P2P network (MAINNET or TESTNET) + * @param network P2PEthPool network (MAINNET or TESTNET) * @param signedTransaction Signed transaction in hex format (with 0x prefix) * @return Either error or broadcast result with transaction hash */ @@ -95,7 +95,7 @@ interface P2PEthPoolRepository { * * Returns current stake, rewards, exit queue status, and available amounts * - * @param network P2P network (MAINNET or TESTNET) + * @param network P2PEthPool network (MAINNET or TESTNET) * @param delegatorAddress User's wallet address * @param vaultAddress Vault contract address * @return Either error or account info @@ -109,7 +109,7 @@ interface P2PEthPoolRepository { /** * Get rewards history for account and vault * - * @param network P2P network (MAINNET or TESTNET) + * @param network P2PEthPool network (MAINNET or TESTNET) * @param delegatorAddress User's wallet address * @param vaultAddress Vault contract address * @param period Optional period filter in days (30, 60, or 90) @@ -123,17 +123,27 @@ interface P2PEthPoolRepository { ): Either> /** - * Check P2P staking availability by finding public vault + * Get flow of cached vaults. * - * @return Flow of StakingAvailability - Available with StakingOption.P2P if public vault found, + * This returns vaults from the local cache/store. + * Call [fetchVaults] first to populate the cache from the network. + * + * @return Flow of cached vaults list + */ + fun getVaultsFlow(): Flow> + + /** + * Check P2PEthPool staking availability by finding public vault + * + * @return Flow of StakingAvailability - Available with StakingOption.P2PEthPool if public vault found, * TemporaryUnavailable if not found or vaults empty */ fun getStakingAvailability(): Flow /** - * Check P2P staking availability synchronously + * Check P2PEthPool staking availability synchronously * - * @return StakingAvailability - Available with StakingOption.P2P if public vault found, + * @return StakingAvailability - Available with StakingOption.P2PEthPool if public vault found, * TemporaryUnavailable if not found or vaults empty */ suspend fun getStakingAvailabilitySync(): StakingAvailability diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakeKitRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakeKitRepository.kt index a12b108ca4..e614a685bc 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakeKitRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakeKitRepository.kt @@ -8,6 +8,7 @@ import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo +import com.tangem.domain.staking.model.StakingIntegrationID import com.tangem.domain.models.staking.NetworkType import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingAction @@ -24,9 +25,17 @@ interface StakeKitRepository { fun getEnabledYields(): Flow> - fun getStakingAvailability(rawCurrencyId: CryptoCurrency.RawID, symbol: String): Flow + fun getStakingAvailability( + integrationId: StakingIntegrationID.StakeKit, + rawCurrencyId: CryptoCurrency.RawID, + symbol: String, + ): Flow - suspend fun getStakingAvailabilitySync(rawCurrencyId: CryptoCurrency.RawID, symbol: String): StakingAvailability + suspend fun getStakingAvailabilitySync( + integrationId: StakingIntegrationID.StakeKit, + rawCurrencyId: CryptoCurrency.RawID, + symbol: String, + ): StakingAvailability suspend fun getEntryInfo(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): StakingEntryInfo diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/usecase/StakingApyFlowUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/usecase/StakingApyFlowUseCase.kt index 22f83164b2..357ec88fa5 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/usecase/StakingApyFlowUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/usecase/StakingApyFlowUseCase.kt @@ -2,35 +2,50 @@ package com.tangem.domain.staking.usecase import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.toCoinId -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingTarget +import com.tangem.domain.staking.model.toStakingTarget +import com.tangem.domain.staking.repositories.P2PEthPoolRepository import com.tangem.domain.staking.repositories.StakeKitRepository import com.tangem.domain.staking.toggles.StakingFeatureToggles import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.combine /** - * Emits a map of Validators values per currency for staking. + * Emits a map of StakingTarget values per currency for staking. * * Return map: - * - key: currency staking key (network.backendId + "_" + symbol) - * - value: validators + * - key: currency staking key (coinGeckoId + "_" + symbol) + * - value: list of staking targets (validators or vaults) */ class StakingApyFlowUseCase( private val stakeKitRepository: StakeKitRepository, + private val p2pEthPoolRepository: P2PEthPoolRepository, private val stakingFeatureToggles: StakingFeatureToggles, ) { - operator fun invoke(): Flow>> { - return stakeKitRepository.getEnabledYields() - .map { yields -> - yields.filterNot { yield -> - val isCardanoYield = yield.token.coinGeckoId == Blockchain.Cardano.toCoinId() - isCardanoYield && !stakingFeatureToggles.isCardanoStakingEnabled - }.associate { yield -> - val key = "${yield.token.coinGeckoId}_${yield.token.symbol}" - val apy = yield.validators - key to apy - } + operator fun invoke(): Flow>> { + return combine( + stakeKitRepository.getEnabledYields(), + p2pEthPoolRepository.getVaultsFlow(), + ) { yields, p2pVaults -> + val stakeKitMap = yields.filterNot { yield -> + val isCardanoYield = yield.token.coinGeckoId == Blockchain.Cardano.toCoinId() + isCardanoYield && !stakingFeatureToggles.isCardanoStakingEnabled + }.associate { yield -> + val key = "${yield.token.coinGeckoId}_${yield.token.symbol}" + val targets = yield.validators.map { it.toStakingTarget() } + key to targets } + + val p2pMap = if (p2pVaults.isNotEmpty()) { + val ethKey = "${Blockchain.Ethereum.toCoinId()}_${Blockchain.Ethereum.currency}" + val targets = p2pVaults.map { it.toStakingTarget() } + mapOf(ethKey to targets) + } else { + emptyMap() + } + + stakeKitMap + p2pMap + } } } \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/utils/StakingBalanceExt.kt b/domain/staking/src/main/java/com/tangem/domain/staking/utils/StakingBalanceExt.kt index 75b52aa6f4..66c7a151db 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/utils/StakingBalanceExt.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/utils/StakingBalanceExt.kt @@ -7,7 +7,7 @@ import java.math.BigDecimal /** * Provider-agnostic extension to get total balance including rewards. - * Works for both StakeKit and P2P providers. + * Works for both StakeKit and P2PEthPool providers. * * Returns sum of all staking-related balances including rewards * (staked + unstaking + withdrawable + rewards). @@ -18,7 +18,7 @@ import java.math.BigDecimal fun StakingBalance.Data.getTotalWithRewardsStakingBalance(blockchainId: String): BigDecimal { return when (this) { is StakingBalance.Data.StakeKit -> getTotalWithRewardsStakingBalanceStakeKit(blockchainId) - is StakingBalance.Data.P2P -> { + is StakingBalance.Data.P2PEthPool -> { val rewards = totalRewards if (BlockchainUtils.isIncludeStakingTotalBalance(blockchainId)) { totalStaked + unstakingAmount + withdrawableAmount + rewards @@ -31,7 +31,7 @@ fun StakingBalance.Data.getTotalWithRewardsStakingBalance(blockchainId: String): /** * Provider-agnostic extension to get total staking balance excluding rewards. - * Works for both StakeKit and P2P providers. + * Works for both StakeKit and P2PEthPool providers. * * Returns sum of all staking-related balances (staked + unstaking + withdrawable) * excluding rewards. @@ -39,7 +39,7 @@ fun StakingBalance.Data.getTotalWithRewardsStakingBalance(blockchainId: String): fun StakingBalance.Data.getTotalStakingBalance(blockchainId: String): BigDecimal { return when (this) { is StakingBalance.Data.StakeKit -> getTotalStakingBalanceStakeKit(blockchainId) - is StakingBalance.Data.P2P -> totalStaked + unstakingAmount + withdrawableAmount + is StakingBalance.Data.P2PEthPool -> totalStaked + unstakingAmount + withdrawableAmount } } diff --git a/domain/staking/src/test/kotlin/com/tangem/domain/staking/StakingIdFactoryTest.kt b/domain/staking/src/test/kotlin/com/tangem/domain/staking/StakingIdFactoryTest.kt index 996ea8cca0..71a5d271ed 100644 --- a/domain/staking/src/test/kotlin/com/tangem/domain/staking/StakingIdFactoryTest.kt +++ b/domain/staking/src/test/kotlin/com/tangem/domain/staking/StakingIdFactoryTest.kt @@ -149,8 +149,8 @@ internal class StakingIdFactoryTest { expected = createStakingId(integrationId = StakingIntegrationID.StakeKit.Coin.Cardano), ), CreateModel( - currencyId = createCurrencyId(blockchain = StakingIntegrationID.P2P.EthereumPooled.blockchain), - expected = createStakingId(integrationId = StakingIntegrationID.P2P.EthereumPooled), + currencyId = createCurrencyId(blockchain = StakingIntegrationID.P2PEthPool.blockchain), + expected = createStakingId(integrationId = StakingIntegrationID.P2PEthPool), ), CreateModel( currencyId = CryptoCurrency.ID.fromValue( diff --git a/domain/staking/src/test/kotlin/com/tangem/domain/staking/StakingIntegrationIDTest.kt b/domain/staking/src/test/kotlin/com/tangem/domain/staking/StakingIntegrationIDTest.kt index 745af46793..c62e943341 100644 --- a/domain/staking/src/test/kotlin/com/tangem/domain/staking/StakingIntegrationIDTest.kt +++ b/domain/staking/src/test/kotlin/com/tangem/domain/staking/StakingIntegrationIDTest.kt @@ -38,17 +38,6 @@ class StakingIntegrationIDTest { Truth.assertThat(actual).hasSize(expected) } - @Test - fun `all P2P blockchains are unique`() { - // Act - val actual = StakingIntegrationID.P2P.entries - .distinctBy(StakingIntegrationID.P2P::blockchain) - - // Assert - val expected = StakingIntegrationID.P2P.entries.size - Truth.assertThat(actual).hasSize(expected) - } - @Test fun `all sub blockchains are unique`() { // Act @@ -151,8 +140,8 @@ class StakingIntegrationIDTest { expected = StakingIntegrationID.StakeKit.Coin.Cardano, ), CreateModel( - currencyId = createCurrencyId(blockchain = StakingIntegrationID.P2P.EthereumPooled.blockchain), - expected = StakingIntegrationID.P2P.EthereumPooled, + currencyId = createCurrencyId(blockchain = StakingIntegrationID.P2PEthPool.blockchain), + expected = StakingIntegrationID.P2PEthPool, ), CreateModel( currencyId = CryptoCurrency.ID.fromValue(value = "token⟨ETH⟩polygon-ecosystem-token⚓1234567890"), diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactory.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactory.kt index 3c2726eb06..d58781bda3 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactory.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactory.kt @@ -267,7 +267,7 @@ object CryptoCurrencyStatusFactory { null } } - is StakingBalance.Data.P2P -> { + is StakingBalance.Data.P2PEthPool -> { // TODO p2p val isCurrentAddressStaking = stakingBalance.stakingId.address == address.defaultAddress.value if (isCurrentAddressStaking) stakingBalance else null diff --git a/features/markets/impl/build.gradle.kts b/features/markets/impl/build.gradle.kts index 24d38f3109..1928a996ed 100644 --- a/features/markets/impl/build.gradle.kts +++ b/features/markets/impl/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { implementation(projects.domain.markets) implementation(projects.domain.onramp.models) implementation(projects.domain.staking.models) + implementation(projects.domain.staking) implementation(projects.domain.tokens) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt index 7561518e9f..dc82bfb8a8 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt @@ -174,7 +174,7 @@ internal class TokenActionsHandler @AssistedInject constructor( AppRoute.Staking( userWalletId = cryptoCurrencyData.userWallet.walletId, cryptoCurrency = cryptoCurrencyData.status.currency, - yieldId = option.integrationId, + integrationId = option.integrationId, ), ) } diff --git a/features/staking/api/build.gradle.kts b/features/staking/api/build.gradle.kts index 749f851fc5..74149b41f2 100644 --- a/features/staking/api/build.gradle.kts +++ b/features/staking/api/build.gradle.kts @@ -16,7 +16,7 @@ dependencies { /** Domain models */ api(projects.domain.models) - implementation(projects.domain.staking.models) + implementation(projects.domain.staking) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) diff --git a/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/StakingComponent.kt b/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/StakingComponent.kt index bf5f6a1b9d..56a53b0333 100644 --- a/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/StakingComponent.kt +++ b/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/StakingComponent.kt @@ -1,5 +1,6 @@ package com.tangem.features.staking.api +import com.tangem.domain.staking.model.StakingIntegrationID import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.domain.models.currency.CryptoCurrency @@ -10,7 +11,7 @@ interface StakingComponent : ComposableContentComponent { data class Params( val userWalletId: UserWalletId, val cryptoCurrency: CryptoCurrency, - val yieldId: String, + val integrationId: StakingIntegrationID, ) interface Factory : ComponentFactory diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt index cdd95ace06..5b97de21cf 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt @@ -18,10 +18,10 @@ internal class StakingAnalyticSender( fun initialInfoScreen(value: StakingUiState) { val initialInfoState = value.initialInfoState as? StakingStates.InitialInfoState.Data - val validatorState = initialInfoState?.yieldBalance as? InnerYieldBalanceState.Data - val validatorCount = validatorState?.balances - ?.filterNot { it.validator?.address.isNullOrBlank() } - ?.distinctBy { it.validator?.address } + val balanceState = initialInfoState?.yieldBalance as? InnerYieldBalanceState.Data + val validatorCount = balanceState?.balances + ?.filterNot { it.target?.address.isNullOrBlank() } + ?.distinctBy { it.target?.address } ?.size ?: 0 analyticsEventHandler.send( @@ -34,7 +34,7 @@ internal class StakingAnalyticSender( fun confirmationScreen(value: StakingUiState) { val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data val validatorState = value.validatorState as? StakingStates.ValidatorState.Data - val validatorName = validatorState?.chosenValidator?.name ?: return + val validatorName = validatorState?.chosenTarget?.name ?: return if (confirmationState?.innerState == InnerConfirmationStakingState.COMPLETED) return @@ -80,7 +80,7 @@ internal class StakingAnalyticSender( fun sendTransactionStakingAnalytics(value: StakingUiState, cryptoCurrencyStatus: CryptoCurrencyStatus) { val validatorState = value.validatorState as? StakingStates.ValidatorState.Data - val validatorName = validatorState?.chosenValidator?.name ?: return + val validatorName = validatorState?.chosenTarget?.name ?: return analyticsEventHandler.send( Basic.TransactionSent( @@ -102,7 +102,7 @@ internal class StakingAnalyticSender( fun sendTransactionStakingClickedAnalytics(value: StakingUiState) { val validatorState = value.validatorState as? StakingStates.ValidatorState.Data - val validatorName = validatorState?.chosenValidator?.name ?: return + val validatorName = validatorState?.chosenTarget?.name ?: return analyticsEventHandler.send( StakingAnalyticsEvent.ButtonAction( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/deeplink/DefaultStakingDeepLinkHandler.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/deeplink/DefaultStakingDeepLinkHandler.kt index eaa7af9ae5..9c90294738 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/deeplink/DefaultStakingDeepLinkHandler.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/deeplink/DefaultStakingDeepLinkHandler.kt @@ -1,6 +1,5 @@ package com.tangem.features.staking.impl.deeplink -import arrow.core.getOrElse import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.common.routing.deeplink.DeeplinkConst.NETWORK_ID_KEY @@ -8,7 +7,6 @@ import com.tangem.common.routing.deeplink.DeeplinkConst.TOKEN_ID_KEY import com.tangem.common.routing.deeplink.DeeplinkConst.WALLET_ID_KEY import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.staking.GetStakingAvailabilityUseCase -import com.tangem.domain.staking.GetYieldUseCase import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier @@ -28,7 +26,6 @@ internal class DefaultStakingDeepLinkHandler @AssistedInject constructor( private val appRouter: AppRouter, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, - private val getYieldUseCase: GetYieldUseCase, private val getStakingAvailabilityUseCase: GetStakingAvailabilityUseCase, ) : StakingDeepLinkHandler { @@ -73,19 +70,13 @@ internal class DefaultStakingDeepLinkHandler @AssistedInject constructor( return@launch } - val isStakingEnabled = getStakingAvailabilityUseCase.invokeSync( + val availability = getStakingAvailabilityUseCase.invokeSync( userWalletId = selectedUserWalletId, cryptoCurrency = cryptoCurrency, ).getOrNull() - if (isStakingEnabled !is StakingAvailability.Available) { - return@launch - } - - val yield = getYieldUseCase.invoke( - cryptoCurrencyId = cryptoCurrency.id, - symbol = cryptoCurrency.symbol, - ).getOrElse { + val option = (availability as? StakingAvailability.Available)?.option + if (option == null) { Timber.e("Staking is unavailable for ${cryptoCurrency.name}") return@launch } @@ -94,7 +85,7 @@ internal class DefaultStakingDeepLinkHandler @AssistedInject constructor( AppRoute.Staking( userWalletId = selectedUserWalletId, cryptoCurrency = cryptoCurrency, - yieldId = yield.id, + integrationId = option.integrationId, ), ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt index 7e79b03b94..9d21d90396 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt @@ -4,7 +4,7 @@ import com.tangem.common.ui.amountScreen.AmountScreenClickIntents import com.tangem.common.ui.bottomsheet.permission.state.ApproveType import com.tangem.common.ui.notifications.NotificationUM import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingTarget import com.tangem.features.staking.impl.presentation.state.BalanceState import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType import java.math.BigDecimal @@ -35,7 +35,7 @@ internal interface StakingClickIntents : AmountScreenClickIntents { fun openValidators() - fun onValidatorSelect(validator: Yield.Validator) + fun onTargetSelect(target: StakingTarget) fun openRewardsValidators() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt index fd67cc140c..a19ebcc6c7 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt @@ -45,11 +45,15 @@ import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.staking.* import com.tangem.domain.staking.analytics.StakeScreenSource import com.tangem.domain.staking.analytics.StakingAnalyticsEvent +import com.tangem.domain.staking.model.P2PEthPoolIntegration +import com.tangem.domain.staking.model.StakeKitIntegration import com.tangem.domain.staking.model.StakingApproval +import com.tangem.domain.staking.model.StakingIntegration import com.tangem.domain.staking.model.StakingIntegrationID +import com.tangem.domain.staking.model.StakingTarget import com.tangem.domain.staking.model.stakekit.StakingError -import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingAction +import com.tangem.domain.staking.repositories.P2PEthPoolRepository import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction import com.tangem.domain.staking.utils.getValidatorsCount @@ -134,6 +138,7 @@ internal class StakingModel @Inject constructor( private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, private val getActionsUseCase: GetActionsUseCase, private val getYieldUseCase: GetYieldUseCase, + private val p2pEthPoolRepository: P2PEthPoolRepository, private val checkAccountInitializedUseCase: CheckAccountInitializedUseCase, private val createTransferTransactionUseCase: CreateTransferTransactionUseCase, private val getFeeUseCase: GetFeeUseCase, @@ -164,9 +169,18 @@ internal class StakingModel @Inject constructor( private val cryptoCurrencyId: CryptoCurrency.ID = params.cryptoCurrency.id private val userWalletId: UserWalletId = params.userWalletId - private val yield: Yield = runBlocking { - getYieldUseCase(params.yieldId).getOrElse { - error("yield must be not null") + private val integration: StakingIntegration = runBlocking { + when (val integrationId = params.integrationId) { + is StakingIntegrationID.StakeKit -> { + val yield = getYieldUseCase(integrationId.value).getOrElse { + error("yield must be not null") + } + StakeKitIntegration(integrationId, yield) + } + StakingIntegrationID.P2PEthPool -> { + val vaults = p2pEthPoolRepository.getVaults().getOrElse { emptyList() } + P2PEthPoolIntegration(integrationId, vaults) + } } } @@ -194,7 +208,7 @@ internal class StakingModel @Inject constructor( return invalidatePendingTransactionsUseCase( balanceItems = stakeKitBalance?.balance?.items.orEmpty(), stakingActions = stakingActions, - token = yield.token, + token = integration.token, ).getOrElse { emptyList() } } @@ -205,7 +219,7 @@ internal class StakingModel @Inject constructor( stakingBalanceUpdater.create( cryptoCurrencyStatus, userWallet, - yield, + integration, ) } @@ -213,7 +227,7 @@ internal class StakingModel @Inject constructor( stakingFeeTransactionLoader.create( cryptoCurrencyStatus = cryptoCurrencyStatus, userWallet = userWallet, - yield = yield, + integration = integration, ) } @@ -221,7 +235,7 @@ internal class StakingModel @Inject constructor( stakingTransactionLoader.create( cryptoCurrencyStatus = cryptoCurrencyStatus, userWallet = userWallet, - yield = yield, + integration = integration, isAmountSubtractAvailable = isAmountSubtractAvailable, ) } @@ -280,7 +294,7 @@ internal class StakingModel @Inject constructor( val hasNoYieldBalanceData = cryptoCurrencyStatus.value.stakingBalance !is StakingBalance.Data.StakeKit when { - isInitialInfoStep && noBalanceState && yield.allValidatorsFull && hasNoYieldBalanceData -> { + isInitialInfoStep && noBalanceState && integration.areAllTargetsFull && hasNoYieldBalanceData -> { stakingEventFactory.createStakingValidatorsUnavailableAlert() return@launch } @@ -293,12 +307,12 @@ internal class StakingModel @Inject constructor( cryptoCurrencyStatus = cryptoCurrencyStatus, stakingApproval = stakingApproval, stakingAllowance = stakingAllowance, - yieldArgs = yield.args, + integration = integration, ).let(::add) - if (yield.args.enter.isPartialAmountDisabled) { + if (integration.isPartialAmountDisabled) { ValidatorSelectChangeTransformer( - selectedValidator = yield.preferredValidators.firstOrNull(), - yield = yield, + selectedTarget = integration.preferredTargets.firstOrNull(), + integration = integration, ).let(::add) SetAmountDataTransformer( clickIntents = this@StakingModel, @@ -313,7 +327,7 @@ internal class StakingModel @Inject constructor( cryptoCurrencyStatus = cryptoCurrencyStatus, minimumTransactionAmount = minimumTransactionAmount, actionType = uiState.value.actionType, - yield = yield, + integration = integration, ).let(::add) } } @@ -327,7 +341,7 @@ internal class StakingModel @Inject constructor( override fun getFee() { stateController.update( SetConfirmationStateLoadingTransformer( - yield = yield, + integration = integration, appCurrency = appCurrency, cryptoCurrency = cryptoCurrencyStatus.currency, ), @@ -479,7 +493,7 @@ internal class StakingModel @Inject constructor( } override fun onAmountEnterClick() { - if (yield.preferredValidators.isEmpty()) { + if (integration.preferredTargets.isEmpty()) { stateController.updateEvent( StakingEvent.ShowAlert(StakingAlertUM.NoAvailableValidators), ) @@ -487,8 +501,8 @@ internal class StakingModel @Inject constructor( if (uiState.value.actionType is StakingActionCommonType.Enter) { stateController.updateAll( ValidatorSelectChangeTransformer( - selectedValidator = null, - yield = yield, + selectedTarget = null, + integration = integration, ), ) } @@ -502,7 +516,7 @@ internal class StakingModel @Inject constructor( cryptoCurrencyStatus = cryptoCurrencyStatus, minimumTransactionAmount = minimumTransactionAmount, value = value, - yield = yield, + integration = integration, ), ) } @@ -518,7 +532,7 @@ internal class StakingModel @Inject constructor( cryptoCurrencyStatus = cryptoCurrencyStatus, minimumTransactionAmount = minimumTransactionAmount, actionType = uiState.value.actionType, - yield = yield, + integration = integration, ), ) } @@ -539,16 +553,16 @@ internal class StakingModel @Inject constructor( stakingStateRouter.showValidators() } - override fun onValidatorSelect(validator: Yield.Validator) { + override fun onTargetSelect(target: StakingTarget) { analyticsEventHandler.send( StakingAnalyticsEvent.ValidatorChosen( - validator = validator.name, + validator = target.name, ), ) stateController.update( ValidatorSelectChangeTransformer( - selectedValidator = validator, - yield = yield, + selectedTarget = target, + integration = integration, ), ) } @@ -606,7 +620,7 @@ internal class StakingModel @Inject constructor( balanceType = activeStake.type, pendingActions = activeStake.pendingActions, balanceState = activeStake, - validator = activeStake.validator, + target = activeStake.target, amountValue = activeStake.cryptoValue, ) onNextClick(activeStake) @@ -619,7 +633,7 @@ internal class StakingModel @Inject constructor( balanceType = activeStake.type, pendingAction = action, balanceState = activeStake, - validator = activeStake.validator, + target = activeStake.target, amountValue = activeStake.cryptoValue, ) stateController.update(DismissBottomSheetStateTransformer) @@ -788,7 +802,7 @@ internal class StakingModel @Inject constructor( isSubtractAvailable = isAmountSubtractAvailable, feeError = feeError, stakingError = stakingError, - yield = yield, + integration = integration, ), ) } @@ -911,7 +925,7 @@ internal class StakingModel @Inject constructor( val validatorState = uiState.value.validatorState as? StakingStates.ValidatorState.Data val feeState = confirmationState?.feeState as? FeeState.Content - val validator = validatorState?.chosenValidator + val target = validatorState?.chosenTarget val feeAmount = feeState?.fee?.amount val amount = amountState?.amountTextField?.cryptoAmount saveBlockchainErrorUseCase( @@ -919,7 +933,7 @@ internal class StakingModel @Inject constructor( errorMessage = errorMessage, blockchainId = network.rawId, derivationPath = network.derivationPath.value, - destinationAddress = validator?.address.orEmpty(), + destinationAddress = target?.address.orEmpty(), tokenSymbol = (cryptoCurrencyStatus.currency as? CryptoCurrency.Token)?.symbol, amount = amount?.run { value?.toPlainString() + currencySymbol }.orEmpty(), fee = feeAmount?.run { value?.toPlainString() + currencySymbol }.orEmpty(), @@ -928,7 +942,7 @@ internal class StakingModel @Inject constructor( val email = FeedbackEmailType.StakingProblem( walletMetaInfo = metaInfo, - validatorName = validator?.name, + validatorName = target?.name, transactionTypes = transactionsInProgress.map { it.type.name }, unsignedTransactions = transactionsInProgress.map { it.unsignedTransaction }, ) @@ -1229,7 +1243,7 @@ internal class StakingModel @Inject constructor( stateController.updateAll( SetInitialDataStateTransformer( clickIntents = this@StakingModel, - yield = yield, + integration = integration, isAnyTokenStaked = isAnyTokenStaked, cryptoCurrencyStatus = status, userWalletProvider = Provider { userWallet }, @@ -1248,7 +1262,7 @@ internal class StakingModel @Inject constructor( balanceState: BalanceState, pendingActions: ImmutableList = persistentListOf(), pendingAction: PendingAction? = pendingActions.firstOrNull(), - validator: Yield.Validator?, + target: StakingTarget?, amountValue: String, ) { stateController.updateAll( @@ -1261,11 +1275,11 @@ internal class StakingModel @Inject constructor( pendingActions = pendingActions, pendingAction = pendingAction, stakingAllowance = stakingAllowance, - yieldArgs = yield.args, + integration = integration, ), ValidatorSelectChangeTransformer( - selectedValidator = validator, - yield = yield, + selectedTarget = target, + integration = integration, ), SetAmountDataTransformer( clickIntents = this, @@ -1280,7 +1294,7 @@ internal class StakingModel @Inject constructor( cryptoCurrencyStatus = cryptoCurrencyStatus, value = amountValue, minimumTransactionAmount = minimumTransactionAmount, - yield = yield, + integration = integration, ), ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt index 4be332b317..fa590eb0bc 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt @@ -6,7 +6,7 @@ import com.tangem.domain.models.staking.BalanceType import com.tangem.domain.models.staking.PendingAction import com.tangem.domain.models.staking.PendingActionConstraints import com.tangem.domain.models.staking.RewardBlockType -import com.tangem.domain.staking.model.stakekit.* +import com.tangem.domain.staking.model.StakingTarget import kotlinx.collections.immutable.ImmutableList import java.math.BigDecimal @@ -35,10 +35,10 @@ internal data class BalanceState( val fiatAmount: BigDecimal?, val formattedFiatAmount: TextReference, val rawCurrencyId: String?, - val validator: Yield.Validator?, + val target: StakingTarget?, val pendingActions: ImmutableList, val isPending: Boolean, - val validatorAddress: String?, + val targetAddress: String?, ) @Immutable diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt index 56f8f60341..698d8f8181 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt @@ -10,7 +10,7 @@ import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig import com.tangem.domain.models.staking.PendingAction -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingTarget import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType import com.tangem.features.staking.impl.presentation.state.events.StakingEvent @@ -97,9 +97,9 @@ internal sealed class StakingStates { override val isPrimaryButtonEnabled: Boolean, override val isClickable: Boolean, val isVisibleOnConfirmation: Boolean, - val chosenValidator: Yield.Validator, - val activeValidator: Yield.Validator?, - val availableValidators: List, + val chosenTarget: StakingTarget, + val activeTarget: StakingTarget?, + val availableTargets: List, ) : ValidatorState() data class Empty( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt index 20230aecef..1b4a71149b 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt @@ -12,7 +12,7 @@ import com.tangem.domain.models.staking.BalanceType import com.tangem.domain.models.staking.BalanceType.Companion.isClickable import com.tangem.domain.models.staking.StakingBalance import com.tangem.domain.models.staking.action.StakingActionType -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingIntegration import com.tangem.domain.staking.utils.getRewardStakingBalance import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.state.BalanceState @@ -29,24 +29,24 @@ import java.util.Calendar internal class BalanceItemConverter( private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val appCurrencyProvider: Provider, - private val yield: Yield, + private val integration: StakingIntegration, ) : Converter { override fun convert(value: BalanceItem): BalanceState? { val appCurrency = appCurrencyProvider() val cryptoCurrency = cryptoCurrencyStatus.currency - val validator = yield.validators.firstOrNull { + val target = integration.targets.firstOrNull { value.validatorAddress?.contains(it.address, ignoreCase = true) == true } val cryptoAmount = value.getBalanceValue() val fiatAmount = cryptoCurrencyStatus.value.fiatRate?.times(cryptoAmount) - val title = value.type.getTitle(validator?.name) + val title = value.type.getTitle(target?.name) return title?.let { BalanceState( groupId = value.groupId, - validator = validator, + target = target, title = title, subtitle = getSubtitle(value), type = value.type, @@ -68,7 +68,7 @@ internal class BalanceItemConverter( pendingActions = value.pendingActions.toPersistentList(), isClickable = value.isClickable(), isPending = value.isPending, - validatorAddress = value.validatorAddress, + targetAddress = value.validatorAddress, ) } } @@ -108,7 +108,7 @@ internal class BalanceItemConverter( resourceReference(R.string.staking_tap_to_unlock) } BalanceType.PREPARING -> { - val warmupPeriod = yield.metadata.warmupPeriod.days + val warmupPeriod = integration.warmupPeriodDays combinedReference( resourceReference(R.string.staking_details_warmup_period), stringReference(" "), @@ -124,7 +124,7 @@ internal class BalanceItemConverter( } private fun getUnbondingDate(date: Instant?): TextReference? { - val unbondingPeriod = yield.metadata.cooldownPeriod?.days ?: return null + val unbondingPeriod = integration.cooldownPeriodDays ?: return null if (date == null) { return combinedReference( resourceReference(R.string.staking_details_unbonding_period), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt index 68e09c30c5..e81cb4512e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt @@ -10,7 +10,8 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.staking.BalanceItem import com.tangem.domain.models.staking.BalanceType import com.tangem.domain.models.staking.StakingBalance -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingIntegration +import com.tangem.domain.staking.model.StakingTarget import com.tangem.features.staking.impl.presentation.state.BalanceState import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.utils.Provider @@ -21,7 +22,7 @@ import java.math.BigDecimal internal class RewardsValidatorStateConverter( private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val appCurrencyProvider: Provider, - private val yield: Yield, + private val integration: StakingIntegration, ) : Converter { override fun convert(value: Unit): StakingStates.RewardsValidatorsState { val stakingBalance = cryptoCurrencyStatus.value.stakingBalance @@ -42,13 +43,13 @@ internal class RewardsValidatorStateConverter( private fun List.mapRewardBalances(cryptoCurrencyStatus: CryptoCurrencyStatus) = this.mapNotNull { balance -> - val validator = yield.validators.firstOrNull { + val target = integration.targets.firstOrNull { it.address.contains(balance.validatorAddress.orEmpty(), ignoreCase = true) } val cryptoValue = balance.amount val fiatValue = cryptoCurrencyStatus.value.fiatRate?.times(cryptoValue) - validator?.toBalanceState( + target?.toBalanceState( balance = balance, cryptoCurrencyStatus = cryptoCurrencyStatus, cryptoValue = cryptoValue, @@ -56,7 +57,7 @@ internal class RewardsValidatorStateConverter( ) } - private fun Yield.Validator.toBalanceState( + private fun StakingTarget.toBalanceState( balance: BalanceItem, cryptoCurrencyStatus: CryptoCurrencyStatus, cryptoValue: BigDecimal, @@ -80,7 +81,7 @@ internal class RewardsValidatorStateConverter( return BalanceState( groupId = balance.groupId, - validator = this, + target = this, title = stringReference(this.name), subtitle = null, cryptoValue = cryptoValue.parseBigDecimal(cryptoCurrency.decimals), @@ -93,7 +94,7 @@ internal class RewardsValidatorStateConverter( isClickable = true, type = balance.type, isPending = balance.isPending, - validatorAddress = balance.validatorAddress, + targetAddress = balance.validatorAddress, ) } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt index 8c118c5572..1c1b278c34 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt @@ -11,7 +11,7 @@ import com.tangem.domain.models.staking.BalanceType import com.tangem.domain.models.staking.RewardBlockType import com.tangem.domain.models.staking.StakingBalance import com.tangem.domain.models.staking.action.StakingActionType -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingIntegration import com.tangem.domain.staking.utils.getRewardStakingBalance import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState import com.tangem.features.staking.impl.presentation.state.YieldReward @@ -25,11 +25,11 @@ internal class YieldBalancesConverter( private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val appCurrencyProvider: Provider, private val balancesToShowProvider: Provider>, - private val yield: Yield, + private val integration: StakingIntegration, ) : Converter { private val balanceItemConverter by lazy(LazyThreadSafetyMode.NONE) { - BalanceItemConverter(cryptoCurrencyStatus, appCurrencyProvider, yield) + BalanceItemConverter(cryptoCurrencyStatus, appCurrencyProvider, integration) } override fun convert(value: Unit): InnerYieldBalanceState { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt index 977585131e..4c8bc0ceae 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt @@ -4,7 +4,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.staking.FetchActionsUseCase import com.tangem.domain.staking.FetchStakingYieldBalanceUseCase -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingIntegration import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus import com.tangem.domain.tokens.FetchCurrencyStatusUseCase import com.tangem.domain.tokens.FetchPendingTransactionsUseCase @@ -27,7 +27,7 @@ internal class StakingBalanceUpdater @AssistedInject constructor( @DelayedWork private val coroutineScope: CoroutineScope, @Assisted private val userWallet: UserWallet, @Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus, - @Assisted private val yield: Yield, + @Assisted private val integration: StakingIntegration, ) { fun updateAfterTransaction() { coroutineScope.launch { @@ -105,7 +105,7 @@ internal class StakingBalanceUpdater @AssistedInject constructor( fetchActionsUseCase( userWalletId = userWallet.walletId, cryptoCurrency = cryptoCurrencyStatus.currency, - networkType = yield.token.network, + networkType = integration.token.network, stakingActionStatus = StakingActionStatus.PROCESSING, ) } @@ -115,7 +115,7 @@ internal class StakingBalanceUpdater @AssistedInject constructor( fun create( cryptoCurrencyStatus: CryptoCurrencyStatus, userWallet: UserWallet, - yield: Yield, + integration: StakingIntegration, ): StakingBalanceUpdater } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt index 05373bb1f8..9e5ad3188c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt @@ -12,12 +12,11 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.staking.PendingAction import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.staking.EstimateGasUseCase +import com.tangem.domain.staking.model.StakingIntegration import com.tangem.domain.staking.model.stakekit.StakingError -import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.staking.model.stakekit.transaction.ActionParams import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate -import com.tangem.domain.tokens.model.staking.getCurrentToken import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.transaction.usecase.CreateApprovalTransactionUseCase import com.tangem.domain.transaction.usecase.GetFeeUseCase @@ -45,7 +44,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor( private val createApprovalTransactionUseCase: CreateApprovalTransactionUseCase, @Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus, @Assisted private val userWallet: UserWallet, - @Assisted private val yield: Yield, + @Assisted private val integration: StakingIntegration, ) { suspend fun getFee( @@ -58,9 +57,9 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor( val confirmationState = state.confirmationState as? StakingStates.ConfirmationState.Data ?: error("Illegal state") - val validatorAddress = (state.validatorState as? StakingStates.ValidatorState.Data)?.chosenValidator?.address - ?: state.balanceState?.validatorAddress - ?: error("No validator address provided") + val validatorAddress = (state.validatorState as? StakingStates.ValidatorState.Data)?.chosenTarget?.address + ?: state.balanceState?.targetAddress + ?: error("No target address provided") val amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value ?: error("No amount provided") @@ -170,11 +169,11 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor( network = cryptoCurrencyStatus.currency.network, params = ActionParams( actionCommonType = stateController.value.actionType, - integrationId = yield.id, + integrationId = integration.integrationId.value, amount = amount, address = sourceAddress, validatorAddress = validatorAddress, - token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId), + token = integration.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId), passthrough = action?.passthrough, type = action?.type, ), @@ -234,7 +233,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor( fun create( cryptoCurrencyStatus: CryptoCurrencyStatus, userWallet: UserWallet, - yield: Yield, + integration: StakingIntegration, ): StakingFeeTransactionLoader } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt index ef5030f334..f54ed73203 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt @@ -13,15 +13,14 @@ import com.tangem.domain.staking.GetConstructedStakingTransactionUseCase import com.tangem.domain.staking.GetStakingTransactionsUseCase import com.tangem.domain.staking.SaveUnsubmittedHashUseCase import com.tangem.domain.staking.SubmitHashUseCase +import com.tangem.domain.staking.model.StakingIntegration import com.tangem.domain.staking.model.SubmitHashData import com.tangem.domain.staking.model.stakekit.StakingError -import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.staking.model.stakekit.transaction.ActionParams import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionStatus import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionType -import com.tangem.domain.tokens.model.staking.getCurrentToken import com.tangem.domain.transaction.error.SendTransactionError import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase import com.tangem.domain.transaction.usecase.SendTransactionUseCase @@ -56,12 +55,12 @@ internal class StakingTransactionSender @AssistedInject constructor( private val isFeeApproximateUseCase: IsFeeApproximateUseCase, @Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus, @Assisted private val userWallet: UserWallet, - @Assisted private val yield: Yield, + @Assisted private val integration: StakingIntegration, @Assisted private val isAmountSubtractAvailable: Boolean, ) { private val balanceUpdater: StakingBalanceUpdater - get() = stakingBalanceUpdater.create(cryptoCurrencyStatus, userWallet, yield) + get() = stakingBalanceUpdater.create(cryptoCurrencyStatus, userWallet, integration) suspend fun constructAndSendTransactions( onConstructSuccess: (List) -> Unit, @@ -193,7 +192,7 @@ internal class StakingTransactionSender @AssistedInject constructor( val amountState = state.amountState as? AmountState.Data ?: error("No amount provided") - val validatorAddress = validatorState.chosenValidator.address + val validatorAddress = validatorState.chosenTarget.address val amount = getAmount(amountState, fee, confirmationState.reduceAmountBy) return getStakingTransactionsUseCase( @@ -201,11 +200,11 @@ internal class StakingTransactionSender @AssistedInject constructor( network = cryptoCurrencyStatus.currency.network, params = ActionParams( actionCommonType = state.actionType, - integrationId = yield.id, + integrationId = integration.integrationId.value, amount = amount, address = defaultAddress, validatorAddress = validatorAddress, - token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId), + token = integration.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId), passthrough = action?.passthrough, type = action?.type, ), @@ -306,7 +305,7 @@ internal class StakingTransactionSender @AssistedInject constructor( fun create( cryptoCurrencyStatus: CryptoCurrencyStatus, userWallet: UserWallet, - yield: Yield, + integration: StakingIntegration, isAmountSubtractAvailable: Boolean, ): StakingTransactionSender } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt index 1b6868f841..bcad264866 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt @@ -7,6 +7,7 @@ import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.models.staking.BalanceType import com.tangem.domain.models.staking.RewardBlockType import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.toStakingTarget import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.state.BalanceState import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState @@ -81,7 +82,7 @@ internal object InitialStakingStatePreview { fiatAmount = null, formattedFiatAmount = stringReference("100 $"), rawCurrencyId = null, - validator = Yield.Validator( + target = Yield.Validator( address = "address", status = Yield.Validator.ValidatorStatus.ACTIVE, name = "Binance", @@ -92,13 +93,13 @@ internal object InitialStakingStatePreview { votingPower = null, preferred = false, isStrategicPartner = false, - ), + ).toStakingTarget(), pendingActions = persistentListOf(), isClickable = true, type = BalanceType.STAKED, subtitle = null, isPending = false, - validatorAddress = "", + targetAddress = "", ), ), ), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ValidatorStatePreviewData.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ValidatorStatePreviewData.kt index b65729f59a..8ce4a816d3 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ValidatorStatePreviewData.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ValidatorStatePreviewData.kt @@ -1,7 +1,11 @@ package com.tangem.features.staking.impl.presentation.state.previewdata +import com.tangem.domain.staking.model.common.RewardInfo +import com.tangem.domain.staking.model.common.RewardType +import com.tangem.domain.staking.model.StakingTarget import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.Yield.Validator.ValidatorStatus +import com.tangem.domain.staking.model.toStakingTarget import com.tangem.features.staking.impl.presentation.state.StakingStates import java.math.BigDecimal @@ -13,9 +17,9 @@ internal object ValidatorStatePreviewData { status = ValidatorStatus.ACTIVE, name = "Luganodes", image = "https://assets.stakek.it/validators/luganodes.png", - rewardInfo = Yield.RewardInfo( + rewardInfo = RewardInfo( rate = BigDecimal("0.054823398040640445"), - type = Yield.RewardType.APR, + type = RewardType.APR, ), commission = 0.1, stakedBalance = "355544384.45009977", @@ -29,9 +33,9 @@ internal object ValidatorStatePreviewData { status = ValidatorStatus.ACTIVE, name = "InfStones", image = "https://assets.stakek.it/validators/infstones.png", - rewardInfo = Yield.RewardInfo( + rewardInfo = RewardInfo( rate = BigDecimal("0.057786472172836965"), - type = Yield.RewardType.APR, + type = RewardType.APR, ), commission = 0.05, stakedBalance = "12495684.05643019", @@ -45,9 +49,9 @@ internal object ValidatorStatePreviewData { status = ValidatorStatus.ACTIVE, name = "Kiln", image = "https://assets.stakek.it/validators/kiln.png", - rewardInfo = Yield.RewardInfo( + rewardInfo = RewardInfo( rate = BigDecimal("0.057786472172836965"), - type = Yield.RewardType.APR, + type = RewardType.APR, ), commission = 0.05, stakedBalance = "85400369.96393165", @@ -58,11 +62,13 @@ internal object ValidatorStatePreviewData { ), ) + private val targetList: List = validatorList.map { it.toStakingTarget() } + val validatorState = StakingStates.ValidatorState.Data( - availableValidators = validatorList, - chosenValidator = validatorList.first(), + availableTargets = targetList, + chosenTarget = targetList.first(), isPrimaryButtonEnabled = true, - activeValidator = null, + activeTarget = null, isClickable = true, isVisibleOnConfirmation = true, ) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt index 1b90c1dc49..5b91544cd2 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt @@ -3,7 +3,7 @@ package com.tangem.features.staking.impl.presentation.state.stub import com.tangem.common.ui.bottomsheet.permission.state.ApproveType import com.tangem.common.ui.notifications.NotificationUM import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingTarget import com.tangem.features.staking.impl.presentation.model.StakingClickIntents import com.tangem.features.staking.impl.presentation.state.BalanceState import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType @@ -40,7 +40,7 @@ internal object StakingClickIntentsStub : StakingClickIntents { override fun openValidators() {} - override fun onValidatorSelect(validator: Yield.Validator) {} + override fun onTargetSelect(target: StakingTarget) {} override fun openRewardsValidators() {} diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt index 7fb48b2a61..31471a94ec 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt @@ -5,7 +5,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.staking.PendingAction import com.tangem.domain.models.staking.action.StakingActionType import com.tangem.domain.staking.model.StakingApproval -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingIntegration import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.features.staking.impl.presentation.state.* import com.tangem.features.staking.impl.presentation.state.utils.isCompositePendingActions @@ -24,7 +24,7 @@ internal class SetConfirmationStateInitTransformer( private val stakingApproval: StakingApproval, private val stakingAllowance: BigDecimal, private val cryptoCurrencyStatus: CryptoCurrencyStatus, - private val yieldArgs: Yield.Args, + private val integration: StakingIntegration, private val pendingActions: ImmutableList? = null, private val pendingAction: PendingAction? = pendingActions?.firstOrNull(), ) : Transformer { @@ -66,7 +66,7 @@ internal class SetConfirmationStateInitTransformer( } private fun getActionType(prevState: StakingUiState): StakingActionCommonType { - val isPartialEnterAmountDisabled = yieldArgs.enter.isPartialAmountDisabled + val isPartialEnterAmountDisabled = integration.isPartialAmountDisabled val isPartialExitAmountDisabled = isPartiallyUnstakeDisabled(prevState) return when { isEnter -> StakingActionCommonType.Enter(isPartialEnterAmountDisabled) @@ -87,12 +87,12 @@ internal class SetConfirmationStateInitTransformer( private fun isPartiallyUnstakeDisabled(state: StakingUiState): Boolean { val isSolana = BlockchainUtils.isSolana(state.cryptoCurrencyBlockchainId) - val isValidatorPreferred = balanceState?.validator?.preferred == true + val isTargetPreferred = balanceState?.target?.isPreferred == true - return if (isSolana && !isValidatorPreferred) { + return if (isSolana && !isTargetPreferred) { true } else { - yieldArgs.exit?.isPartialAmountDisabled == true + integration.exitArgs?.isPartialAmountDisabled == true } } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt index 5809667c31..4ec20302ce 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt @@ -8,7 +8,7 @@ import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.format import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingIntegration import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.state.FeeState @@ -18,7 +18,7 @@ import com.tangem.features.staking.impl.presentation.state.utils.getRewardSchedu import com.tangem.utils.transformer.Transformer internal class SetConfirmationStateLoadingTransformer( - private val yield: Yield, + private val integration: StakingIntegration, private val appCurrency: AppCurrency, private val cryptoCurrency: CryptoCurrency, ) : Transformer { @@ -48,7 +48,7 @@ internal class SetConfirmationStateLoadingTransformer( ) } val rewardSchedule = getRewardScheduleText( - rewardSchedule = yield.metadata.rewardSchedule, + rewardSchedule = integration.rewardSchedule, networkId = cryptoCurrency.network.rawId, decapitalize = true, ) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index 2f55f146f8..86fdcfd533 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -19,6 +19,9 @@ import com.tangem.domain.models.account.Account import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.staking.BalanceItem import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.staking.model.common.RewardType +import com.tangem.domain.staking.model.StakingIntegration +import com.tangem.domain.staking.model.StakingTarget import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.model.StakingClickIntents @@ -41,7 +44,7 @@ import java.math.BigDecimal @Suppress("LongParameterList") internal class SetInitialDataStateTransformer( private val clickIntents: StakingClickIntents, - private val yield: Yield, + private val integration: StakingIntegration, private val isAnyTokenStaked: Boolean, private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val userWalletProvider: Provider, @@ -55,7 +58,7 @@ internal class SetInitialDataStateTransformer( private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) private val rewardsValidatorStateConverter by lazy(LazyThreadSafetyMode.NONE) { - RewardsValidatorStateConverter(cryptoCurrencyStatus, appCurrencyProvider, yield) + RewardsValidatorStateConverter(cryptoCurrencyStatus, appCurrencyProvider, integration) } private val yieldBalancesConverter by lazy(LazyThreadSafetyMode.NONE) { @@ -63,7 +66,7 @@ internal class SetInitialDataStateTransformer( cryptoCurrencyStatus = cryptoCurrencyStatus, appCurrencyProvider = appCurrencyProvider, balancesToShowProvider = balancesToShowProvider, - yield = yield, + integration = integration, ) } @@ -116,27 +119,27 @@ internal class SetInitialDataStateTransformer( } private fun createAnnualPercentageItem(): RoundedListWithDividersItemData { - val validators = yield.preferredValidators - val rateRangeInfo = getPercentageRange(validators) + val targets = integration.preferredTargets + val rateRangeInfo = getPercentageRange(targets) return RoundedListWithDividersItemData( id = R.string.staking_details_annual_percentage_rate, startText = getRateStartText(rateRangeInfo.first), endText = rateRangeInfo.second, iconClick = { when (rateRangeInfo.first) { - Yield.RewardType.APR -> clickIntents.onInfoClick(InfoType.ANNUAL_PERCENTAGE_RATE) - Yield.RewardType.APY -> clickIntents.onInfoClick(InfoType.ANNUAL_PERCENTAGE_YIELD) - Yield.RewardType.UNKNOWN -> {} + RewardType.APR -> clickIntents.onInfoClick(InfoType.ANNUAL_PERCENTAGE_RATE) + RewardType.APY -> clickIntents.onInfoClick(InfoType.ANNUAL_PERCENTAGE_YIELD) + RewardType.UNKNOWN -> {} } }, isEndTextHighlighted = false, ) } - private fun getRateStartText(rewardType: Yield.RewardType): TextReference { + private fun getRateStartText(rewardType: RewardType): TextReference { return when (rewardType) { - Yield.RewardType.APR -> TextReference.Res(R.string.staking_details_annual_percentage_rate) - Yield.RewardType.APY -> TextReference.Res(R.string.staking_details_annual_percentage_yield) + RewardType.APR -> TextReference.Res(R.string.staking_details_annual_percentage_rate) + RewardType.APY -> TextReference.Res(R.string.staking_details_annual_percentage_yield) else -> TextReference.EMPTY } } @@ -153,7 +156,7 @@ internal class SetInitialDataStateTransformer( } private fun createUnbondingPeriodItem(): RoundedListWithDividersItemData? { - val cooldownPeriodDays = yield.metadata.cooldownPeriod?.days ?: return null + val cooldownPeriodDays = integration.cooldownPeriodDays ?: return null return RoundedListWithDividersItemData( id = R.string.staking_details_unbonding_period, startText = TextReference.Res(R.string.staking_details_unbonding_period), @@ -169,7 +172,7 @@ internal class SetInitialDataStateTransformer( private fun createMinimumRequirementItem( cryptoCurrencyStatus: CryptoCurrencyStatus, ): RoundedListWithDividersItemData? { - val minimumCryptoAmount = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum ?: return null + val minimumCryptoAmount = integration.enterMinimumAmount ?: return null val blockchainId = cryptoCurrencyStatus.currency.network.rawId if (!showMinimumRequirementInfo(blockchainId)) return null @@ -183,7 +186,7 @@ internal class SetInitialDataStateTransformer( } private fun createRewardClaimingItem(): RoundedListWithDividersItemData? { - val rewardClaiming = yield.metadata.rewardClaiming + val rewardClaiming = integration.rewardClaiming val endTextId = rewardClaimingResources[rewardClaiming] ?: return null return RoundedListWithDividersItemData( @@ -195,7 +198,7 @@ internal class SetInitialDataStateTransformer( } private fun createWarmupPeriodItem(): RoundedListWithDividersItemData? { - val warmupPeriodDays = yield.metadata.warmupPeriod.days + val warmupPeriodDays = integration.warmupPeriodDays if (warmupPeriodDays == 0) return null return RoundedListWithDividersItemData( @@ -212,7 +215,7 @@ internal class SetInitialDataStateTransformer( private fun createRewardScheduleItem(): RoundedListWithDividersItemData? { val endTextReference = getRewardScheduleText( - rewardSchedule = yield.metadata.rewardSchedule, + rewardSchedule = integration.rewardSchedule, networkId = cryptoCurrencyStatus.currency.network.rawId, decapitalize = false, ) ?: return null @@ -252,15 +255,19 @@ internal class SetInitialDataStateTransformer( ) } - private fun getPercentageRange(validators: List): Pair { - if (validators.isEmpty()) { - return Yield.RewardType.APR to stringReference(DASH_SIGN) + private fun getPercentageRange(targets: List): Pair { + if (targets.isEmpty()) { + return RewardType.APR to stringReference(DASH_SIGN) } - val rewardInfos = validators - .filter { it.preferred } + val rewardInfos = targets + .filter { it.isPreferred } .takeIf { it.isNotEmpty() } ?.mapNotNull { it.rewardInfo } - ?: validators.mapNotNull { it.rewardInfo } + ?: targets.mapNotNull { it.rewardInfo } + + if (rewardInfos.isEmpty()) { + return RewardType.APR to stringReference(DASH_SIGN) + } val infoWithMinRate = rewardInfos.minBy { it.rate } val infoWithMaxRate = rewardInfos.maxBy { it.rate } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountChangeStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountChangeStateTransformer.kt index 0603ec9e7f..9c9c0d1595 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountChangeStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountChangeStateTransformer.kt @@ -4,7 +4,7 @@ import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingIntegration import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.utils.transformer.Transformer @@ -13,7 +13,7 @@ internal class AmountChangeStateTransformer( private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val minimumTransactionAmount: EnterAmountBoundary?, private val value: String, - private val yield: Yield, + private val integration: StakingIntegration, ) : Transformer { private val maxEnterAmountConverter = MaxEnterAmountConverter() @@ -41,7 +41,7 @@ internal class AmountChangeStateTransformer( amountState = AmountRequirementStateTransformer( cryptoCurrencyStatus = cryptoCurrencyStatus, maxAmount = maxEnterAmount, - yield = yield, + integration = integration, actionType = prevState.actionType, ).transform(updatedAmountState), ) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountMaxValueStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountMaxValueStateTransformer.kt index cee87196ee..94476c6be9 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountMaxValueStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountMaxValueStateTransformer.kt @@ -4,7 +4,7 @@ import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter import com.tangem.common.ui.amountScreen.converters.field.AmountFieldSetMaxAmountTransformer import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingIntegration import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.utils.transformer.Transformer @@ -13,7 +13,7 @@ internal class AmountMaxValueStateTransformer( private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val minimumTransactionAmount: EnterAmountBoundary?, private val actionType: StakingActionCommonType, - private val yield: Yield, + private val integration: StakingIntegration, ) : Transformer { private val maxEnterAmountConverter = MaxEnterAmountConverter() @@ -38,7 +38,7 @@ internal class AmountMaxValueStateTransformer( amountState = AmountRequirementStateTransformer( maxAmount = maxEnterAmount, cryptoCurrencyStatus = cryptoCurrencyStatus, - yield = yield, + integration = integration, actionType = prevState.actionType, ).transform(updatedAmountState), ) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt index f24052ac95..8af103c0f1 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt @@ -12,6 +12,7 @@ import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.staking.model.StakingIntegration import com.tangem.domain.staking.model.stakekit.AddressArgument import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType @@ -26,7 +27,7 @@ import java.math.RoundingMode internal class AmountRequirementStateTransformer( private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val maxAmount: EnterAmountBoundary, - private val yield: Yield, + private val integration: StakingIntegration, private val actionType: StakingActionCommonType, ) : Transformer { override fun transform(prevState: AmountState): AmountState { @@ -96,11 +97,11 @@ internal class AmountRequirementStateTransformer( return when (actionType) { is StakingActionCommonType.Enter -> { - val enterRequirements = yield.args.enter.args[Yield.Args.ArgType.AMOUNT] + val enterRequirements = integration.enterArgs?.args?.get(Yield.Args.ArgType.AMOUNT) enterRequirements?.getError(amountDecimal, R.string.staking_amount_requirement_error) } is StakingActionCommonType.Exit -> { - val exitRequirements = yield.args.exit?.args?.get(Yield.Args.ArgType.AMOUNT) + val exitRequirements = integration.exitArgs?.args?.get(Yield.Args.ArgType.AMOUNT) exitRequirements?.getError(amountDecimal, R.string.staking_unstake_amount_requirement_error) } else -> null diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt index f0b2ca6298..0dec39a031 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt @@ -37,7 +37,7 @@ internal class ShowApprovalBottomSheetTransformer( val fee = feeState.fee ?: return prevState val walletAddress = cryptoCurrencyValue.networkAddress?.defaultAddress?.value.orEmpty() - val validatorAddress = validatorState.chosenValidator.address + val targetAddress = validatorState.chosenTarget.address val feeCryptoValue = fee.amount.value.format { crypto(fee.amount.currencySymbol, fee.amount.decimals) } @@ -57,7 +57,7 @@ internal class ShowApprovalBottomSheetTransformer( amount = amountState.amountTextField.value, approveType = ApproveType.UNLIMITED, walletAddress = walletAddress, - spenderAddress = validatorAddress, + spenderAddress = targetAddress, fee = resourceReference( R.string.common_crypto_fiat_format, wrappedList(feeCryptoValue, feeFiatValue), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt index 45351b95ed..9912043b5d 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt @@ -15,9 +15,9 @@ import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.staking.model.StakingIntegration import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.StakingErrors -import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning @@ -47,12 +47,12 @@ internal class AddStakingNotificationsTransformer( private val stakingError: StakingError?, private val currencyCheck: CryptoCurrencyCheck, private val isSubtractAvailable: Boolean, - private val yield: Yield, + private val integration: StakingIntegration, ) : Transformer { private val stakingInfoNotificationsFactory = StakingInfoNotificationsFactory( cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - yield = yield, + integration = integration, isSubtractAvailable = isSubtractAvailable, ) @@ -77,7 +77,7 @@ internal class AddStakingNotificationsTransformer( isSubtractAvailable = isSubtractAvailable, reduceAmountBy = reduceAmountBy, ) - val minimumRequirement = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum.orZero() + val minimumRequirement = integration.enterMinimumAmount.orZero() val sendingAmount = if (isEnterAction) { checkAndCalculateSubtractedAmount( isAmountSubtractAvailable = isSubtractAvailable, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt index bdda0a7ac5..7b6e21df7d 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt @@ -8,6 +8,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.staking.BalanceType import com.tangem.domain.models.staking.StakingBalance import com.tangem.domain.models.staking.action.StakingActionType +import com.tangem.domain.staking.model.StakingIntegration import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.features.staking.impl.R @@ -26,7 +27,7 @@ import java.math.BigDecimal internal class StakingInfoNotificationsFactory( private val cryptoCurrencyStatusProvider: Provider, - private val yield: Yield, + private val integration: StakingIntegration, private val isSubtractAvailable: Boolean, ) { @@ -94,7 +95,7 @@ internal class StakingInfoNotificationsFactory( resourceReference(R.string.staking_notification_withdraw_text) } StakingActionType.UNLOCK_LOCKED -> { - val cooldownPeriodDays = yield.metadata.cooldownPeriod?.days + val cooldownPeriodDays = integration.cooldownPeriodDays if (cooldownPeriodDays != null) { resourceReference(R.string.staking_unlocked_locked) to resourceReference( R.string.staking_notification_unlock_text, @@ -213,7 +214,7 @@ internal class StakingInfoNotificationsFactory( if (prevState.actionType !is StakingActionCommonType.Exit) return val maxAmount = prevState.balanceState?.cryptoAmount ?: return - val exitRequirements = yield.args.exit?.args?.get(Yield.Args.ArgType.AMOUNT) ?: return + val exitRequirements = integration.exitArgs?.args?.get(Yield.Args.ArgType.AMOUNT) ?: return val amountLeft = maxAmount - actionAmount val isNotEnoughLeft = !amountLeft.isZero() && amountLeft < exitRequirements.minimum.orZero() @@ -224,7 +225,7 @@ internal class StakingInfoNotificationsFactory( } private fun MutableList.addUnstakeInfoNotification() { - val cooldownPeriodDays = yield.metadata.cooldownPeriod?.days + val cooldownPeriodDays = integration.cooldownPeriodDays val cryptoCurrencyNetworkIdValue = cryptoCurrencyStatusProvider().currency.network.rawId if (cooldownPeriodDays != null) { @@ -248,18 +249,17 @@ internal class StakingInfoNotificationsFactory( val initialInfoState = prevState.initialInfoState as? StakingStates.InitialInfoState.Data val stakingBalances = (initialInfoState?.yieldBalance as? InnerYieldBalanceState.Data)?.balances - val validatorAddress = prevState.balanceState?.validator?.address ?: return + val targetAddress = prevState.balanceState?.target?.address ?: return - val stakesCountWithCertainValidator = stakingBalances.orEmpty() - .filter { - it.type == BalanceType.STAKED || - it.type == BalanceType.PREPARING || - it.type == BalanceType.UNSTAKED + val stakesCountWithCertainTarget = stakingBalances.orEmpty() + .count { state -> + (state.type == BalanceType.STAKED || + state.type == BalanceType.PREPARING || + state.type == BalanceType.UNSTAKED) && + state.target?.address == targetAddress } - .filter { it.validator?.address == validatorAddress } - .size - if (stakesCountWithCertainValidator > 1) { + if (stakesCountWithCertainTarget > 1) { add( StakingNotification.Info.Ordinary( title = resourceReference(R.string.staking_notification_ton_have_to_unstake_all_title), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt index c940c12e8a..ddaad3e60e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt @@ -1,6 +1,7 @@ package com.tangem.features.staking.impl.presentation.state.transformers.validator -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingIntegration +import com.tangem.domain.staking.model.StakingTarget import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.models.staking.action.StakingActionType import com.tangem.features.staking.impl.presentation.state.StakingStates @@ -9,8 +10,8 @@ import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.utils.transformer.Transformer internal class ValidatorSelectChangeTransformer( - private val yield: Yield, - private val selectedValidator: Yield.Validator?, + private val integration: StakingIntegration, + private val selectedTarget: StakingTarget?, ) : Transformer { override fun transform(prevState: StakingUiState): StakingUiState { @@ -23,27 +24,27 @@ internal class ValidatorSelectChangeTransformer( val isFromInfoScreen = prevState.currentStep == StakingStep.InitialInfo val isVoteLocked = confirmationState?.pendingAction?.type == StakingActionType.VOTE_LOCKED - val activeValidator = selectedValidator.takeIf { isFromInfoScreen && isRestake } - ?: validatorState?.activeValidator - val filteredValidators = yield.preferredValidators.filterNot { it == activeValidator } + val activeTarget = selectedTarget.takeIf { isFromInfoScreen && isRestake } + ?: validatorState?.activeTarget + val filteredTargets = integration.preferredTargets.filterNot { it == activeTarget } - val selectedValidator = if (isRestake && isFromInfoScreen) { - filteredValidators.firstOrNull() + val selectedTarget = if (isRestake && isFromInfoScreen) { + filteredTargets.firstOrNull() } else { - selectedValidator + selectedTarget } - if (selectedValidator == null && yield.preferredValidators.isEmpty()) { + if (selectedTarget == null && integration.preferredTargets.isEmpty()) { return prevState } return prevState.copy( validatorState = StakingStates.ValidatorState.Data( - chosenValidator = selectedValidator ?: yield.preferredValidators.first(), - availableValidators = filteredValidators, + chosenTarget = selectedTarget ?: integration.preferredTargets.first(), + availableTargets = filteredTargets, isPrimaryButtonEnabled = true, - isClickable = yield.preferredValidators.size > 1, - activeValidator = activeValidator, + isClickable = integration.preferredTargets.size > 1, + activeTarget = activeTarget, isVisibleOnConfirmation = isEnter || isRestake || isVoteLocked, ), ) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingRewardsUtils.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingRewardsUtils.kt index 454a1aa5fb..ba5cc07eee 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingRewardsUtils.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingRewardsUtils.kt @@ -1,6 +1,7 @@ package com.tangem.features.staking.impl.presentation.state.utils import com.tangem.core.ui.extensions.* +import com.tangem.domain.staking.model.common.RewardType import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.state.utils.StakingRewardSchedule.COSMOS_SCHEDULE @@ -52,18 +53,18 @@ internal fun getRewardScheduleText( } } -internal fun getRewardTypeShortText(rewardType: Yield.RewardType): TextReference { +internal fun getRewardTypeShortText(rewardType: RewardType): TextReference { return when (rewardType) { - Yield.RewardType.APR -> TextReference.Res(R.string.staking_details_apr) - Yield.RewardType.APY -> TextReference.Res(R.string.staking_details_apy) + RewardType.APR -> TextReference.Res(R.string.staking_details_apr) + RewardType.APY -> TextReference.Res(R.string.staking_details_apy) else -> TextReference.EMPTY } } -internal fun getRewardTypeLongText(rewardType: Yield.RewardType): TextReference { +internal fun getRewardTypeLongText(rewardType: RewardType): TextReference { return when (rewardType) { - Yield.RewardType.APR -> TextReference.Res(R.string.staking_details_annual_percentage_rate) - Yield.RewardType.APY -> TextReference.Res(R.string.staking_details_annual_percentage_yield) + RewardType.APR -> TextReference.Res(R.string.staking_details_annual_percentage_rate) + RewardType.APY -> TextReference.Res(R.string.staking_details_annual_percentage_yield) else -> TextReference.EMPTY } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt index 2fa13297d4..20f8688108 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt @@ -15,7 +15,7 @@ import com.tangem.core.ui.extensions.* import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.res.TangemTheme -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.common.RewardType import com.tangem.features.staking.impl.presentation.model.StakingClickIntents import com.tangem.features.staking.impl.presentation.state.BalanceState import com.tangem.features.staking.impl.presentation.state.StakingStates @@ -30,7 +30,7 @@ internal fun StakingClaimRewardsValidatorContent( ) { if (state !is StakingStates.RewardsValidatorsState.Data) return Column( - modifier = Modifier // Do not put fillMaxSize() in here + modifier = modifier // This function shouldn't itself apply fillMaxSize(); callers should avoid passing it here .background(TangemTheme.colors.background.secondary) .padding(horizontal = TangemTheme.dimens.spacing12) .verticalScroll(rememberScrollState()), @@ -42,9 +42,9 @@ internal fun StakingClaimRewardsValidatorContent( caption = item.getAprTextNeutral(), infoTitle = item.formattedFiatAmount, infoSubtitle = item.formattedCryptoAmount, - imageUrl = item.validator?.image.orEmpty(), + imageUrl = item.target?.image.orEmpty(), onImageError = { ValidatorImagePlaceholder() }, - modifier = modifier + modifier = Modifier .roundedShapeItemDecoration(index, state.rewards.lastIndex, false) .background(TangemTheme.colors.background.action) .clickable( @@ -65,11 +65,11 @@ internal fun StakingClaimRewardsValidatorContent( @Suppress("UnusedPrivateMember") @Composable private fun BalanceState.getAprTextColored() = combinedReference( - getRewardTypeShortText(validator?.rewardInfo?.type ?: Yield.RewardType.UNKNOWN), + getRewardTypeShortText(target?.rewardInfo?.type ?: RewardType.UNKNOWN), annotatedReference { appendSpace() appendColored( - text = validator?.rewardInfo?.rate?.orZero().format { percent() }, + text = target?.rewardInfo?.rate?.orZero().format { percent() }, color = TangemTheme.colors.text.accent, ) }, @@ -77,6 +77,6 @@ private fun BalanceState.getAprTextColored() = combinedReference( @Composable private fun BalanceState.getAprTextNeutral() = combinedReference( - getRewardTypeShortText(validator?.rewardInfo?.type ?: Yield.RewardType.UNKNOWN), - stringReference(" " + validator?.rewardInfo?.rate?.orZero().format { percent() }), + getRewardTypeShortText(target?.rewardInfo?.type ?: RewardType.UNKNOWN), + stringReference(" " + target?.rewardInfo?.rate?.orZero().format { percent() }), ) \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt index d838576585..1a2d54564a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt @@ -46,7 +46,7 @@ import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.test.StakingDetailsScreenTestTags import com.tangem.domain.models.staking.BalanceType import com.tangem.domain.models.staking.RewardBlockType -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.common.RewardType import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.model.StakingClickIntents import com.tangem.features.staking.impl.presentation.state.BalanceState @@ -317,11 +317,11 @@ private fun StakeButtonBlock(buttonState: NavigationButtonsState) { @Suppress("UnusedPrivateMember") @Composable private fun BalanceState.getAprTextColored() = combinedReference( - getRewardTypeShortText(validator?.rewardInfo?.type ?: Yield.RewardType.UNKNOWN), + getRewardTypeShortText(target?.rewardInfo?.type ?: RewardType.UNKNOWN), annotatedReference { appendSpace() appendColored( - text = validator?.rewardInfo?.rate?.orZero().format { percent() }, + text = target?.rewardInfo?.rate?.orZero().format { percent() }, color = TangemTheme.colors.text.accent, ) }, @@ -329,8 +329,8 @@ private fun BalanceState.getAprTextColored() = combinedReference( @Composable private fun BalanceState.getAprTextNeutral() = combinedReference( - getRewardTypeShortText(validator?.rewardInfo?.type ?: Yield.RewardType.UNKNOWN), - stringReference(" " + validator?.rewardInfo?.rate?.orZero().format { percent() }), + getRewardTypeShortText(target?.rewardInfo?.type ?: RewardType.UNKNOWN), + stringReference(" " + target?.rewardInfo?.rate?.orZero().format { percent() }), ) @Composable @@ -347,7 +347,7 @@ private fun BalanceState.getImage() = when (type) { BalanceType.UNSTAKED, BalanceType.LOCKED, -> null - else -> validator?.image + else -> target?.image } private val textGradientColors = listOf( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt index c98b98f368..3f79c1729d 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt @@ -27,7 +27,8 @@ import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.common.RewardType +import com.tangem.domain.staking.model.StakingTarget import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.model.StakingClickIntents import com.tangem.features.staking.impl.presentation.state.StakingStates @@ -54,24 +55,24 @@ internal fun StakingValidatorListContent( .padding(horizontal = TangemTheme.dimens.spacing16), ) { if (state is StakingStates.ValidatorState.Data) { - val validators = state.availableValidators + val targets = state.availableTargets items( - count = validators.size, - key = { validators[it].address }, - contentType = { validators[it]::class.java }, + count = targets.size, + key = { targets[it].address }, + contentType = { targets[it]::class.java }, ) { index -> - val item = validators[index] + val item = targets[index] InputRowImageSelector( subtitle = stringReference(item.name), caption = item.getAprTextNeutral(), imageUrl = item.image.orEmpty(), - isSelected = item == state.chosenValidator, - onSelect = { clickIntents.onValidatorSelect(item) }, + isSelected = item == state.chosenTarget, + onSelect = { clickIntents.onTargetSelect(item) }, modifier = Modifier .roundedShapeItemDecoration( currentIndex = index, - lastIndex = validators.lastIndex, + lastIndex = targets.lastIndex, radius = TangemTheme.dimens.radius12, addDefaultPadding = false, @@ -106,21 +107,21 @@ internal fun StakingValidatorListContent( */ @Suppress("UnusedPrivateMember") @Composable -private fun Yield.Validator.getAprTextColored() = combinedReference( - getRewardTypeLongText(rewardInfo?.type ?: Yield.RewardType.UNKNOWN), +private fun StakingTarget.getAprTextColored() = combinedReference( + getRewardTypeLongText(rewardInfo?.type ?: RewardType.UNKNOWN), annotatedReference { appendSpace() appendColored( - text = rewardInfo?.rate?.orZero().format { percent() }, + text = rewardInfo?.rate.orZero().format { percent() }, color = TangemTheme.colors.text.accent, ) }, ) @Composable -private fun Yield.Validator.getAprTextNeutral() = combinedReference( - getRewardTypeLongText(rewardInfo?.type ?: Yield.RewardType.UNKNOWN), - stringReference(" " + rewardInfo?.rate?.orZero().format { percent() }), +private fun StakingTarget.getAprTextNeutral() = combinedReference( + getRewardTypeLongText(rewardInfo?.type ?: RewardType.UNKNOWN), + stringReference(" " + rewardInfo?.rate.orZero().format { percent() }), ) @Composable diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt index 53402f0c11..44012d5abc 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt @@ -25,8 +25,9 @@ import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.test.StakingSendDetailsScreenTestTags -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.common.RewardType import com.tangem.features.staking.impl.R +import com.tangem.features.staking.impl.presentation.state.StakingStates.ValidatorState import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.features.staking.impl.presentation.state.utils.getRewardTypeShortText import com.tangem.features.staking.impl.presentation.ui.ValidatorImagePlaceholder @@ -63,20 +64,20 @@ internal fun ValidatorBlock(validatorState: StakingStates.ValidatorState, isClic modifier = Modifier.padding(12.dp), ) { InputRowAsyncImage( - imageUrl = validatorState.chosenValidator.image.orEmpty(), + imageUrl = state.chosenTarget.image.orEmpty(), onImageError = { ValidatorImagePlaceholder() }, modifier = Modifier .size(24.dp) .clip(TangemTheme.shapes.roundedCornersXLarge), ) Text( - text = validatorState.chosenValidator.name, + text = state.chosenTarget.name, style = TangemTheme.typography.body1, color = TangemTheme.colors.text.primary1, ) SpacerWMax() Text( - text = validatorState.getInfoTitleNeutral().resolveReference(), + text = state.getInfoTitleNeutral().resolveReference(), style = TangemTheme.typography.body1, color = TangemTheme.colors.text.tertiary, ) @@ -91,16 +92,16 @@ internal fun ValidatorBlock(validatorState: StakingStates.ValidatorState, isClic @Composable private fun StakingStates.ValidatorState.Data.getInfoTitleColored() = combinedReference( annotatedReference { - append(getRewardTypeShortText(chosenValidator.rewardInfo?.type ?: Yield.RewardType.UNKNOWN).resolveReference()) + append(getRewardTypeShortText(chosenTarget.rewardInfo?.type ?: RewardType.UNKNOWN).resolveReference()) appendSpace() appendColored( - text = chosenValidator.rewardInfo?.rate.orZero().format { percent() }, + text = chosenTarget.rewardInfo?.rate.orZero().format { percent() }, color = TangemTheme.colors.text.accent, ) }, ) private fun StakingStates.ValidatorState.Data.getInfoTitleNeutral() = combinedReference( - getRewardTypeShortText(chosenValidator.rewardInfo?.type ?: Yield.RewardType.UNKNOWN), - stringReference(" " + chosenValidator.rewardInfo?.rate?.orZero().format { percent() }), + getRewardTypeShortText(chosenTarget.rewardInfo?.type ?: RewardType.UNKNOWN), + stringReference(" " + chosenTarget.rewardInfo?.rate.orZero().format { percent() }), ) \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt index 353c7016ce..c59ef273b8 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt @@ -2,6 +2,7 @@ package com.tangem.feature.tokendetails.presentation.router import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter +import com.tangem.domain.staking.model.StakingIntegrationID import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.url.UrlOpener @@ -37,12 +38,16 @@ internal class DefaultTokenDetailsRouter @Inject constructor( ) } - override fun openStaking(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, yieldId: String) { + override fun openStaking( + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency, + integrationId: StakingIntegrationID, + ) { router.push( AppRoute.Staking( userWalletId = userWalletId, cryptoCurrency = cryptoCurrency, - yieldId = yieldId, + integrationId = integrationId, ), ) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/InnerTokenDetailsRouter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/InnerTokenDetailsRouter.kt index f6cf5397ab..6bec13bb2d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/InnerTokenDetailsRouter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/InnerTokenDetailsRouter.kt @@ -1,5 +1,6 @@ package com.tangem.feature.tokendetails.presentation.router +import com.tangem.domain.staking.model.StakingIntegrationID import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWalletId @@ -16,5 +17,5 @@ internal interface InnerTokenDetailsRouter { fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency) - fun openStaking(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, yieldId: String) + fun openStaking(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, integrationId: StakingIntegrationID) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index 0b39af391f..b660c09fdd 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -54,7 +54,6 @@ import com.tangem.domain.promo.models.PromoId import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.staking.GetStakingAvailabilityUseCase import com.tangem.domain.staking.GetStakingEntryInfoUseCase -import com.tangem.domain.staking.GetYieldUseCase import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.tokens.* import com.tangem.domain.tokens.legacy.TradeCryptoAction @@ -124,7 +123,6 @@ internal class TokenDetailsModel @Inject constructor( private val getExtendedPublicKeyForCurrencyUseCase: GetExtendedPublicKeyForCurrencyUseCase, private val getStakingEntryInfoUseCase: GetStakingEntryInfoUseCase, private val getStakingAvailabilityUseCase: GetStakingAvailabilityUseCase, - private val getYieldUseCase: GetYieldUseCase, private val networkHasDerivationUseCase: NetworkHasDerivationUseCase, private val isDemoCardUseCase: IsDemoCardUseCase, private val associateAssetUseCase: AssociateAssetUseCase, @@ -1119,18 +1117,28 @@ internal class TokenDetailsModel @Inject constructor( private fun openStaking() { modelScope.launch { - getYieldUseCase.invoke( - cryptoCurrencyId = cryptoCurrency.id, - symbol = cryptoCurrency.symbol, - ).onRight { yield -> - router.openStaking(userWalletId, cryptoCurrency, yield.id) - }.onLeft { - Timber.e("Staking is unavailable for ${cryptoCurrency.name}") - uiMessageSender.send(SnackbarMessage(resourceReference(R.string.staking_error_no_validators_title))) - } + getStakingAvailabilityUseCase.invokeSync(userWalletId, cryptoCurrency) + .onRight { availability -> + val option = (availability as? StakingAvailability.Available)?.option + if (option != null) { + router.openStaking( + userWalletId = userWalletId, + cryptoCurrency = cryptoCurrency, + integrationId = option.integrationId, + ) + } else { + showStakingUnavailable() + } + } + .onLeft { showStakingUnavailable() } } } + private fun showStakingUnavailable() { + Timber.e("Staking is unavailable for ${cryptoCurrency.name}") + uiMessageSender.send(SnackbarMessage(resourceReference(R.string.staking_error_no_validators_title))) + } + private fun checkForActionUpdates() { combine( tokenDetailsDeepLinkActionListener.tokenDetailsActionFlow, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStakingInfoConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStakingInfoConverter.kt index e87c73a48e..d93f8c4469 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStakingInfoConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStakingInfoConverter.kt @@ -62,12 +62,12 @@ internal class TokenDetailsStakingInfoConverter( val hasPendingBalances = when (stakingBalance) { is StakingBalance.Data.StakeKit -> stakingBalance.balance.items.isNotEmpty() - is StakingBalance.Data.P2P -> !stakingBalance.unstakingAmount.isNullOrZero() + is StakingBalance.Data.P2PEthPool -> !stakingBalance.unstakingAmount.isNullOrZero() null -> false } val pendingAmount = when (stakingBalance) { is StakingBalance.Data.StakeKit -> stakingBalance.balance.items.sumOf { it.amount } - is StakingBalance.Data.P2P -> stakingBalance.unstakingAmount + is StakingBalance.Data.P2PEthPool -> stakingBalance.unstakingAmount null -> BigDecimal.ZERO } @@ -98,7 +98,7 @@ internal class TokenDetailsStakingInfoConverter( stakingAmount = stakingCryptoAmount, rewardAmount = when (stakingBalance) { is StakingBalance.Data.StakeKit -> stakingBalance.getRewardStakingBalance() - is StakingBalance.Data.P2P -> stakingBalance.totalRewards + is StakingBalance.Data.P2PEthPool -> stakingBalance.totalRewards else -> BigDecimal.ZERO }, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt index 09c16514ad..9cc74286c9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt @@ -505,6 +505,8 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( ) { stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWalletId)) + val integrationId = option?.integrationId ?: return + modelScope.launch { val cryptoCurrency = cryptoCurrencyStatus.currency @@ -512,7 +514,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( AppRoute.Staking( userWalletId = userWalletId, cryptoCurrency = cryptoCurrency, - yieldId = option?.integrationId ?: return@launch, + integrationId = integrationId, ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt index 0dec24b4b9..1bcd5cd45a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt @@ -2,7 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingTarget import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState @@ -19,7 +19,7 @@ internal class SetTokenListTransformer( private val appCurrency: AppCurrency, private val clickIntents: WalletClickIntents, private val yieldSupplyApyMap: Map = emptyMap(), - private val stakingApyMap: Map> = emptyMap(), + private val stakingApyMap: Map> = emptyMap(), private val shouldShowMainPromo: Boolean, ) : WalletStateTransformer(userWallet.walletId) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt index 257e68fc6b..3a8a6b60d4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt @@ -18,7 +18,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.tokenlist.TokenList import com.tangem.domain.models.tokenlist.TokenList.GroupedByNetwork.NetworkGroup import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingTarget import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState @@ -38,7 +38,7 @@ internal class TokenListStateConverter( private val selectedWallet: UserWallet, private val clickIntents: WalletClickIntents, private val yieldModuleApyMap: Map, - private val stakingApyMap: Map>, + private val stakingApyMap: Map>, private val shouldShowMainPromo: Boolean, ) : Converter { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt index d1cbb38064..88201bad3e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt @@ -2,7 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingTarget import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase @@ -50,7 +50,7 @@ internal class AccountListSubscriber @AssistedInject constructor( return yieldSupplyApyFlowUseCase().distinctUntilChanged() } - private fun stakingApyFlow(): Flow>> { + private fun stakingApyFlow(): Flow>> { return stakingApyFlowUseCase().distinctUntilChanged() } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt index ae2648f6d7..2e12662158 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt @@ -9,7 +9,7 @@ import com.tangem.domain.core.utils.getOrElse import com.tangem.domain.models.PortfolioId import com.tangem.domain.models.account.AccountId import com.tangem.domain.models.tokenlist.TokenList -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingTarget import com.tangem.domain.tokens.error.TokenListError import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.account.AccountDependencies @@ -48,7 +48,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { expandedAccounts: Set, isAccountMode: Boolean, yieldSupplyApyMap: Map = emptyMap(), - stakingApyMap: Map> = emptyMap(), + stakingApyMap: Map> = emptyMap(), shouldShowMainPromo: Boolean = false, ) { val mainAccount = accountList.mainAccount @@ -89,7 +89,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { appCurrency: AppCurrency, portfolioId: PortfolioId, yieldSupplyApyMap: Map = emptyMap(), - stakingApyMap: Map> = emptyMap(), + stakingApyMap: Map> = emptyMap(), shouldShowMainPromo: Boolean, ) { val tokenList = maybeTokenList.getOrElse( @@ -128,7 +128,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { params: TokenConverterParams, appCurrency: AppCurrency, yieldSupplyApyMap: Map = emptyMap(), - stakingApyMap: Map> = emptyMap(), + stakingApyMap: Map> = emptyMap(), shouldShowMainPromo: Boolean, ) { stateController.update( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt index 36de78ef22..2adfb41c3e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt @@ -11,7 +11,7 @@ import com.tangem.domain.models.TotalFiatBalance import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.tokenlist.TokenList import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.StakingTarget import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase @@ -128,7 +128,7 @@ internal abstract class BasicTokenListSubscriber( params: TokenConverterParams, appCurrency: AppCurrency, yieldSupplyApyMap: Map, - stakingApyMap: Map>, + stakingApyMap: Map>, shouldShowMainPromo: Boolean, ) { stateHolder.update( @@ -156,7 +156,7 @@ internal abstract class BasicTokenListSubscriber( private fun yieldSupplyApyFlow(): Flow> = yieldSupplyApyFlowUseCase() .distinctUntilChanged() - private fun stakingApyFlow(): Flow>> = stakingApyFlowUseCase() + private fun stakingApyFlow(): Flow>> = stakingApyFlowUseCase() .distinctUntilChanged() private fun yieldSupplyGetShouldShowMainPromoFlow(): Flow = yieldSupplyGetShouldShowMainPromoUseCase()