From df32ea7ab67d160fe0381ff4f1dcec13cb02ca40 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 24 Nov 2025 13:48:52 +0200 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/di/domain/StakingDomainModule.kt | 61 +-- data/staking/detekt-baseline-debug.xml | 15 - .../staking/DefaultP2PEthPoolRepository.kt | 57 +- ....kt => DefaultStakeKitActionRepository.kt} | 6 +- .../data/staking/DefaultStakeKitRepository.kt | 421 +++++++++++++++ ...faultStakeKitTransactionHashRepository.kt} | 6 +- .../data/staking/DefaultStakingRepository.kt | 489 +----------------- .../data/staking/di/StakingDataModule.kt | 50 +- .../multi/DefaultMultiYieldBalanceFetcher.kt | 22 +- .../utils/YieldBalanceRequestBodyFactory.kt | 2 +- detekt_baseline_report.txt | 3 +- .../domain/staking/EstimateGasUseCase.kt | 6 +- .../domain/staking/FetchActionsUseCase.kt | 12 +- .../staking/FetchStakingOptionsUseCase.kt | 6 +- .../staking/FetchStakingTokensUseCase.kt | 6 +- .../domain/staking/GetActionsUseCase.kt | 6 +- ...GetConstructedStakingTransactionUseCase.kt | 6 +- .../staking/GetStakingAvailabilityUseCase.kt | 2 +- .../staking/GetStakingEntryInfoUseCase.kt | 6 +- .../staking/GetStakingTransactionsUseCase.kt | 6 +- .../tangem/domain/staking/GetYieldUseCase.kt | 8 +- .../domain/staking/IsAnyTokenStakedUseCase.kt | 2 +- .../staking/SaveUnsubmittedHashUseCase.kt | 6 +- .../staking/SendUnsubmittedHashesUseCase.kt | 6 +- .../domain/staking/SubmitHashUseCase.kt | 6 +- .../repositories/P2PEthPoolRepository.kt | 18 + ...ository.kt => StakeKitActionRepository.kt} | 2 +- .../repositories/StakeKitRepository.kt | 54 ++ ...t => StakeKitTransactionHashRepository.kt} | 2 +- .../staking/repositories/StakingRepository.kt | 42 -- .../staking/usecase/StakingApyFlowUseCase.kt | 6 +- .../impl/detekt-baseline-debug.xml | 1 - 32 files changed, 704 insertions(+), 637 deletions(-) delete mode 100644 data/staking/detekt-baseline-debug.xml rename data/staking/src/main/java/com/tangem/data/staking/{DefaultStakingActionRepository.kt => DefaultStakeKitActionRepository.kt} (87%) create mode 100644 data/staking/src/main/java/com/tangem/data/staking/DefaultStakeKitRepository.kt rename data/staking/src/main/java/com/tangem/data/staking/{DefaultStakingTransactionHashRepository.kt => DefaultStakeKitTransactionHashRepository.kt} (95%) rename domain/staking/src/main/java/com/tangem/domain/staking/repositories/{StakingActionRepository.kt => StakeKitActionRepository.kt} (92%) create mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakeKitRepository.kt rename domain/staking/src/main/java/com/tangem/domain/staking/repositories/{StakingTransactionHashRepository.kt => StakeKitTransactionHashRepository.kt} (88%) 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 9995a3753b..7112c00220 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 @@ -2,10 +2,11 @@ package com.tangem.tap.di.domain import com.tangem.domain.staking.* import com.tangem.domain.staking.repositories.P2PEthPoolRepository -import com.tangem.domain.staking.repositories.StakingActionRepository +import com.tangem.domain.staking.repositories.StakeKitActionRepository import com.tangem.domain.staking.repositories.StakingErrorResolver +import com.tangem.domain.staking.repositories.StakeKitRepository import com.tangem.domain.staking.repositories.StakingRepository -import com.tangem.domain.staking.repositories.StakingTransactionHashRepository +import com.tangem.domain.staking.repositories.StakeKitTransactionHashRepository import com.tangem.domain.staking.single.SingleYieldBalanceFetcher import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.walletmanager.WalletManagersFacade @@ -35,11 +36,11 @@ internal object StakingDomainModule { @Provides @Singleton fun provideGetStakingEntryInfoUseCase( - stakingRepository: StakingRepository, + stakeKitRepository: StakeKitRepository, stakingErrorResolver: StakingErrorResolver, ): GetStakingEntryInfoUseCase { return GetStakingEntryInfoUseCase( - stakingRepository = stakingRepository, + stakeKitRepository = stakeKitRepository, stakingErrorResolver = stakingErrorResolver, ) } @@ -47,11 +48,11 @@ internal object StakingDomainModule { @Provides @Singleton fun provideGetYieldUseCase( - stakingRepository: StakingRepository, + stakeKitRepository: StakeKitRepository, stakingErrorResolver: StakingErrorResolver, ): GetYieldUseCase { return GetYieldUseCase( - stakingRepository = stakingRepository, + stakeKitRepository = stakeKitRepository, stakingErrorResolver = stakingErrorResolver, ) } @@ -59,13 +60,13 @@ internal object StakingDomainModule { @Provides @Singleton fun provideFetchActionsUseCase( - stakingRepository: StakingRepository, - stakingActionRepository: StakingActionRepository, + stakeKitRepository: StakeKitRepository, + stakeKitActionRepository: StakeKitActionRepository, stakingErrorResolver: StakingErrorResolver, ): FetchActionsUseCase { return FetchActionsUseCase( - stakingRepository = stakingRepository, - stakingActionRepository = stakingActionRepository, + stakeKitRepository = stakeKitRepository, + stakeKitActionRepository = stakeKitActionRepository, stakingErrorResolver = stakingErrorResolver, ) } @@ -73,11 +74,11 @@ internal object StakingDomainModule { @Provides @Singleton fun provideGetActionsUseCase( - stakingActionRepository: StakingActionRepository, + stakeKitActionRepository: StakeKitActionRepository, stakingErrorResolver: StakingErrorResolver, ): GetActionsUseCase { return GetActionsUseCase( - stakingActionRepository = stakingActionRepository, + stakeKitActionRepository = stakeKitActionRepository, stakingErrorResolver = stakingErrorResolver, ) } @@ -85,11 +86,11 @@ internal object StakingDomainModule { @Provides @Singleton fun provideGetStakingTokensUseCase( - stakingRepository: StakingRepository, + stakeKitRepository: StakeKitRepository, stakingErrorResolver: StakingErrorResolver, ): FetchStakingTokensUseCase { return FetchStakingTokensUseCase( - stakingRepository = stakingRepository, + stakeKitRepository = stakeKitRepository, stakingErrorResolver = stakingErrorResolver, ) } @@ -97,12 +98,12 @@ internal object StakingDomainModule { @Provides @Singleton fun provideFetchStakingOptionsUseCase( - stakingRepository: StakingRepository, + stakeKitRepository: StakeKitRepository, p2pRepository: P2PEthPoolRepository, stakingErrorResolver: StakingErrorResolver, ): FetchStakingOptionsUseCase { return FetchStakingOptionsUseCase( - stakingRepository = stakingRepository, + stakeKitRepository = stakeKitRepository, p2pRepository = p2pRepository, stakingErrorResolver = stakingErrorResolver, ) @@ -123,11 +124,11 @@ internal object StakingDomainModule { @Provides @Singleton fun provideGetStakingTransactionsUseCase( - stakingRepository: StakingRepository, + stakeKitRepository: StakeKitRepository, stakingErrorResolver: StakingErrorResolver, ): GetStakingTransactionsUseCase { return GetStakingTransactionsUseCase( - stakingRepository = stakingRepository, + stakeKitRepository = stakeKitRepository, stakingErrorResolver = stakingErrorResolver, ) } @@ -135,11 +136,11 @@ internal object StakingDomainModule { @Provides @Singleton fun provideGasEstimateUseCase( - stakingRepository: StakingRepository, + stakeKitRepository: StakeKitRepository, stakingErrorResolver: StakingErrorResolver, ): EstimateGasUseCase { return EstimateGasUseCase( - stakingRepository = stakingRepository, + stakeKitRepository = stakeKitRepository, stakingErrorResolver = stakingErrorResolver, ) } @@ -147,11 +148,11 @@ internal object StakingDomainModule { @Provides @Singleton fun provideSubmitHashUseCase( - stakingTransactionHashRepository: StakingTransactionHashRepository, + stakeKitTransactionHashRepository: StakeKitTransactionHashRepository, stakingErrorResolver: StakingErrorResolver, ): SubmitHashUseCase { return SubmitHashUseCase( - stakingTransactionHashRepository = stakingTransactionHashRepository, + stakeKitTransactionHashRepository = stakeKitTransactionHashRepository, stakingErrorResolver = stakingErrorResolver, ) } @@ -159,11 +160,11 @@ internal object StakingDomainModule { @Provides @Singleton fun provideSaveUnsubmittedHashUseCase( - stakingTransactionHashRepository: StakingTransactionHashRepository, + stakeKitTransactionHashRepository: StakeKitTransactionHashRepository, stakingErrorResolver: StakingErrorResolver, ): SaveUnsubmittedHashUseCase { return SaveUnsubmittedHashUseCase( - stakingTransactionHashRepository = stakingTransactionHashRepository, + stakeKitTransactionHashRepository = stakeKitTransactionHashRepository, stakingErrorResolver = stakingErrorResolver, ) } @@ -181,11 +182,11 @@ internal object StakingDomainModule { @Provides @Singleton fun provideSendUnsubmittedHashesUseCase( - stakingTransactionHashRepository: StakingTransactionHashRepository, + stakeKitTransactionHashRepository: StakeKitTransactionHashRepository, stakingErrorResolver: StakingErrorResolver, ): SendUnsubmittedHashesUseCase { return SendUnsubmittedHashesUseCase( - stakingTransactionHashRepository = stakingTransactionHashRepository, + stakeKitTransactionHashRepository = stakeKitTransactionHashRepository, stakingErrorResolver = stakingErrorResolver, ) } @@ -193,11 +194,11 @@ internal object StakingDomainModule { @Provides @Singleton fun provideGetConstructedStakingTransactionUseCase( - stakingRepository: StakingRepository, + stakeKitRepository: StakeKitRepository, stakingErrorResolver: StakingErrorResolver, ): GetConstructedStakingTransactionUseCase { return GetConstructedStakingTransactionUseCase( - stakingRepository = stakingRepository, + stakeKitRepository = stakeKitRepository, stakingErrorResolver = stakingErrorResolver, ) } @@ -236,7 +237,7 @@ internal object StakingDomainModule { @Provides @Singleton - fun provideStakingApyFlowUseCase(stakingRepository: StakingRepository): StakingApyFlowUseCase { - return StakingApyFlowUseCase(stakingRepository) + fun provideStakingApyFlowUseCase(stakeKitRepository: StakeKitRepository): StakingApyFlowUseCase { + return StakingApyFlowUseCase(stakeKitRepository) } } \ No newline at end of file diff --git a/data/staking/detekt-baseline-debug.xml b/data/staking/detekt-baseline-debug.xml deleted file mode 100644 index 39dba1e36e..0000000000 --- a/data/staking/detekt-baseline-debug.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - MultilineLambdaItParameter:DefaultMultiYieldBalanceFetcher.kt$DefaultMultiYieldBalanceFetcher${ Timber.e(it, "Unable to fetch yield balances $params") yieldsBalancesStore.storeError(userWalletId = params.userWalletId, stakingIds = stakingIds) } - MultilineLambdaItParameter:DefaultMultiYieldBalanceFetcher.kt$DefaultMultiYieldBalanceFetcher${ Timber.e(it, "Unable to fetch yield balances $userWalletId") yieldsBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds) throw it } - MultilineLambdaItParameter:DefaultMultiYieldBalanceFetcher.kt$DefaultMultiYieldBalanceFetcher${ request.integrationId == it.integrationId && request.addresses.address == it.addresses.address } - MultilineLambdaItParameter:DefaultMultiYieldBalanceFetcher.kt$DefaultMultiYieldBalanceFetcher${ stakingId.integrationId == it.integrationId && stakingId.address == it.addresses.address } - MultilineLambdaItParameter:DefaultStakingRepository.kt$DefaultStakingRepository${ YieldConverter.convertListIgnoreErrors( input = it, onError = { Timber.e("Error converting one of the items in enabled yields: $it") }, ) } - NamedArguments:DefaultP2PEthPoolRepository.kt$DefaultP2PEthPoolRepository$getRewards(network.value, delegatorAddress, vaultAddress, period) - NoNameShadowing:DefaultStakingRepository.kt$DefaultStakingRepository${ Timber.e("Error converting one of the items in enabled yields: $it") } - NullableToStringCall:DefaultStakingRepository.kt$DefaultStakingRepository$${(response as? ApiResponse.Error)?.cause} - UseEmptyCounterpart:YieldBalanceRequestBodyFactory.kt$YieldBalanceRequestBodyFactory$listOf() - - 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 4af834f017..b97325f373 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 @@ -12,10 +12,15 @@ import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolDepositRequest import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolUnstakeRequest import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolWithdrawRequest import com.tangem.datasource.local.token.P2PEthPoolVaultsStore +import com.tangem.domain.staking.model.StakingAvailability +import com.tangem.domain.staking.model.StakingOption import com.tangem.domain.staking.model.ethpool.* import com.tangem.domain.staking.repositories.P2PEthPoolRepository import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext import timber.log.Timber @@ -192,7 +197,12 @@ internal class DefaultP2PEthPoolRepository( period: Int?, ): Either> = either { withContext(dispatchers.io) { - val response = p2pApi.getRewards(network.value, delegatorAddress, vaultAddress, period) + val response = p2pApi.getRewards( + network = network.value, + delegatorAddress = delegatorAddress, + vaultAddress = vaultAddress, + period = period, + ) when (response) { is ApiResponse.Success -> { val data = response.data @@ -206,4 +216,49 @@ internal class DefaultP2PEthPoolRepository( } } } + + override fun getStakingAvailability(): Flow { + return getVaultsFlow() + .distinctUntilChanged() + .map { vaults -> + if (vaults.isEmpty()) { + return@map StakingAvailability.TemporaryUnavailable + } + + val vault = findPublicVault(vaults = vaults) + + if (vault != null) { + StakingAvailability.Available(StakingOption.P2P(vault)) + } else { + StakingAvailability.TemporaryUnavailable + } + } + } + + override suspend fun getStakingAvailabilitySync(): StakingAvailability { + val vaults = getVaultsSync() + if (vaults.isEmpty()) { + return StakingAvailability.TemporaryUnavailable + } + + val vault = findPublicVault(vaults = vaults) + + return if (vault != null) { + StakingAvailability.Available(StakingOption.P2P(vault)) + } else { + StakingAvailability.TemporaryUnavailable + } + } + + private suspend fun getVaultsSync(): List { + return p2pEthPoolVaultsStore.getSync() + } + + private fun getVaultsFlow(): Flow> { + return p2pEthPoolVaultsStore.get() + } + + private fun findPublicVault(vaults: List): P2PEthPoolVault? { + return vaults.firstOrNull { vault -> !vault.isPrivate } + } } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingActionRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakeKitActionRepository.kt similarity index 87% rename from data/staking/src/main/java/com/tangem/data/staking/DefaultStakingActionRepository.kt rename to data/staking/src/main/java/com/tangem/data/staking/DefaultStakeKitActionRepository.kt index 030af40fb7..afc4dbef58 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingActionRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakeKitActionRepository.kt @@ -4,15 +4,15 @@ import com.tangem.datasource.local.token.StakingActionsStore import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.staking.model.stakekit.action.StakingAction -import com.tangem.domain.staking.repositories.StakingActionRepository +import com.tangem.domain.staking.repositories.StakeKitActionRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext -internal class DefaultStakingActionRepository( +internal class DefaultStakeKitActionRepository( private val stakingActionsStore: StakingActionsStore, private val dispatchers: CoroutineDispatcherProvider, -) : StakingActionRepository { +) : StakeKitActionRepository { override suspend fun store( userWalletId: UserWalletId, 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 new file mode 100644 index 0000000000..65c3e14fd7 --- /dev/null +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakeKitRepository.kt @@ -0,0 +1,421 @@ +package com.tangem.data.staking + +import android.util.Base64 +import com.squareup.moshi.Moshi +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.TransactionData +import com.tangem.blockchain.common.TransactionStatus +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.blockchainsdk.utils.fromNetworkId +import com.tangem.common.extensions.hexToBytes +import com.tangem.common.extensions.toCompressedPublicKey +import com.tangem.data.staking.converters.YieldConverter +import com.tangem.data.staking.converters.action.ActionStatusConverter +import com.tangem.data.staking.converters.action.EnterActionResponseConverter +import com.tangem.data.staking.converters.transaction.GasEstimateConverter +import com.tangem.data.staking.converters.transaction.StakingTransactionConverter +import com.tangem.data.staking.converters.transaction.StakingTransactionStatusConverter +import com.tangem.data.staking.converters.transaction.StakingTransactionTypeConverter +import com.tangem.datasource.api.common.response.ApiResponse +import com.tangem.datasource.api.common.response.getOrThrow +import com.tangem.datasource.api.stakekit.StakeKitApi +import com.tangem.datasource.api.stakekit.models.request.* +import com.tangem.datasource.api.stakekit.models.response.EnabledYieldsResponse +import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO +import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO +import com.tangem.datasource.api.stakekit.models.response.model.transaction.tron.TronStakeKitTransaction +import com.tangem.datasource.local.token.StakingYieldsStore +import com.tangem.datasource.local.token.converter.StakingNetworkTypeConverter +import com.tangem.datasource.local.token.converter.YieldTokenConverter +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.network.Network +import com.tangem.domain.models.staking.NetworkType +import com.tangem.domain.models.staking.action.StakingActionType +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.staking.model.StakingOption +import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.stakekit.action.StakingAction +import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType +import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus +import com.tangem.domain.staking.model.stakekit.transaction.ActionParams +import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate +import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction +import com.tangem.domain.staking.repositories.StakeKitRepository +import com.tangem.domain.staking.toggles.StakingFeatureToggles +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.withContext +import timber.log.Timber + +@Suppress("LargeClass", "LongParameterList", "TooManyFunctions") +internal class DefaultStakeKitRepository( + private val stakeKitApi: StakeKitApi, + private val stakingYieldsStore: StakingYieldsStore, + private val dispatchers: CoroutineDispatcherProvider, + private val walletManagersFacade: WalletManagersFacade, + private val stakingFeatureToggles: StakingFeatureToggles, + moshi: Moshi, +) : StakeKitRepository { + + private val transactionStatusConverter = StakingTransactionStatusConverter() + private val transactionTypeConverter = StakingTransactionTypeConverter() + private val actionStatusConverter = ActionStatusConverter() + + private val transactionConverter = StakingTransactionConverter( + transactionStatusConverter = transactionStatusConverter, + transactionTypeConverter = transactionTypeConverter, + ) + private val enterActionResponseConverter = EnterActionResponseConverter( + actionStatusConverter = actionStatusConverter, + transactionConverter = transactionConverter, + ) + + private val tronStakeKitTransactionAdapter by lazy { moshi.adapter(TronStakeKitTransaction::class.java) } + private val networkTypeAdapter by lazy { moshi.adapter(NetworkTypeDTO::class.java) } + private val stakingActionStatusAdapter by lazy { moshi.adapter(StakingActionStatusDTO::class.java) } + + override suspend fun fetchYields() { + withContext(dispatchers.io) { + val yieldsResponses = getAvailableStakeKitIntegrationsIds().map { + async { it.getYieldRequest() } + }.awaitAll() + + val yields = yieldsResponses.flatMap { response -> + when (response) { + is ApiResponse.Success -> response.data.data.filter { yield -> yield.isAvailable == true } + is ApiResponse.Error -> { + Timber.e("Error fetching enabled yields: ${response.cause}") + emptyList() + } + } + } + stakingYieldsStore.store(yields) + } + } + + override suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield { + return withContext(dispatchers.io) { + val rawCurrencyId = cryptoCurrencyId.rawCurrencyId ?: error("Staking custom tokens is not available") + + val prefetchedYield = findPrefetchedYield( + yields = getEnabledYieldsSync(), + currencyId = rawCurrencyId, + symbol = symbol, + ) + + prefetchedYield ?: error("Staking is unavailable") + } + } + + override suspend fun getYield(yieldId: String): Yield { + return withContext(dispatchers.io) { + getEnabledYieldsSync().find { it.id == yieldId } ?: error("Staking is unavailable") + } + } + + override suspend fun getActions( + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency, + networkType: NetworkType, + stakingActionStatus: StakingActionStatus, + ): List { + return withContext(dispatchers.io) { + val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty() + + val networkTypeDto = StakingNetworkTypeConverter.convertBack(networkType) + val networkTypeString = networkTypeDto.extractJsonName() + + val actionStatusDTO = actionStatusConverter.convertBack(stakingActionStatus) + val actionStatusString = actionStatusDTO.extractJsonName() + + enterActionResponseConverter.convertListIgnoreErrors( + input = stakeKitApi.getActions( + walletAddress = address, + network = networkTypeString, + status = actionStatusString, + ).getOrThrow().data, + onError = { Timber.e("Error converting staking actions list: $it") }, + ) + } + } + + private suspend fun StakingIntegrationID.StakeKit.getYieldRequest(): ApiResponse { + return when (this) { + is StakingIntegrationID.StakeKit.Coin -> stakeKitApi.getEnabledYields( + preferredValidatorsOnly = false, + network = networkId, + ) + is StakingIntegrationID.StakeKit.EthereumToken -> stakeKitApi.getEnabledYields( + preferredValidatorsOnly = false, + yieldId = value, + network = networkId, + ) + } + } + + private fun getAvailableStakeKitIntegrationsIds(): List { + return StakingIntegrationID.StakeKit.entries.filterNot { + it.blockchain == Blockchain.Cardano && !stakingFeatureToggles.isCardanoStakingEnabled + } + } + + private fun NetworkTypeDTO.extractJsonName(): String { + return networkTypeAdapter.toJson(this).replace("\"", "") + } + + private fun StakingActionStatusDTO.extractJsonName(): String { + return stakingActionStatusAdapter.toJson(this).replace("\"", "") + } + + override suspend fun getEntryInfo(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): StakingEntryInfo { + return withContext(dispatchers.io) { + val yield = getYield(cryptoCurrencyId, symbol) + + StakingEntryInfo( + tokenSymbol = yield.token.symbol, + ) + } + } + + override suspend fun createAction( + userWalletId: UserWalletId, + network: Network, + params: ActionParams, + ): StakingAction { + return withContext(dispatchers.io) { + val response = when (params.actionCommonType) { + is StakingActionCommonType.Enter -> stakeKitApi.createEnterAction( + createActionRequestBody( + userWalletId, + network, + params, + ), + ) + is StakingActionCommonType.Exit -> stakeKitApi.createExitAction( + createActionRequestBody( + userWalletId, + network, + params, + ), + ) + is StakingActionCommonType.Pending -> stakeKitApi.createPendingAction( + createPendingActionRequestBody(params), + ) + } + + enterActionResponseConverter.convert(response.getOrThrow()) + } + } + + override suspend fun estimateGas( + userWalletId: UserWalletId, + network: Network, + params: ActionParams, + ): StakingGasEstimate { + return withContext(dispatchers.io) { + val gasEstimateDTO = when (params.actionCommonType) { + is StakingActionCommonType.Enter -> stakeKitApi.estimateGasOnEnter( + createActionRequestBody( + userWalletId, + network, + params, + ), + ) + is StakingActionCommonType.Exit -> stakeKitApi.estimateGasOnExit( + createActionRequestBody( + userWalletId, + network, + params, + ), + ) + is StakingActionCommonType.Pending -> stakeKitApi.estimateGasOnPending( + createPendingActionRequestBody(params), + ) + } + + GasEstimateConverter.convert(gasEstimateDTO.getOrThrow()) + } + } + + override suspend fun constructTransaction( + networkId: String, + fee: Fee, + amount: Amount, + transactionId: String, + ): Pair { + return withContext(dispatchers.io) { + val transactionResponse = stakeKitApi.constructTransaction( + transactionId = transactionId, + body = ConstructTransactionRequestBody(), + ) + + val transaction = transactionConverter.convert(transactionResponse.getOrThrow()) + val unsignedTransaction = + transaction.unsignedTransaction ?: error("No unsigned transaction available") + val transactionData = TransactionData.Compiled( + value = getTransactionDataType(networkId, unsignedTransaction), + fee = fee, + amount = amount, + status = TransactionStatus.Unconfirmed, + ) + + transaction to transactionData + } + } + + private suspend fun createActionRequestBody( + userWalletId: UserWalletId, + network: Network, + params: ActionParams, + ): ActionRequestBody { + return ActionRequestBody( + integrationId = params.integrationId, + addresses = Address( + address = params.address, + additionalAddresses = createAdditionalAddresses(userWalletId, network, params), + ), + args = ActionRequestBodyArgs( + amount = params.amount.toPlainString(), + inputToken = YieldTokenConverter.convertBack(params.token), + validatorAddress = params.validatorAddress, + validatorAddresses = listOf(params.validatorAddress), // check on other networks + tronResource = getTronResource(network), + ), + ) + } + + private fun createPendingActionRequestBody(params: ActionParams): PendingActionRequestBody { + return PendingActionRequestBody( + integrationId = params.integrationId, + type = params.type ?: StakingActionType.UNKNOWN, + passthrough = params.passthrough.orEmpty(), + args = ActionRequestBodyArgs( + amount = params.amount.toPlainString(), + validatorAddress = params.validatorAddress, + validatorAddresses = listOf(params.validatorAddress), + ), + ) + } + + private suspend fun createAdditionalAddresses( + userWalletId: UserWalletId, + network: Network, + params: ActionParams, + ): Address.AdditionalAddresses? { + val selectedWallet = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) + return when (params.token.network) { + NetworkType.COSMOS -> Address.AdditionalAddresses( + cosmosPubKey = Base64.encodeToString( + /* input = */ selectedWallet?.wallet?.publicKey?.blockchainKey?.toCompressedPublicKey(), + /* flags = */ Base64.NO_WRAP, + ), + ) + else -> null + } + } + + private fun getTransactionDataType(networkId: String, unsignedTransaction: String): TransactionData.Compiled.Data { + return when (Blockchain.fromId(networkId)) { + Blockchain.Solana, + Blockchain.Cosmos, + -> TransactionData.Compiled.Data.Bytes(unsignedTransaction.hexToBytes()) + Blockchain.BSC, + Blockchain.Ethereum, + Blockchain.TON, + Blockchain.Cardano, + -> TransactionData.Compiled.Data.RawString(unsignedTransaction) + Blockchain.Tron -> { + val tronStakeKitTransaction = tronStakeKitTransactionAdapter.fromJson(unsignedTransaction) + ?: error("Failed to parse Tron StakeKit transaction") + TransactionData.Compiled.Data.RawString(tronStakeKitTransaction.rawDataHex) + } + else -> error("Unsupported blockchain") + } + } + + private fun findPrefetchedYield(yields: List, currencyId: CryptoCurrency.RawID, symbol: String): Yield? { + return yields.find { yield -> + yield.tokens.any { it.coinGeckoId == currencyId.value && it.symbol == symbol } + } + } + + private suspend fun getEnabledYieldsSync(): List { + return YieldConverter.convertListIgnoreErrors( + input = stakingYieldsStore.getSync(), + onError = { Timber.e("Error converting one of the items in enabled yields: $it") }, + ) + } + + override fun getEnabledYields(): Flow> { + return stakingYieldsStore.get().map { yields -> + YieldConverter.convertListIgnoreErrors( + input = yields, + onError = { Timber.e("Error converting one of the items in enabled yields: $it") }, + ) + } + } + + override fun getStakingAvailability( + rawCurrencyId: CryptoCurrency.RawID, + symbol: String, + ): Flow { + return getEnabledYields() + .distinctUntilChanged() + .map { yields -> + if (yields.isEmpty()) { + return@map StakingAvailability.TemporaryUnavailable + } + + val prefetchedYield = findPrefetchedYield( + yields = yields, + currencyId = rawCurrencyId, + symbol = symbol, + ) + + if (prefetchedYield != null) { + StakingAvailability.Available(StakingOption.StakeKit(prefetchedYield)) + } else { + StakingAvailability.TemporaryUnavailable + } + } + } + + override suspend fun getStakingAvailabilitySync( + rawCurrencyId: CryptoCurrency.RawID, + symbol: String, + ): StakingAvailability { + val yields = getEnabledYieldsSync() + if (yields.isEmpty()) { + return StakingAvailability.TemporaryUnavailable + } + + val prefetchedYield = findPrefetchedYield( + yields = yields, + currencyId = rawCurrencyId, + symbol = symbol, + ) + + return if (prefetchedYield != null) { + StakingAvailability.Available(StakingOption.StakeKit(prefetchedYield)) + } else { + StakingAvailability.TemporaryUnavailable + } + } + + private fun getTronResource(network: Network): TronResource? { + val blockchain = Blockchain.fromNetworkId(network.backendId) + + return if (blockchain == Blockchain.Tron || blockchain == Blockchain.TronTestnet) { + TronResource.ENERGY + } else { + null + } + } +} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingTransactionHashRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakeKitTransactionHashRepository.kt similarity index 95% rename from data/staking/src/main/java/com/tangem/data/staking/DefaultStakingTransactionHashRepository.kt rename to data/staking/src/main/java/com/tangem/data/staking/DefaultStakeKitTransactionHashRepository.kt index 0d12489648..0147b2da07 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingTransactionHashRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakeKitTransactionHashRepository.kt @@ -6,16 +6,16 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObjectListSync import com.tangem.domain.staking.model.UnsubmittedTransactionMetadata -import com.tangem.domain.staking.repositories.StakingTransactionHashRepository +import com.tangem.domain.staking.repositories.StakeKitTransactionHashRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext import timber.log.Timber -internal class DefaultStakingTransactionHashRepository( +internal class DefaultStakeKitTransactionHashRepository( private val stakeKitApi: StakeKitApi, private val appPreferencesStore: AppPreferencesStore, private val dispatchers: CoroutineDispatcherProvider, -) : StakingTransactionHashRepository { +) : StakeKitTransactionHashRepository { override suspend fun submitHash(transactionId: String, transactionHash: String) { withContext(dispatchers.io) { 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 689dd885f3..cfa19d6761 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 @@ -1,204 +1,38 @@ package com.tangem.data.staking -import android.util.Base64 import arrow.core.getOrElse -import com.squareup.moshi.Moshi -import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.TransactionData -import com.tangem.blockchain.common.TransactionStatus -import com.tangem.blockchain.common.transaction.Fee -import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.toBlockchain -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.extensions.toCompressedPublicKey -import com.tangem.data.staking.converters.YieldConverter -import com.tangem.data.staking.converters.action.ActionStatusConverter -import com.tangem.data.staking.converters.action.EnterActionResponseConverter -import com.tangem.data.staking.converters.transaction.GasEstimateConverter -import com.tangem.data.staking.converters.transaction.StakingTransactionConverter -import com.tangem.data.staking.converters.transaction.StakingTransactionStatusConverter -import com.tangem.data.staking.converters.transaction.StakingTransactionTypeConverter import com.tangem.data.staking.store.YieldsBalancesStore -import com.tangem.datasource.api.common.response.ApiResponse -import com.tangem.datasource.api.common.response.getOrThrow -import com.tangem.datasource.api.stakekit.StakeKitApi -import com.tangem.datasource.api.stakekit.models.request.* -import com.tangem.datasource.api.stakekit.models.response.EnabledYieldsResponse -import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO -import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO -import com.tangem.datasource.api.stakekit.models.response.model.transaction.tron.TronStakeKitTransaction -import com.tangem.datasource.local.token.P2PEthPoolVaultsStore -import com.tangem.datasource.local.token.StakingYieldsStore -import com.tangem.datasource.local.token.converter.StakingNetworkTypeConverter -import com.tangem.datasource.local.token.converter.YieldTokenConverter import com.tangem.domain.card.common.TapWorkarounds.isWallet2 import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network -import com.tangem.domain.models.staking.NetworkType import com.tangem.domain.models.staking.YieldBalance -import com.tangem.domain.models.staking.action.StakingActionType import com.tangem.domain.models.wallet.UserWallet 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.staking.model.StakingOption -import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault -import com.tangem.domain.staking.model.stakekit.Yield -import com.tangem.domain.staking.model.stakekit.action.StakingAction -import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType -import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus -import com.tangem.domain.staking.model.stakekit.transaction.ActionParams -import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate -import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction +import com.tangem.domain.staking.repositories.P2PEthPoolRepository +import com.tangem.domain.staking.repositories.StakeKitRepository import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.staking.toggles.StakingFeatureToggles -import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.lib.crypto.BlockchainUtils.isCardano import com.tangem.lib.crypto.BlockchainUtils.isSolana import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.flow.* import kotlinx.coroutines.withContext -import timber.log.Timber @Suppress("LargeClass", "LongParameterList", "TooManyFunctions") internal class DefaultStakingRepository( - private val stakeKitApi: StakeKitApi, - private val stakingYieldsStore: StakingYieldsStore, - private val p2pEthPoolVaultsStore: P2PEthPoolVaultsStore, + private val stakeKitRepository: StakeKitRepository, + private val p2pEthPoolRepository: P2PEthPoolRepository, private val stakingBalanceStoreV2: YieldsBalancesStore, private val dispatchers: CoroutineDispatcherProvider, - private val walletManagersFacade: WalletManagersFacade, private val getUserWalletUseCase: GetUserWalletUseCase, private val stakingFeatureToggles: StakingFeatureToggles, - moshi: Moshi, ) : StakingRepository { - private val transactionStatusConverter = StakingTransactionStatusConverter() - private val transactionTypeConverter = StakingTransactionTypeConverter() - private val actionStatusConverter = ActionStatusConverter() - - private val transactionConverter = StakingTransactionConverter( - transactionStatusConverter = transactionStatusConverter, - transactionTypeConverter = transactionTypeConverter, - ) - private val enterActionResponseConverter = EnterActionResponseConverter( - actionStatusConverter = actionStatusConverter, - transactionConverter = transactionConverter, - ) - - private val tronStakeKitTransactionAdapter by lazy { moshi.adapter(TronStakeKitTransaction::class.java) } - private val networkTypeAdapter by lazy { moshi.adapter(NetworkTypeDTO::class.java) } - private val stakingActionStatusAdapter by lazy { moshi.adapter(StakingActionStatusDTO::class.java) } - - override suspend fun fetchYields() { - withContext(dispatchers.io) { - val yieldsResponses = getAvailableStakeKitIntegrationsIds().map { - async { it.getYieldRequest() } - }.awaitAll() - - val yields = yieldsResponses.flatMap { response -> - when (response) { - is ApiResponse.Success -> response.data.data.filter { yield -> yield.isAvailable == true } - else -> { - Timber.e("Error fetching enabled yields: ${(response as? ApiResponse.Error)?.cause}") - emptyList() - } - } - } - stakingYieldsStore.store(yields) - } - } - - override suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield { - return withContext(dispatchers.io) { - val rawCurrencyId = cryptoCurrencyId.rawCurrencyId ?: error("Staking custom tokens is not available") - - val prefetchedYield = findPrefetchedYield( - yields = getEnabledYieldsSync(), - currencyId = rawCurrencyId, - symbol = symbol, - ) - - prefetchedYield ?: error("Staking is unavailable") - } - } - - override suspend fun getYield(yieldId: String): Yield { - return withContext(dispatchers.io) { - getEnabledYieldsSync().find { it.id == yieldId } ?: error("Staking is unavailable") - } - } - - override suspend fun getActions( - userWalletId: UserWalletId, - cryptoCurrency: CryptoCurrency, - networkType: NetworkType, - stakingActionStatus: StakingActionStatus, - ): List { - return withContext(dispatchers.io) { - val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty() - - val networkTypeDto = StakingNetworkTypeConverter.convertBack(networkType) - val networkTypeString = networkTypeDto.extractJsonName() - - val actionStatusDTO = actionStatusConverter.convertBack(stakingActionStatus) - val actionStatusString = actionStatusDTO.extractJsonName() - - enterActionResponseConverter.convertListIgnoreErrors( - input = stakeKitApi.getActions( - walletAddress = address, - network = networkTypeString, - status = actionStatusString, - ).getOrThrow().data, - onError = { Timber.e("Error converting staking actions list: $it") }, - ) - } - } - - private suspend fun StakingIntegrationID.StakeKit.getYieldRequest(): ApiResponse { - return when (this) { - is StakingIntegrationID.StakeKit.Coin -> stakeKitApi.getEnabledYields( - preferredValidatorsOnly = false, - network = networkId, - ) - is StakingIntegrationID.StakeKit.EthereumToken -> stakeKitApi.getEnabledYields( - preferredValidatorsOnly = false, - yieldId = value, - network = networkId, - ) - } - } - - private fun getAvailableStakeKitIntegrationsIds(): List { - return StakingIntegrationID.StakeKit.entries.filterNot { - it.blockchain == Blockchain.Cardano && !stakingFeatureToggles.isCardanoStakingEnabled - } - } - - private fun NetworkTypeDTO.extractJsonName(): String { - return networkTypeAdapter.toJson(this).replace("\"", "") - } - - private fun StakingActionStatusDTO.extractJsonName(): String { - return stakingActionStatusAdapter.toJson(this).replace("\"", "") - } - - override suspend fun getEntryInfo(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): StakingEntryInfo { - return withContext(dispatchers.io) { - val yield = getYield(cryptoCurrencyId, symbol) - - StakingEntryInfo( - tokenSymbol = yield.token.symbol, - ) - } - } - override fun getStakingAvailability( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, @@ -222,14 +56,16 @@ internal class DefaultStakingRepository( val stakingIntegration = StakingIntegrationID.create(currencyId = cryptoCurrency.id) - when (stakingIntegration) { - is StakingIntegrationID.P2P -> subscribeToP2PStakingAvailability() - is StakingIntegrationID.StakeKit -> subscribeToStakeKitStakingAvailability( + val availabilityFlow = when (stakingIntegration) { + is StakingIntegrationID.P2P -> p2pEthPoolRepository.getStakingAvailability() + is StakingIntegrationID.StakeKit -> stakeKitRepository.getStakingAvailability( rawCurrencyId, - cryptoCurrency, + cryptoCurrency.symbol, ) - null -> send(StakingAvailability.Unavailable) + null -> flowOf(StakingAvailability.Unavailable) } + + availabilityFlow.collect { send(it) } } } @@ -254,39 +90,25 @@ internal class DefaultStakingRepository( ?: return StakingAvailability.Unavailable return when (stakingIntegration) { - is StakingIntegrationID.P2P -> { - val vaults = getP2PEthPoolVaultsSync() - if (vaults.isEmpty()) { - return StakingAvailability.TemporaryUnavailable - } + is StakingIntegrationID.P2P -> p2pEthPoolRepository.getStakingAvailabilitySync() + is StakingIntegrationID.StakeKit -> stakeKitRepository.getStakingAvailabilitySync( + rawCurrencyId, + cryptoCurrency.symbol, + ) + } + } - val vault = findP2PEthPoolVault( - vaults = vaults, - ) + override suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean { + return withContext(dispatchers.default) { + val balances = stakingBalanceStoreV2.getAllSyncOrNull(userWalletId) ?: return@withContext false - if (vault != null) { - StakingAvailability.Available(StakingOption.P2P(vault)) - } else { - StakingAvailability.TemporaryUnavailable + val hasDataYieldBalance by lazy { + balances.any { yieldBalance -> + (yieldBalance as? YieldBalance.Data)?.balance?.items?.isNotEmpty() == true } } - is StakingIntegrationID.StakeKit -> { - val yields = getEnabledYieldsSync() - if (yields.isEmpty()) { - return StakingAvailability.TemporaryUnavailable - } - val prefetchedYield = findPrefetchedYield( - yields = yields, - currencyId = rawCurrencyId, - symbol = cryptoCurrency.symbol, - ) - - when { - prefetchedYield != null -> StakingAvailability.Available(StakingOption.StakeKit(prefetchedYield)) - else -> StakingAvailability.TemporaryUnavailable - } - } + balances.isNotEmpty() && hasDataYieldBalance } } @@ -316,267 +138,6 @@ internal class DefaultStakingRepository( } } - override suspend fun createAction( - userWalletId: UserWalletId, - network: Network, - params: ActionParams, - ): StakingAction { - return withContext(dispatchers.io) { - val response = when (params.actionCommonType) { - is StakingActionCommonType.Enter -> stakeKitApi.createEnterAction( - createActionRequestBody( - userWalletId, - network, - params, - ), - ) - is StakingActionCommonType.Exit -> stakeKitApi.createExitAction( - createActionRequestBody( - userWalletId, - network, - params, - ), - ) - is StakingActionCommonType.Pending -> stakeKitApi.createPendingAction( - createPendingActionRequestBody(params), - ) - } - - enterActionResponseConverter.convert(response.getOrThrow()) - } - } - - override suspend fun estimateGas( - userWalletId: UserWalletId, - network: Network, - params: ActionParams, - ): StakingGasEstimate { - return withContext(dispatchers.io) { - val gasEstimateDTO = when (params.actionCommonType) { - is StakingActionCommonType.Enter -> stakeKitApi.estimateGasOnEnter( - createActionRequestBody( - userWalletId, - network, - params, - ), - ) - is StakingActionCommonType.Exit -> stakeKitApi.estimateGasOnExit( - createActionRequestBody( - userWalletId, - network, - params, - ), - ) - is StakingActionCommonType.Pending -> stakeKitApi.estimateGasOnPending( - createPendingActionRequestBody(params), - ) - } - - GasEstimateConverter.convert(gasEstimateDTO.getOrThrow()) - } - } - - override suspend fun constructTransaction( - networkId: String, - fee: Fee, - amount: Amount, - transactionId: String, - ): Pair { - return withContext(dispatchers.io) { - val transactionResponse = stakeKitApi.constructTransaction( - transactionId = transactionId, - body = ConstructTransactionRequestBody(), - ) - - val transaction = transactionConverter.convert(transactionResponse.getOrThrow()) - val unsignedTransaction = - transaction.unsignedTransaction ?: error("No unsigned transaction available") - val transactionData = TransactionData.Compiled( - value = getTransactionDataType(networkId, unsignedTransaction), - fee = fee, - amount = amount, - status = TransactionStatus.Unconfirmed, - ) - - transaction to transactionData - } - } - - override suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean { - return withContext(dispatchers.default) { - val balances = stakingBalanceStoreV2.getAllSyncOrNull(userWalletId) ?: return@withContext false - - val hasDataYieldBalance by lazy { - balances.any { yieldBalance -> - (yieldBalance as? YieldBalance.Data)?.balance?.items?.isNotEmpty() == true - } - } - - balances.isNotEmpty() && hasDataYieldBalance - } - } - - private suspend fun createActionRequestBody( - userWalletId: UserWalletId, - network: Network, - params: ActionParams, - ): ActionRequestBody { - return ActionRequestBody( - integrationId = params.integrationId, - addresses = Address( - address = params.address, - additionalAddresses = createAdditionalAddresses(userWalletId, network, params), - ), - args = ActionRequestBodyArgs( - amount = params.amount.toPlainString(), - inputToken = YieldTokenConverter.convertBack(params.token), - validatorAddress = params.validatorAddress, - validatorAddresses = listOf(params.validatorAddress), // check on other networks - tronResource = getTronResource(network), - ), - ) - } - - private fun createPendingActionRequestBody(params: ActionParams): PendingActionRequestBody { - return PendingActionRequestBody( - integrationId = params.integrationId, - type = params.type ?: StakingActionType.UNKNOWN, - passthrough = params.passthrough.orEmpty(), - args = ActionRequestBodyArgs( - amount = params.amount.toPlainString(), - validatorAddress = params.validatorAddress, - validatorAddresses = listOf(params.validatorAddress), - ), - ) - } - - private suspend fun createAdditionalAddresses( - userWalletId: UserWalletId, - network: Network, - params: ActionParams, - ): Address.AdditionalAddresses? { - val selectedWallet = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) - return when (params.token.network) { - NetworkType.COSMOS -> Address.AdditionalAddresses( - cosmosPubKey = Base64.encodeToString( - /* input = */ selectedWallet?.wallet?.publicKey?.blockchainKey?.toCompressedPublicKey(), - /* flags = */ Base64.NO_WRAP, - ), - ) - else -> null - } - } - - private fun getTransactionDataType(networkId: String, unsignedTransaction: String): TransactionData.Compiled.Data { - return when (Blockchain.fromId(networkId)) { - Blockchain.Solana, - Blockchain.Cosmos, - -> TransactionData.Compiled.Data.Bytes(unsignedTransaction.hexToBytes()) - Blockchain.BSC, - Blockchain.Ethereum, - Blockchain.TON, - Blockchain.Cardano, - -> TransactionData.Compiled.Data.RawString(unsignedTransaction) - Blockchain.Tron -> { - val tronStakeKitTransaction = tronStakeKitTransactionAdapter.fromJson(unsignedTransaction) - ?: error("Failed to parse Tron StakeKit transaction") - TransactionData.Compiled.Data.RawString(tronStakeKitTransaction.rawDataHex) - } - else -> error("Unsupported blockchain") - } - } - - private fun findPrefetchedYield(yields: List, currencyId: CryptoCurrency.RawID, symbol: String): Yield? { - return yields.find { yield -> - yield.tokens.any { it.coinGeckoId == currencyId.value && it.symbol == symbol } - } - } - - private suspend fun getEnabledYieldsSync(): List { - return YieldConverter.convertListIgnoreErrors( - input = stakingYieldsStore.getSync(), - onError = { Timber.e("Error converting one of the items in enabled yields: $it") }, - ) - } - - override fun getEnabledYields(): Flow> { - return stakingYieldsStore.get().map { - YieldConverter.convertListIgnoreErrors( - input = it, - onError = { Timber.e("Error converting one of the items in enabled yields: $it") }, - ) - } - } - - private suspend fun getP2PEthPoolVaultsSync(): List { - return p2pEthPoolVaultsStore.getSync() - } - - private fun getP2PEthPoolVaults(): Flow> { - return p2pEthPoolVaultsStore.get() - } - - private fun findP2PEthPoolVault(vaults: List): P2PEthPoolVault? { - return vaults.firstOrNull { vault -> !vault.isPrivate } - } - - private fun ProducerScope.subscribeToP2PStakingAvailability() { - getP2PEthPoolVaults() - .distinctUntilChanged() - .onEach { vaults -> - if (vaults.isEmpty()) { - send(StakingAvailability.TemporaryUnavailable) - return@onEach - } - - val vault = findP2PEthPoolVault(vaults = vaults) - - if (vault != null) { - send(StakingAvailability.Available(StakingOption.P2P(vault))) - } else { - send(StakingAvailability.TemporaryUnavailable) - } - } - .launchIn(this) - } - - private fun ProducerScope.subscribeToStakeKitStakingAvailability( - rawCurrencyId: CryptoCurrency.RawID, - cryptoCurrency: CryptoCurrency, - ) { - getEnabledYields() - .distinctUntilChanged() - .onEach { yields -> - if (yields.isEmpty()) { - send(StakingAvailability.TemporaryUnavailable) - return@onEach - } - - val prefetchedYield = findPrefetchedYield( - yields = yields, - currencyId = rawCurrencyId, - symbol = cryptoCurrency.symbol, - ) - - if (prefetchedYield != null) { - send(StakingAvailability.Available(StakingOption.StakeKit(prefetchedYield))) - } else { - send(StakingAvailability.TemporaryUnavailable) - } - } - .launchIn(this) - } - - private fun getTronResource(network: Network): TronResource? { - val blockchain = Blockchain.fromNetworkId(network.backendId) - - return if (blockchain == Blockchain.Tron || blockchain == Blockchain.TronTestnet) { - TronResource.ENERGY - } else { - null - } - } - companion object { private val INVALID_BATCHES_FOR_SOLANA = listOf("AC01", "CB79") } 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 8e82d81a8f..6b51907e70 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 @@ -4,10 +4,11 @@ import com.squareup.moshi.Moshi import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.data.staking.DefaultP2PEthPoolRepository -import com.tangem.data.staking.DefaultStakingActionRepository +import com.tangem.data.staking.DefaultStakeKitActionRepository import com.tangem.data.staking.DefaultStakingErrorResolver +import com.tangem.data.staking.DefaultStakeKitRepository import com.tangem.data.staking.DefaultStakingRepository -import com.tangem.data.staking.DefaultStakingTransactionHashRepository +import com.tangem.data.staking.DefaultStakeKitTransactionHashRepository import com.tangem.data.staking.converters.error.StakeKitErrorConverter import com.tangem.data.staking.store.YieldsBalancesStore import com.tangem.data.staking.toggles.DefaultStakingFeatureToggles @@ -21,10 +22,11 @@ import com.tangem.datasource.local.token.P2PEthPoolVaultsStore import com.tangem.datasource.local.token.StakingActionsStore import com.tangem.datasource.local.token.StakingYieldsStore import com.tangem.domain.staking.repositories.P2PEthPoolRepository -import com.tangem.domain.staking.repositories.StakingActionRepository +import com.tangem.domain.staking.repositories.StakeKitActionRepository import com.tangem.domain.staking.repositories.StakingErrorResolver +import com.tangem.domain.staking.repositories.StakeKitRepository import com.tangem.domain.staking.repositories.StakingRepository -import com.tangem.domain.staking.repositories.StakingTransactionHashRepository +import com.tangem.domain.staking.repositories.StakeKitTransactionHashRepository import com.tangem.domain.staking.toggles.StakingFeatureToggles import com.tangem.domain.staking.utils.StakingCleaner import com.tangem.domain.walletmanager.WalletManagersFacade @@ -42,30 +44,44 @@ internal object StakingDataModule { @Provides @Singleton - fun provideStakingRepository( + fun provideStakeKitRepository( stakeKitApi: StakeKitApi, stakingYieldsStore: StakingYieldsStore, - p2pEthPoolVaultsStore: P2PEthPoolVaultsStore, - yieldsBalancesStore: YieldsBalancesStore, dispatchers: CoroutineDispatcherProvider, walletManagersFacade: WalletManagersFacade, - getUserWalletUseCase: GetUserWalletUseCase, stakingFeatureToggles: StakingFeatureToggles, @NetworkMoshi moshi: Moshi, - ): StakingRepository { - return DefaultStakingRepository( + ): StakeKitRepository { + return DefaultStakeKitRepository( stakeKitApi = stakeKitApi, stakingYieldsStore = stakingYieldsStore, - p2pEthPoolVaultsStore = p2pEthPoolVaultsStore, - stakingBalanceStoreV2 = yieldsBalancesStore, dispatchers = dispatchers, walletManagersFacade = walletManagersFacade, - getUserWalletUseCase = getUserWalletUseCase, stakingFeatureToggles = stakingFeatureToggles, moshi = moshi, ) } + @Provides + @Singleton + fun provideStakingRepository( + stakeKitRepository: StakeKitRepository, + p2pEthPoolRepository: P2PEthPoolRepository, + yieldsBalancesStore: YieldsBalancesStore, + dispatchers: CoroutineDispatcherProvider, + getUserWalletUseCase: GetUserWalletUseCase, + stakingFeatureToggles: StakingFeatureToggles, + ): StakingRepository { + return DefaultStakingRepository( + stakeKitRepository = stakeKitRepository, + p2pEthPoolRepository = p2pEthPoolRepository, + stakingBalanceStoreV2 = yieldsBalancesStore, + dispatchers = dispatchers, + getUserWalletUseCase = getUserWalletUseCase, + stakingFeatureToggles = stakingFeatureToggles, + ) + } + @Provides @Singleton fun provideP2PEthPoolRepository( @@ -86,8 +102,8 @@ internal object StakingDataModule { stakeKitApi: StakeKitApi, appPreferencesStore: AppPreferencesStore, dispatchers: CoroutineDispatcherProvider, - ): StakingTransactionHashRepository { - return DefaultStakingTransactionHashRepository( + ): StakeKitTransactionHashRepository { + return DefaultStakeKitTransactionHashRepository( stakeKitApi = stakeKitApi, appPreferencesStore = appPreferencesStore, dispatchers = dispatchers, @@ -99,8 +115,8 @@ internal object StakingDataModule { fun provideStakingActionRepository( stakingActionsStore: StakingActionsStore, dispatchers: CoroutineDispatcherProvider, - ): StakingActionRepository { - return DefaultStakingActionRepository( + ): StakeKitActionRepository { + return DefaultStakeKitActionRepository( stakingActionsStore = stakingActionsStore, dispatchers = dispatchers, ) diff --git a/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt b/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt index 62f2e5e33e..eca2f69f33 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt @@ -69,8 +69,8 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor( fetch(userWalletId = params.userWalletId, stakingIds = availableStakingIds) } - .onLeft { - Timber.e(it, "Unable to fetch yield balances $params") + .onLeft { throwable -> + Timber.e(throwable, "Unable to fetch yield balances $params") yieldsBalancesStore.storeError(userWalletId = params.userWalletId, stakingIds = stakingIds) } @@ -163,21 +163,21 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor( if (!allResponsesReceived(requests, yieldBalances)) { val values = stakingIds.filter { stakingId -> - yieldBalances.none { - stakingId.integrationId == it.integrationId && - stakingId.address == it.addresses.address + yieldBalances.none { balanceWrapper -> + stakingId.integrationId == balanceWrapper.integrationId && + stakingId.address == balanceWrapper.addresses.address } } yieldsBalancesStore.storeError(userWalletId = userWalletId, stakingIds = values.toSet()) } }, - onError = { - Timber.e(it, "Unable to fetch yield balances $userWalletId") + onError = { throwable -> + Timber.e(throwable, "Unable to fetch yield balances $userWalletId") yieldsBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds) - throw it + throw throwable }, ) } @@ -187,9 +187,9 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor( yieldBalances: Set, ): Boolean { return requests.all { request -> - yieldBalances.any { - request.integrationId == it.integrationId && - request.addresses.address == it.addresses.address + yieldBalances.any { balance -> + request.integrationId == balance.integrationId && + request.addresses.address == balance.addresses.address } } } diff --git a/data/staking/src/main/java/com/tangem/data/staking/utils/YieldBalanceRequestBodyFactory.kt b/data/staking/src/main/java/com/tangem/data/staking/utils/YieldBalanceRequestBodyFactory.kt index 8da94e5450..4db8d77f02 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/utils/YieldBalanceRequestBodyFactory.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/utils/YieldBalanceRequestBodyFactory.kt @@ -14,7 +14,7 @@ internal object YieldBalanceRequestBodyFactory { return YieldBalanceRequestBody( addresses = YieldBalanceRequestBodyAddressFactory.create(stakingID), args = YieldBalanceRequestBody.YieldBalanceRequestArgs( - validatorAddresses = listOf(), // todo add validators [REDACTED_JIRA] + validatorAddresses = emptyList(), // todo add validators [REDACTED_JIRA] ), integrationId = stakingID.integrationId, ) diff --git a/detekt_baseline_report.txt b/detekt_baseline_report.txt index 8bd49ad41c..fac2141bcf 100644 --- a/detekt_baseline_report.txt +++ b/detekt_baseline_report.txt @@ -38,7 +38,7 @@ features/staking/impl 56 data/wallet-connect 55 features/swap-v2/impl 53 features/walletconnect/impl 51 -features/tokendetails/impl 50 +features/tokendetails/impl 49 features/manage-tokens/impl 45 domain/wallets 39 features/nft/impl 36 @@ -65,7 +65,6 @@ features/details/impl 10 data/markets 10 domain/staking 9 data/yield-supply 9 -data/staking 9 data/networks 9 features/welcome/impl 8 features/home/impl 8 diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/EstimateGasUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/EstimateGasUseCase.kt index 21e2e6ee0e..6c9ab7efc3 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/EstimateGasUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/EstimateGasUseCase.kt @@ -6,14 +6,14 @@ import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.transaction.ActionParams import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.staking.repositories.StakeKitRepository import com.tangem.domain.models.wallet.UserWalletId /** * Use case for staking gas estimation. */ class EstimateGasUseCase( - private val stakingRepository: StakingRepository, + private val stakeKitRepository: StakeKitRepository, private val stakingErrorResolver: StakingErrorResolver, ) { @@ -23,7 +23,7 @@ class EstimateGasUseCase( params: ActionParams, ): Either { return Either.catch { - stakingRepository.estimateGas(userWalletId, network, params) + stakeKitRepository.estimateGas(userWalletId, network, params) }.mapLeft { stakingErrorResolver.resolve(it) } diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/FetchActionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/FetchActionsUseCase.kt index f44adef4ae..bf2136e66f 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/FetchActionsUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/FetchActionsUseCase.kt @@ -5,17 +5,17 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.staking.NetworkType import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus -import com.tangem.domain.staking.repositories.StakingActionRepository +import com.tangem.domain.staking.repositories.StakeKitActionRepository import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.staking.repositories.StakeKitRepository import com.tangem.domain.models.wallet.UserWalletId /** * Use case for getting pending actions list. */ class FetchActionsUseCase( - private val stakingRepository: StakingRepository, - private val stakingActionRepository: StakingActionRepository, + private val stakeKitRepository: StakeKitRepository, + private val stakeKitActionRepository: StakeKitActionRepository, private val stakingErrorResolver: StakingErrorResolver, ) { @@ -27,14 +27,14 @@ class FetchActionsUseCase( ): Either { return Either .catch { - val actions = stakingRepository.getActions( + val actions = stakeKitRepository.getActions( userWalletId = userWalletId, cryptoCurrency = cryptoCurrency, networkType = networkType, stakingActionStatus = stakingActionStatus, ) - stakingActionRepository.store(userWalletId, cryptoCurrency.id, actions) + stakeKitActionRepository.store(userWalletId, cryptoCurrency.id, actions) } .mapLeft { stakingErrorResolver.resolve(it) } } 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 135f589770..3c5a7b4fe1 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 @@ -6,7 +6,7 @@ import arrow.core.raise.either import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.repositories.P2PEthPoolRepository import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.staking.repositories.StakeKitRepository import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @@ -15,7 +15,7 @@ import kotlinx.coroutines.launch * Fetches both StakeKit yields and P2P vaults */ class FetchStakingOptionsUseCase( - private val stakingRepository: StakingRepository, + private val stakeKitRepository: StakeKitRepository, private val p2pRepository: P2PEthPoolRepository, private val stakingErrorResolver: StakingErrorResolver, ) { @@ -24,7 +24,7 @@ class FetchStakingOptionsUseCase( catch( block = { coroutineScope { - launch { stakingRepository.fetchYields() } + launch { stakeKitRepository.fetchYields() } launch { p2pRepository.fetchVaults() } } }, diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingTokensUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingTokensUseCase.kt index 5aef2d9cf1..86fcd054f0 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingTokensUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingTokensUseCase.kt @@ -5,19 +5,19 @@ import arrow.core.raise.catch import arrow.core.raise.either import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.staking.repositories.StakeKitRepository /** * Use case for getting enabled tokens */ class FetchStakingTokensUseCase( - private val stakingRepository: StakingRepository, + private val stakeKitRepository: StakeKitRepository, private val stakingErrorResolver: StakingErrorResolver, ) { suspend operator fun invoke(): Either { return either { catch( - block = { stakingRepository.fetchYields() }, + block = { stakeKitRepository.fetchYields() }, catch = { stakingErrorResolver.resolve(it) }, ) } diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetActionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetActionsUseCase.kt index 69d66c85b3..29c649c1cf 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/GetActionsUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/GetActionsUseCase.kt @@ -7,7 +7,7 @@ import com.tangem.domain.core.utils.EitherFlow import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.action.StakingAction -import com.tangem.domain.staking.repositories.StakingActionRepository +import com.tangem.domain.staking.repositories.StakeKitActionRepository import com.tangem.domain.staking.repositories.StakingErrorResolver import com.tangem.domain.models.wallet.UserWalletId import kotlinx.coroutines.flow.catch @@ -17,7 +17,7 @@ import kotlinx.coroutines.flow.map * Use case for getting pending actions list. */ class GetActionsUseCase( - private val stakingActionRepository: StakingActionRepository, + private val stakeKitActionRepository: StakeKitActionRepository, private val stakingErrorResolver: StakingErrorResolver, ) { @@ -25,7 +25,7 @@ class GetActionsUseCase( userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, ): EitherFlow> { - return stakingActionRepository.get( + return stakeKitActionRepository.get( userWalletId = userWalletId, cryptoCurrencyId = cryptoCurrencyId, ).map, Either>> { it.right() } diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetConstructedStakingTransactionUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetConstructedStakingTransactionUseCase.kt index 38ae3bf571..3402b6bb03 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/GetConstructedStakingTransactionUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/GetConstructedStakingTransactionUseCase.kt @@ -7,10 +7,10 @@ import com.tangem.blockchain.common.transaction.Fee import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.staking.repositories.StakeKitRepository class GetConstructedStakingTransactionUseCase( - private val stakingRepository: StakingRepository, + private val stakeKitRepository: StakeKitRepository, private val stakingErrorResolver: StakingErrorResolver, ) { @@ -20,7 +20,7 @@ class GetConstructedStakingTransactionUseCase( amount: Amount, transactionId: String, ): Either> = Either.catch { - stakingRepository.constructTransaction(networkId, fee, amount, transactionId) + stakeKitRepository.constructTransaction(networkId, fee, amount, transactionId) }.mapLeft { stakingErrorResolver.resolve(it) } diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingAvailabilityUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingAvailabilityUseCase.kt index ecfd9fb4fb..6f7397f5a3 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingAvailabilityUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingAvailabilityUseCase.kt @@ -8,8 +8,8 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.staking.repositories.StakingRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.map 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 b8e2633bd7..ef77b03abb 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 @@ -6,13 +6,13 @@ import com.tangem.domain.staking.model.StakingEntryInfo import com.tangem.domain.staking.model.StakingOption import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.staking.repositories.StakeKitRepository /** * Use case for getting entry info about staking on token screen. */ class GetStakingEntryInfoUseCase( - private val stakingRepository: StakingRepository, + private val stakeKitRepository: StakeKitRepository, private val stakingErrorResolver: StakingErrorResolver, ) { @@ -25,7 +25,7 @@ class GetStakingEntryInfoUseCase( .catch { when (stakingOption) { is StakingOption.StakeKit -> { - stakingRepository.getEntryInfo( + stakeKitRepository.getEntryInfo( cryptoCurrencyId = cryptoCurrencyId, symbol = symbol, ) diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingTransactionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingTransactionsUseCase.kt index 63f92c60e5..df26a82e64 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingTransactionsUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingTransactionsUseCase.kt @@ -6,7 +6,7 @@ import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.transaction.ActionParams import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.staking.repositories.StakeKitRepository import com.tangem.domain.models.wallet.UserWalletId import kotlinx.coroutines.delay @@ -14,7 +14,7 @@ import kotlinx.coroutines.delay * Use case for creating enter action */ class GetStakingTransactionsUseCase( - private val stakingRepository: StakingRepository, + private val stakeKitRepository: StakeKitRepository, private val stakingErrorResolver: StakingErrorResolver, ) { @@ -24,7 +24,7 @@ class GetStakingTransactionsUseCase( params: ActionParams, ): Either> { return Either.catch { - val createAction = stakingRepository.createAction(userWalletId, network, params) + val createAction = stakeKitRepository.createAction(userWalletId, network, params) // workaround, sometimes transaction is not created immediately after actions/enter delay(PATCH_TRANSACTION_REQUEST_DELAY) diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetYieldUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetYieldUseCase.kt index b000f7126c..4dd2f1d833 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/GetYieldUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/GetYieldUseCase.kt @@ -5,25 +5,25 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.staking.repositories.StakeKitRepository /** * Use case for getting staking yield for staking scenario start. */ class GetYieldUseCase( - private val stakingRepository: StakingRepository, + private val stakeKitRepository: StakeKitRepository, private val stakingErrorResolver: StakingErrorResolver, ) { suspend operator fun invoke(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Either { return Either - .catch { stakingRepository.getYield(cryptoCurrencyId, symbol) } + .catch { stakeKitRepository.getYield(cryptoCurrencyId, symbol) } .mapLeft { stakingErrorResolver.resolve(it) } } suspend operator fun invoke(yieldId: String): Either { return Either - .catch { stakingRepository.getYield(yieldId) } + .catch { stakeKitRepository.getYield(yieldId) } .mapLeft { stakingErrorResolver.resolve(it) } } } \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/IsAnyTokenStakedUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/IsAnyTokenStakedUseCase.kt index 0346ef7f06..5beff03f92 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/IsAnyTokenStakedUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/IsAnyTokenStakedUseCase.kt @@ -3,8 +3,8 @@ package com.tangem.domain.staking import arrow.core.Either import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.staking.repositories.StakingRepository class IsAnyTokenStakedUseCase( private val stakingRepository: StakingRepository, diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/SaveUnsubmittedHashUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/SaveUnsubmittedHashUseCase.kt index abfeab0d56..629b3e8891 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/SaveUnsubmittedHashUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/SaveUnsubmittedHashUseCase.kt @@ -4,19 +4,19 @@ import arrow.core.Either import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.UnsubmittedTransactionMetadata import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingTransactionHashRepository +import com.tangem.domain.staking.repositories.StakeKitTransactionHashRepository /** * Use case for saving hash that failed to submit during staking confirmation */ class SaveUnsubmittedHashUseCase( - private val stakingTransactionHashRepository: StakingTransactionHashRepository, + private val stakeKitTransactionHashRepository: StakeKitTransactionHashRepository, private val stakingErrorResolver: StakingErrorResolver, ) { suspend operator fun invoke(transactionId: String, transactionHash: String): Either { return Either.catch { - stakingTransactionHashRepository.storeUnsubmittedHash( + stakeKitTransactionHashRepository.storeUnsubmittedHash( unsubmittedTransactionMetadata = UnsubmittedTransactionMetadata( transactionId = transactionId, transactionHash = transactionHash, diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/SendUnsubmittedHashesUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/SendUnsubmittedHashesUseCase.kt index 3564651d9f..dc4b0a8832 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/SendUnsubmittedHashesUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/SendUnsubmittedHashesUseCase.kt @@ -3,19 +3,19 @@ package com.tangem.domain.staking import arrow.core.Either import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingTransactionHashRepository +import com.tangem.domain.staking.repositories.StakeKitTransactionHashRepository /** * Use case for commiting hashes that failed to submit during staking confirmation */ class SendUnsubmittedHashesUseCase( - private val stakingTransactionHashRepository: StakingTransactionHashRepository, + private val stakeKitTransactionHashRepository: StakeKitTransactionHashRepository, private val stakingErrorResolver: StakingErrorResolver, ) { suspend operator fun invoke(): Either { return Either - .catch { stakingTransactionHashRepository.sendUnsubmittedHashes() } + .catch { stakeKitTransactionHashRepository.sendUnsubmittedHashes() } .mapLeft { stakingErrorResolver.resolve(it) } } } \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/SubmitHashUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/SubmitHashUseCase.kt index fba8daa29b..1f9e111df7 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/SubmitHashUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/SubmitHashUseCase.kt @@ -4,20 +4,20 @@ import arrow.core.Either import com.tangem.domain.staking.model.SubmitHashData import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingTransactionHashRepository +import com.tangem.domain.staking.repositories.StakeKitTransactionHashRepository /** * Use case for submitting transaction hash to stakekit */ class SubmitHashUseCase( - private val stakingTransactionHashRepository: StakingTransactionHashRepository, + private val stakeKitTransactionHashRepository: StakeKitTransactionHashRepository, private val stakingErrorResolver: StakingErrorResolver, ) { suspend operator fun invoke(submitHashData: SubmitHashData): Either { return Either .catch { - stakingTransactionHashRepository.submitHash( + stakeKitTransactionHashRepository.submitHash( transactionId = submitHashData.transactionId, transactionHash = submitHashData.transactionHash, ) 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 4c26844a7c..fabb8176d1 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 @@ -1,8 +1,10 @@ package com.tangem.domain.staking.repositories import arrow.core.Either +import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.ethpool.* import com.tangem.domain.staking.model.stakekit.StakingError +import kotlinx.coroutines.flow.Flow interface P2PEthPoolRepository { @@ -113,4 +115,20 @@ interface P2PEthPoolRepository { vaultAddress: String, period: Int? = null, ): Either> + + /** + * Check P2P staking availability by finding public vault + * + * @return Flow of StakingAvailability - Available with StakingOption.P2P if public vault found, + * TemporaryUnavailable if not found or vaults empty + */ + fun getStakingAvailability(): Flow + + /** + * Check P2P staking availability synchronously + * + * @return StakingAvailability - Available with StakingOption.P2P if public vault found, + * TemporaryUnavailable if not found or vaults empty + */ + suspend fun getStakingAvailabilitySync(): StakingAvailability } \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingActionRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakeKitActionRepository.kt similarity index 92% rename from domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingActionRepository.kt rename to domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakeKitActionRepository.kt index 3328d08add..635bbc9d1d 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingActionRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakeKitActionRepository.kt @@ -5,7 +5,7 @@ import com.tangem.domain.staking.model.stakekit.action.StakingAction import com.tangem.domain.models.wallet.UserWalletId import kotlinx.coroutines.flow.Flow -interface StakingActionRepository { +interface StakeKitActionRepository { suspend fun store(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, actions: List) 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 new file mode 100644 index 0000000000..a12b108ca4 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakeKitRepository.kt @@ -0,0 +1,54 @@ +package com.tangem.domain.staking.repositories + +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.TransactionData +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.domain.models.currency.CryptoCurrency +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.models.staking.NetworkType +import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.stakekit.action.StakingAction +import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus +import com.tangem.domain.staking.model.stakekit.transaction.ActionParams +import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate +import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction +import kotlinx.coroutines.flow.Flow + +@Suppress("TooManyFunctions") +interface StakeKitRepository { + + suspend fun fetchYields() + + fun getEnabledYields(): Flow> + + fun getStakingAvailability(rawCurrencyId: CryptoCurrency.RawID, symbol: String): Flow + + suspend fun getStakingAvailabilitySync(rawCurrencyId: CryptoCurrency.RawID, symbol: String): StakingAvailability + + suspend fun getEntryInfo(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): StakingEntryInfo + + suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield + + suspend fun getYield(yieldId: String): Yield + + suspend fun getActions( + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency, + networkType: NetworkType, + stakingActionStatus: StakingActionStatus, + ): List + + suspend fun createAction(userWalletId: UserWalletId, network: Network, params: ActionParams): StakingAction + + suspend fun estimateGas(userWalletId: UserWalletId, network: Network, params: ActionParams): StakingGasEstimate + + suspend fun constructTransaction( + networkId: String, + fee: Fee, + amount: Amount, + transactionId: String, + ): Pair +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingTransactionHashRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakeKitTransactionHashRepository.kt similarity index 88% rename from domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingTransactionHashRepository.kt rename to domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakeKitTransactionHashRepository.kt index be75380a84..97d971aabd 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingTransactionHashRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakeKitTransactionHashRepository.kt @@ -2,7 +2,7 @@ package com.tangem.domain.staking.repositories import com.tangem.domain.staking.model.UnsubmittedTransactionMetadata -interface StakingTransactionHashRepository { +interface StakeKitTransactionHashRepository { suspend fun submitHash(transactionId: String, transactionHash: String) diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt index ab8b969f7b..1cdbcd6ae3 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt @@ -1,36 +1,12 @@ package com.tangem.domain.staking.repositories -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.TransactionData -import com.tangem.blockchain.common.transaction.Fee import com.tangem.domain.models.currency.CryptoCurrency -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.models.staking.NetworkType -import com.tangem.domain.staking.model.stakekit.Yield -import com.tangem.domain.staking.model.stakekit.action.StakingAction -import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus -import com.tangem.domain.staking.model.stakekit.transaction.ActionParams -import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate -import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction import kotlinx.coroutines.flow.Flow -// TODO p2p make 3 repos: stakekit, p2p, common staking -@Suppress("TooManyFunctions") interface StakingRepository { - suspend fun fetchYields() - - fun getEnabledYields(): Flow> - - suspend fun getEntryInfo(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): StakingEntryInfo - - suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield - - suspend fun getYield(yieldId: String): Yield - fun getStakingAvailability(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Flow suspend fun getStakingAvailabilitySync( @@ -38,23 +14,5 @@ interface StakingRepository { cryptoCurrency: CryptoCurrency, ): StakingAvailability - suspend fun getActions( - userWalletId: UserWalletId, - cryptoCurrency: CryptoCurrency, - networkType: NetworkType, - stakingActionStatus: StakingActionStatus, - ): List - - suspend fun createAction(userWalletId: UserWalletId, network: Network, params: ActionParams): StakingAction - - suspend fun estimateGas(userWalletId: UserWalletId, network: Network, params: ActionParams): StakingGasEstimate - - suspend fun constructTransaction( - networkId: String, - fee: Fee, - amount: Amount, - transactionId: String, - ): Pair - suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean } \ No newline at end of file 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 3b50f3a63c..32edb6331c 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 @@ -1,7 +1,7 @@ package com.tangem.domain.staking.usecase import com.tangem.domain.staking.model.stakekit.Yield -import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.staking.repositories.StakeKitRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -12,10 +12,10 @@ import kotlinx.coroutines.flow.map * - key: currency staking key (network.backendId + "_" + symbol) * - value: validators */ -class StakingApyFlowUseCase(private val stakingRepository: StakingRepository) { +class StakingApyFlowUseCase(private val stakeKitRepository: StakeKitRepository) { operator fun invoke(): Flow>> { - return stakingRepository.getEnabledYields() + return stakeKitRepository.getEnabledYields() .map { yields -> yields.associate { yield -> val key = "${yield.token.coinGeckoId}_${yield.token.symbol}" diff --git a/features/tokendetails/impl/detekt-baseline-debug.xml b/features/tokendetails/impl/detekt-baseline-debug.xml index ca58193fa1..2cd9989be6 100644 --- a/features/tokendetails/impl/detekt-baseline-debug.xml +++ b/features/tokendetails/impl/detekt-baseline-debug.xml @@ -23,7 +23,6 @@ MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ internalUiState.value = stateFactory.getStateWithErrorDialog(stringReference(it)) Timber.e(it) } MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ internalUiState.value = stateFactory.getStateWithUpdatedHidden( isBalanceHidden = it.isBalanceHidden, ) } MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ sendButtonsEvents(it.states) internalUiState.value = stateFactory.getManageButtonsState(actions = it.states) } - MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ val stakingEntryInfo = if (it is StakingAvailability.Available) { getStakingEntryInfoUseCase( cryptoCurrencyId = cryptoCurrency.id, symbol = cryptoCurrency.symbol, ).getOrNull() } else { null } internalUiState.update { state -> stateFactory.getStakingInfoState( state = state, stakingEntryInfo = stakingEntryInfo, stakingAvailability = it, cryptoCurrencyStatus = cryptoCurrencyStatus, ) } } MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ val updatedState = stateFactory.getStateWithNotifications(it) notificationsAnalyticsSender.send(internalUiState.value, updatedState.notifications) internalUiState.value = updatedState } MultilineLambdaItParameter:TokenDetailsOnrampTransactionStateConverter.kt$TokenDetailsOnrampTransactionStateConverter${ analyticsEventHandler.send(TokenOnrampAnalyticsEvent.GoToProvider) clickIntents.onGoToProviderClick(it) } MultilineLambdaItParameter:TokenDetailsScreen.kt${ Notification( modifier = itemModifier.animateItem(), config = it.config, iconTint = when (it) { is TokenDetailsNotification.Informational -> TangemTheme.colors.icon.accent is TokenDetailsNotification.UsedOutdatedData -> TangemTheme.colors.text.attention else -> null }, ) }