Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-24 13:48:52 +02:00
parent aa6c1c95f2
commit df32ea7ab6
32 changed files with 704 additions and 637 deletions

View file

@ -2,10 +2,11 @@ package com.tangem.tap.di.domain
import com.tangem.domain.staking.* import com.tangem.domain.staking.*
import com.tangem.domain.staking.repositories.P2PEthPoolRepository 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.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakeKitRepository
import com.tangem.domain.staking.repositories.StakingRepository 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.single.SingleYieldBalanceFetcher
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
@ -35,11 +36,11 @@ internal object StakingDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideGetStakingEntryInfoUseCase( fun provideGetStakingEntryInfoUseCase(
stakingRepository: StakingRepository, stakeKitRepository: StakeKitRepository,
stakingErrorResolver: StakingErrorResolver, stakingErrorResolver: StakingErrorResolver,
): GetStakingEntryInfoUseCase { ): GetStakingEntryInfoUseCase {
return GetStakingEntryInfoUseCase( return GetStakingEntryInfoUseCase(
stakingRepository = stakingRepository, stakeKitRepository = stakeKitRepository,
stakingErrorResolver = stakingErrorResolver, stakingErrorResolver = stakingErrorResolver,
) )
} }
@ -47,11 +48,11 @@ internal object StakingDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideGetYieldUseCase( fun provideGetYieldUseCase(
stakingRepository: StakingRepository, stakeKitRepository: StakeKitRepository,
stakingErrorResolver: StakingErrorResolver, stakingErrorResolver: StakingErrorResolver,
): GetYieldUseCase { ): GetYieldUseCase {
return GetYieldUseCase( return GetYieldUseCase(
stakingRepository = stakingRepository, stakeKitRepository = stakeKitRepository,
stakingErrorResolver = stakingErrorResolver, stakingErrorResolver = stakingErrorResolver,
) )
} }
@ -59,13 +60,13 @@ internal object StakingDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideFetchActionsUseCase( fun provideFetchActionsUseCase(
stakingRepository: StakingRepository, stakeKitRepository: StakeKitRepository,
stakingActionRepository: StakingActionRepository, stakeKitActionRepository: StakeKitActionRepository,
stakingErrorResolver: StakingErrorResolver, stakingErrorResolver: StakingErrorResolver,
): FetchActionsUseCase { ): FetchActionsUseCase {
return FetchActionsUseCase( return FetchActionsUseCase(
stakingRepository = stakingRepository, stakeKitRepository = stakeKitRepository,
stakingActionRepository = stakingActionRepository, stakeKitActionRepository = stakeKitActionRepository,
stakingErrorResolver = stakingErrorResolver, stakingErrorResolver = stakingErrorResolver,
) )
} }
@ -73,11 +74,11 @@ internal object StakingDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideGetActionsUseCase( fun provideGetActionsUseCase(
stakingActionRepository: StakingActionRepository, stakeKitActionRepository: StakeKitActionRepository,
stakingErrorResolver: StakingErrorResolver, stakingErrorResolver: StakingErrorResolver,
): GetActionsUseCase { ): GetActionsUseCase {
return GetActionsUseCase( return GetActionsUseCase(
stakingActionRepository = stakingActionRepository, stakeKitActionRepository = stakeKitActionRepository,
stakingErrorResolver = stakingErrorResolver, stakingErrorResolver = stakingErrorResolver,
) )
} }
@ -85,11 +86,11 @@ internal object StakingDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideGetStakingTokensUseCase( fun provideGetStakingTokensUseCase(
stakingRepository: StakingRepository, stakeKitRepository: StakeKitRepository,
stakingErrorResolver: StakingErrorResolver, stakingErrorResolver: StakingErrorResolver,
): FetchStakingTokensUseCase { ): FetchStakingTokensUseCase {
return FetchStakingTokensUseCase( return FetchStakingTokensUseCase(
stakingRepository = stakingRepository, stakeKitRepository = stakeKitRepository,
stakingErrorResolver = stakingErrorResolver, stakingErrorResolver = stakingErrorResolver,
) )
} }
@ -97,12 +98,12 @@ internal object StakingDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideFetchStakingOptionsUseCase( fun provideFetchStakingOptionsUseCase(
stakingRepository: StakingRepository, stakeKitRepository: StakeKitRepository,
p2pRepository: P2PEthPoolRepository, p2pRepository: P2PEthPoolRepository,
stakingErrorResolver: StakingErrorResolver, stakingErrorResolver: StakingErrorResolver,
): FetchStakingOptionsUseCase { ): FetchStakingOptionsUseCase {
return FetchStakingOptionsUseCase( return FetchStakingOptionsUseCase(
stakingRepository = stakingRepository, stakeKitRepository = stakeKitRepository,
p2pRepository = p2pRepository, p2pRepository = p2pRepository,
stakingErrorResolver = stakingErrorResolver, stakingErrorResolver = stakingErrorResolver,
) )
@ -123,11 +124,11 @@ internal object StakingDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideGetStakingTransactionsUseCase( fun provideGetStakingTransactionsUseCase(
stakingRepository: StakingRepository, stakeKitRepository: StakeKitRepository,
stakingErrorResolver: StakingErrorResolver, stakingErrorResolver: StakingErrorResolver,
): GetStakingTransactionsUseCase { ): GetStakingTransactionsUseCase {
return GetStakingTransactionsUseCase( return GetStakingTransactionsUseCase(
stakingRepository = stakingRepository, stakeKitRepository = stakeKitRepository,
stakingErrorResolver = stakingErrorResolver, stakingErrorResolver = stakingErrorResolver,
) )
} }
@ -135,11 +136,11 @@ internal object StakingDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideGasEstimateUseCase( fun provideGasEstimateUseCase(
stakingRepository: StakingRepository, stakeKitRepository: StakeKitRepository,
stakingErrorResolver: StakingErrorResolver, stakingErrorResolver: StakingErrorResolver,
): EstimateGasUseCase { ): EstimateGasUseCase {
return EstimateGasUseCase( return EstimateGasUseCase(
stakingRepository = stakingRepository, stakeKitRepository = stakeKitRepository,
stakingErrorResolver = stakingErrorResolver, stakingErrorResolver = stakingErrorResolver,
) )
} }
@ -147,11 +148,11 @@ internal object StakingDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideSubmitHashUseCase( fun provideSubmitHashUseCase(
stakingTransactionHashRepository: StakingTransactionHashRepository, stakeKitTransactionHashRepository: StakeKitTransactionHashRepository,
stakingErrorResolver: StakingErrorResolver, stakingErrorResolver: StakingErrorResolver,
): SubmitHashUseCase { ): SubmitHashUseCase {
return SubmitHashUseCase( return SubmitHashUseCase(
stakingTransactionHashRepository = stakingTransactionHashRepository, stakeKitTransactionHashRepository = stakeKitTransactionHashRepository,
stakingErrorResolver = stakingErrorResolver, stakingErrorResolver = stakingErrorResolver,
) )
} }
@ -159,11 +160,11 @@ internal object StakingDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideSaveUnsubmittedHashUseCase( fun provideSaveUnsubmittedHashUseCase(
stakingTransactionHashRepository: StakingTransactionHashRepository, stakeKitTransactionHashRepository: StakeKitTransactionHashRepository,
stakingErrorResolver: StakingErrorResolver, stakingErrorResolver: StakingErrorResolver,
): SaveUnsubmittedHashUseCase { ): SaveUnsubmittedHashUseCase {
return SaveUnsubmittedHashUseCase( return SaveUnsubmittedHashUseCase(
stakingTransactionHashRepository = stakingTransactionHashRepository, stakeKitTransactionHashRepository = stakeKitTransactionHashRepository,
stakingErrorResolver = stakingErrorResolver, stakingErrorResolver = stakingErrorResolver,
) )
} }
@ -181,11 +182,11 @@ internal object StakingDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideSendUnsubmittedHashesUseCase( fun provideSendUnsubmittedHashesUseCase(
stakingTransactionHashRepository: StakingTransactionHashRepository, stakeKitTransactionHashRepository: StakeKitTransactionHashRepository,
stakingErrorResolver: StakingErrorResolver, stakingErrorResolver: StakingErrorResolver,
): SendUnsubmittedHashesUseCase { ): SendUnsubmittedHashesUseCase {
return SendUnsubmittedHashesUseCase( return SendUnsubmittedHashesUseCase(
stakingTransactionHashRepository = stakingTransactionHashRepository, stakeKitTransactionHashRepository = stakeKitTransactionHashRepository,
stakingErrorResolver = stakingErrorResolver, stakingErrorResolver = stakingErrorResolver,
) )
} }
@ -193,11 +194,11 @@ internal object StakingDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideGetConstructedStakingTransactionUseCase( fun provideGetConstructedStakingTransactionUseCase(
stakingRepository: StakingRepository, stakeKitRepository: StakeKitRepository,
stakingErrorResolver: StakingErrorResolver, stakingErrorResolver: StakingErrorResolver,
): GetConstructedStakingTransactionUseCase { ): GetConstructedStakingTransactionUseCase {
return GetConstructedStakingTransactionUseCase( return GetConstructedStakingTransactionUseCase(
stakingRepository = stakingRepository, stakeKitRepository = stakeKitRepository,
stakingErrorResolver = stakingErrorResolver, stakingErrorResolver = stakingErrorResolver,
) )
} }
@ -236,7 +237,7 @@ internal object StakingDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideStakingApyFlowUseCase(stakingRepository: StakingRepository): StakingApyFlowUseCase { fun provideStakingApyFlowUseCase(stakeKitRepository: StakeKitRepository): StakingApyFlowUseCase {
return StakingApyFlowUseCase(stakingRepository) return StakingApyFlowUseCase(stakeKitRepository)
} }
} }

View file

@ -1,15 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>MultilineLambdaItParameter:DefaultMultiYieldBalanceFetcher.kt$DefaultMultiYieldBalanceFetcher${ Timber.e(it, "Unable to fetch yield balances $params") yieldsBalancesStore.storeError(userWalletId = params.userWalletId, stakingIds = stakingIds) }</ID>
<ID>MultilineLambdaItParameter:DefaultMultiYieldBalanceFetcher.kt$DefaultMultiYieldBalanceFetcher${ Timber.e(it, "Unable to fetch yield balances $userWalletId") yieldsBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds) throw it }</ID>
<ID>MultilineLambdaItParameter:DefaultMultiYieldBalanceFetcher.kt$DefaultMultiYieldBalanceFetcher${ request.integrationId == it.integrationId &amp;&amp; request.addresses.address == it.addresses.address }</ID>
<ID>MultilineLambdaItParameter:DefaultMultiYieldBalanceFetcher.kt$DefaultMultiYieldBalanceFetcher${ stakingId.integrationId == it.integrationId &amp;&amp; stakingId.address == it.addresses.address }</ID>
<ID>MultilineLambdaItParameter:DefaultStakingRepository.kt$DefaultStakingRepository${ YieldConverter.convertListIgnoreErrors( input = it, onError = { Timber.e("Error converting one of the items in enabled yields: $it") }, ) }</ID>
<ID>NamedArguments:DefaultP2PEthPoolRepository.kt$DefaultP2PEthPoolRepository$getRewards(network.value, delegatorAddress, vaultAddress, period)</ID>
<ID>NoNameShadowing:DefaultStakingRepository.kt$DefaultStakingRepository${ Timber.e("Error converting one of the items in enabled yields: $it") }</ID>
<ID>NullableToStringCall:DefaultStakingRepository.kt$DefaultStakingRepository$${(response as? ApiResponse.Error)?.cause}</ID>
<ID>UseEmptyCounterpart:YieldBalanceRequestBodyFactory.kt$YieldBalanceRequestBodyFactory$listOf()</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -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.P2PEthPoolUnstakeRequest
import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolWithdrawRequest import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolWithdrawRequest
import com.tangem.datasource.local.token.P2PEthPoolVaultsStore 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.model.ethpool.*
import com.tangem.domain.staking.repositories.P2PEthPoolRepository import com.tangem.domain.staking.repositories.P2PEthPoolRepository
import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.utils.coroutines.CoroutineDispatcherProvider 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 kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
@ -192,7 +197,12 @@ internal class DefaultP2PEthPoolRepository(
period: Int?, period: Int?,
): Either<StakingError, List<P2PEthPoolReward>> = either { ): Either<StakingError, List<P2PEthPoolReward>> = either {
withContext(dispatchers.io) { 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) { when (response) {
is ApiResponse.Success -> { is ApiResponse.Success -> {
val data = response.data val data = response.data
@ -206,4 +216,49 @@ internal class DefaultP2PEthPoolRepository(
} }
} }
} }
override fun getStakingAvailability(): Flow<StakingAvailability> {
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<P2PEthPoolVault> {
return p2pEthPoolVaultsStore.getSync()
}
private fun getVaultsFlow(): Flow<List<P2PEthPoolVault>> {
return p2pEthPoolVaultsStore.get()
}
private fun findPublicVault(vaults: List<P2PEthPoolVault>): P2PEthPoolVault? {
return vaults.firstOrNull { vault -> !vault.isPrivate }
}
} }

View file

@ -4,15 +4,15 @@ import com.tangem.datasource.local.token.StakingActionsStore
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.staking.model.stakekit.action.StakingAction 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 com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
internal class DefaultStakingActionRepository( internal class DefaultStakeKitActionRepository(
private val stakingActionsStore: StakingActionsStore, private val stakingActionsStore: StakingActionsStore,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) : StakingActionRepository { ) : StakeKitActionRepository {
override suspend fun store( override suspend fun store(
userWalletId: UserWalletId, userWalletId: UserWalletId,

View file

@ -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<StakingAction> {
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<EnabledYieldsResponse> {
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<StakingIntegrationID.StakeKit> {
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<StakingTransaction, TransactionData.Compiled> {
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<Yield>, 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<Yield> {
return YieldConverter.convertListIgnoreErrors(
input = stakingYieldsStore.getSync(),
onError = { Timber.e("Error converting one of the items in enabled yields: $it") },
)
}
override fun getEnabledYields(): Flow<List<Yield>> {
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<StakingAvailability> {
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
}
}
}

View file

@ -6,16 +6,16 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectListSync import com.tangem.datasource.local.preferences.utils.getObjectListSync
import com.tangem.domain.staking.model.UnsubmittedTransactionMetadata 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 com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
internal class DefaultStakingTransactionHashRepository( internal class DefaultStakeKitTransactionHashRepository(
private val stakeKitApi: StakeKitApi, private val stakeKitApi: StakeKitApi,
private val appPreferencesStore: AppPreferencesStore, private val appPreferencesStore: AppPreferencesStore,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) : StakingTransactionHashRepository { ) : StakeKitTransactionHashRepository {
override suspend fun submitHash(transactionId: String, transactionHash: String) { override suspend fun submitHash(transactionId: String, transactionHash: String) {
withContext(dispatchers.io) { withContext(dispatchers.io) {

View file

@ -1,204 +1,38 @@
package com.tangem.data.staking package com.tangem.data.staking
import android.util.Base64
import arrow.core.getOrElse 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.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.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.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.card.common.TapWorkarounds.isWallet2
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network 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.YieldBalance
import com.tangem.domain.models.staking.action.StakingActionType
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.staking.model.StakingAvailability 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.StakingIntegrationID
import com.tangem.domain.staking.model.StakingOption import com.tangem.domain.staking.repositories.P2PEthPoolRepository
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault import com.tangem.domain.staking.repositories.StakeKitRepository
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.StakingRepository import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.staking.toggles.StakingFeatureToggles import com.tangem.domain.staking.toggles.StakingFeatureToggles
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.lib.crypto.BlockchainUtils.isCardano import com.tangem.lib.crypto.BlockchainUtils.isCardano
import com.tangem.lib.crypto.BlockchainUtils.isSolana import com.tangem.lib.crypto.BlockchainUtils.isSolana
import com.tangem.utils.coroutines.CoroutineDispatcherProvider 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.flow.*
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions") @Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
internal class DefaultStakingRepository( internal class DefaultStakingRepository(
private val stakeKitApi: StakeKitApi, private val stakeKitRepository: StakeKitRepository,
private val stakingYieldsStore: StakingYieldsStore, private val p2pEthPoolRepository: P2PEthPoolRepository,
private val p2pEthPoolVaultsStore: P2PEthPoolVaultsStore,
private val stakingBalanceStoreV2: YieldsBalancesStore, private val stakingBalanceStoreV2: YieldsBalancesStore,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
private val walletManagersFacade: WalletManagersFacade,
private val getUserWalletUseCase: GetUserWalletUseCase, private val getUserWalletUseCase: GetUserWalletUseCase,
private val stakingFeatureToggles: StakingFeatureToggles, private val stakingFeatureToggles: StakingFeatureToggles,
moshi: Moshi,
) : StakingRepository { ) : 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<StakingAction> {
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<EnabledYieldsResponse> {
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<StakingIntegrationID.StakeKit> {
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( override fun getStakingAvailability(
userWalletId: UserWalletId, userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency, cryptoCurrency: CryptoCurrency,
@ -222,14 +56,16 @@ internal class DefaultStakingRepository(
val stakingIntegration = StakingIntegrationID.create(currencyId = cryptoCurrency.id) val stakingIntegration = StakingIntegrationID.create(currencyId = cryptoCurrency.id)
when (stakingIntegration) { val availabilityFlow = when (stakingIntegration) {
is StakingIntegrationID.P2P -> subscribeToP2PStakingAvailability() is StakingIntegrationID.P2P -> p2pEthPoolRepository.getStakingAvailability()
is StakingIntegrationID.StakeKit -> subscribeToStakeKitStakingAvailability( is StakingIntegrationID.StakeKit -> stakeKitRepository.getStakingAvailability(
rawCurrencyId, 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 StakingAvailability.Unavailable
return when (stakingIntegration) { return when (stakingIntegration) {
is StakingIntegrationID.P2P -> { is StakingIntegrationID.P2P -> p2pEthPoolRepository.getStakingAvailabilitySync()
val vaults = getP2PEthPoolVaultsSync() is StakingIntegrationID.StakeKit -> stakeKitRepository.getStakingAvailabilitySync(
if (vaults.isEmpty()) { rawCurrencyId,
return StakingAvailability.TemporaryUnavailable cryptoCurrency.symbol,
}
val vault = findP2PEthPoolVault(
vaults = vaults,
) )
if (vault != null) {
StakingAvailability.Available(StakingOption.P2P(vault))
} else {
StakingAvailability.TemporaryUnavailable
} }
} }
is StakingIntegrationID.StakeKit -> {
val yields = getEnabledYieldsSync()
if (yields.isEmpty()) {
return StakingAvailability.TemporaryUnavailable
}
val prefetchedYield = findPrefetchedYield( override suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean {
yields = yields, return withContext(dispatchers.default) {
currencyId = rawCurrencyId, val balances = stakingBalanceStoreV2.getAllSyncOrNull(userWalletId) ?: return@withContext false
symbol = cryptoCurrency.symbol,
)
when { val hasDataYieldBalance by lazy {
prefetchedYield != null -> StakingAvailability.Available(StakingOption.StakeKit(prefetchedYield)) balances.any { yieldBalance ->
else -> StakingAvailability.TemporaryUnavailable (yieldBalance as? YieldBalance.Data)?.balance?.items?.isNotEmpty() == true
} }
} }
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<StakingTransaction, TransactionData.Compiled> {
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<Yield>, 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<Yield> {
return YieldConverter.convertListIgnoreErrors(
input = stakingYieldsStore.getSync(),
onError = { Timber.e("Error converting one of the items in enabled yields: $it") },
)
}
override fun getEnabledYields(): Flow<List<Yield>> {
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<P2PEthPoolVault> {
return p2pEthPoolVaultsStore.getSync()
}
private fun getP2PEthPoolVaults(): Flow<List<P2PEthPoolVault>> {
return p2pEthPoolVaultsStore.get()
}
private fun findP2PEthPoolVault(vaults: List<P2PEthPoolVault>): P2PEthPoolVault? {
return vaults.firstOrNull { vault -> !vault.isPrivate }
}
private fun ProducerScope<StakingAvailability>.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<StakingAvailability>.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 { companion object {
private val INVALID_BATCHES_FOR_SOLANA = listOf("AC01", "CB79") private val INVALID_BATCHES_FOR_SOLANA = listOf("AC01", "CB79")
} }

View file

@ -4,10 +4,11 @@ import com.squareup.moshi.Moshi
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.data.staking.DefaultP2PEthPoolRepository 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.DefaultStakingErrorResolver
import com.tangem.data.staking.DefaultStakeKitRepository
import com.tangem.data.staking.DefaultStakingRepository 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.converters.error.StakeKitErrorConverter
import com.tangem.data.staking.store.YieldsBalancesStore import com.tangem.data.staking.store.YieldsBalancesStore
import com.tangem.data.staking.toggles.DefaultStakingFeatureToggles 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.StakingActionsStore
import com.tangem.datasource.local.token.StakingYieldsStore import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.domain.staking.repositories.P2PEthPoolRepository 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.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakeKitRepository
import com.tangem.domain.staking.repositories.StakingRepository 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.toggles.StakingFeatureToggles
import com.tangem.domain.staking.utils.StakingCleaner import com.tangem.domain.staking.utils.StakingCleaner
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
@ -42,30 +44,44 @@ internal object StakingDataModule {
@Provides @Provides
@Singleton @Singleton
fun provideStakingRepository( fun provideStakeKitRepository(
stakeKitApi: StakeKitApi, stakeKitApi: StakeKitApi,
stakingYieldsStore: StakingYieldsStore, stakingYieldsStore: StakingYieldsStore,
p2pEthPoolVaultsStore: P2PEthPoolVaultsStore,
yieldsBalancesStore: YieldsBalancesStore,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
walletManagersFacade: WalletManagersFacade, walletManagersFacade: WalletManagersFacade,
getUserWalletUseCase: GetUserWalletUseCase,
stakingFeatureToggles: StakingFeatureToggles, stakingFeatureToggles: StakingFeatureToggles,
@NetworkMoshi moshi: Moshi, @NetworkMoshi moshi: Moshi,
): StakingRepository { ): StakeKitRepository {
return DefaultStakingRepository( return DefaultStakeKitRepository(
stakeKitApi = stakeKitApi, stakeKitApi = stakeKitApi,
stakingYieldsStore = stakingYieldsStore, stakingYieldsStore = stakingYieldsStore,
p2pEthPoolVaultsStore = p2pEthPoolVaultsStore,
stakingBalanceStoreV2 = yieldsBalancesStore,
dispatchers = dispatchers, dispatchers = dispatchers,
walletManagersFacade = walletManagersFacade, walletManagersFacade = walletManagersFacade,
getUserWalletUseCase = getUserWalletUseCase,
stakingFeatureToggles = stakingFeatureToggles, stakingFeatureToggles = stakingFeatureToggles,
moshi = moshi, 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 @Provides
@Singleton @Singleton
fun provideP2PEthPoolRepository( fun provideP2PEthPoolRepository(
@ -86,8 +102,8 @@ internal object StakingDataModule {
stakeKitApi: StakeKitApi, stakeKitApi: StakeKitApi,
appPreferencesStore: AppPreferencesStore, appPreferencesStore: AppPreferencesStore,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): StakingTransactionHashRepository { ): StakeKitTransactionHashRepository {
return DefaultStakingTransactionHashRepository( return DefaultStakeKitTransactionHashRepository(
stakeKitApi = stakeKitApi, stakeKitApi = stakeKitApi,
appPreferencesStore = appPreferencesStore, appPreferencesStore = appPreferencesStore,
dispatchers = dispatchers, dispatchers = dispatchers,
@ -99,8 +115,8 @@ internal object StakingDataModule {
fun provideStakingActionRepository( fun provideStakingActionRepository(
stakingActionsStore: StakingActionsStore, stakingActionsStore: StakingActionsStore,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): StakingActionRepository { ): StakeKitActionRepository {
return DefaultStakingActionRepository( return DefaultStakeKitActionRepository(
stakingActionsStore = stakingActionsStore, stakingActionsStore = stakingActionsStore,
dispatchers = dispatchers, dispatchers = dispatchers,
) )

View file

@ -69,8 +69,8 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor(
fetch(userWalletId = params.userWalletId, stakingIds = availableStakingIds) fetch(userWalletId = params.userWalletId, stakingIds = availableStakingIds)
} }
.onLeft { .onLeft { throwable ->
Timber.e(it, "Unable to fetch yield balances $params") Timber.e(throwable, "Unable to fetch yield balances $params")
yieldsBalancesStore.storeError(userWalletId = params.userWalletId, stakingIds = stakingIds) yieldsBalancesStore.storeError(userWalletId = params.userWalletId, stakingIds = stakingIds)
} }
@ -163,21 +163,21 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor(
if (!allResponsesReceived(requests, yieldBalances)) { if (!allResponsesReceived(requests, yieldBalances)) {
val values = stakingIds.filter { stakingId -> val values = stakingIds.filter { stakingId ->
yieldBalances.none { yieldBalances.none { balanceWrapper ->
stakingId.integrationId == it.integrationId && stakingId.integrationId == balanceWrapper.integrationId &&
stakingId.address == it.addresses.address stakingId.address == balanceWrapper.addresses.address
} }
} }
yieldsBalancesStore.storeError(userWalletId = userWalletId, stakingIds = values.toSet()) yieldsBalancesStore.storeError(userWalletId = userWalletId, stakingIds = values.toSet())
} }
}, },
onError = { onError = { throwable ->
Timber.e(it, "Unable to fetch yield balances $userWalletId") Timber.e(throwable, "Unable to fetch yield balances $userWalletId")
yieldsBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds) yieldsBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds)
throw it throw throwable
}, },
) )
} }
@ -187,9 +187,9 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor(
yieldBalances: Set<YieldBalanceWrapperDTO>, yieldBalances: Set<YieldBalanceWrapperDTO>,
): Boolean { ): Boolean {
return requests.all { request -> return requests.all { request ->
yieldBalances.any { yieldBalances.any { balance ->
request.integrationId == it.integrationId && request.integrationId == balance.integrationId &&
request.addresses.address == it.addresses.address request.addresses.address == balance.addresses.address
} }
} }
} }

View file

@ -14,7 +14,7 @@ internal object YieldBalanceRequestBodyFactory {
return YieldBalanceRequestBody( return YieldBalanceRequestBody(
addresses = YieldBalanceRequestBodyAddressFactory.create(stakingID), addresses = YieldBalanceRequestBodyAddressFactory.create(stakingID),
args = YieldBalanceRequestBody.YieldBalanceRequestArgs( args = YieldBalanceRequestBody.YieldBalanceRequestArgs(
validatorAddresses = listOf(), // todo add validators [REDACTED_JIRA] validatorAddresses = emptyList(), // todo add validators [REDACTED_JIRA]
), ),
integrationId = stakingID.integrationId, integrationId = stakingID.integrationId,
) )

View file

@ -38,7 +38,7 @@ features/staking/impl 56
data/wallet-connect 55 data/wallet-connect 55
features/swap-v2/impl 53 features/swap-v2/impl 53
features/walletconnect/impl 51 features/walletconnect/impl 51
features/tokendetails/impl 50 features/tokendetails/impl 49
features/manage-tokens/impl 45 features/manage-tokens/impl 45
domain/wallets 39 domain/wallets 39
features/nft/impl 36 features/nft/impl 36
@ -65,7 +65,6 @@ features/details/impl 10
data/markets 10 data/markets 10
domain/staking 9 domain/staking 9
data/yield-supply 9 data/yield-supply 9
data/staking 9
data/networks 9 data/networks 9
features/welcome/impl 8 features/welcome/impl 8
features/home/impl 8 features/home/impl 8

View file

@ -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.ActionParams
import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
import com.tangem.domain.staking.repositories.StakingErrorResolver 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 com.tangem.domain.models.wallet.UserWalletId
/** /**
* Use case for staking gas estimation. * Use case for staking gas estimation.
*/ */
class EstimateGasUseCase( class EstimateGasUseCase(
private val stakingRepository: StakingRepository, private val stakeKitRepository: StakeKitRepository,
private val stakingErrorResolver: StakingErrorResolver, private val stakingErrorResolver: StakingErrorResolver,
) { ) {
@ -23,7 +23,7 @@ class EstimateGasUseCase(
params: ActionParams, params: ActionParams,
): Either<StakingError, StakingGasEstimate> { ): Either<StakingError, StakingGasEstimate> {
return Either.catch { return Either.catch {
stakingRepository.estimateGas(userWalletId, network, params) stakeKitRepository.estimateGas(userWalletId, network, params)
}.mapLeft { }.mapLeft {
stakingErrorResolver.resolve(it) stakingErrorResolver.resolve(it)
} }

View file

@ -5,17 +5,17 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.staking.NetworkType import com.tangem.domain.models.staking.NetworkType
import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus 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.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.staking.repositories.StakeKitRepository
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
/** /**
* Use case for getting pending actions list. * Use case for getting pending actions list.
*/ */
class FetchActionsUseCase( class FetchActionsUseCase(
private val stakingRepository: StakingRepository, private val stakeKitRepository: StakeKitRepository,
private val stakingActionRepository: StakingActionRepository, private val stakeKitActionRepository: StakeKitActionRepository,
private val stakingErrorResolver: StakingErrorResolver, private val stakingErrorResolver: StakingErrorResolver,
) { ) {
@ -27,14 +27,14 @@ class FetchActionsUseCase(
): Either<StakingError, Unit> { ): Either<StakingError, Unit> {
return Either return Either
.catch { .catch {
val actions = stakingRepository.getActions( val actions = stakeKitRepository.getActions(
userWalletId = userWalletId, userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency, cryptoCurrency = cryptoCurrency,
networkType = networkType, networkType = networkType,
stakingActionStatus = stakingActionStatus, stakingActionStatus = stakingActionStatus,
) )
stakingActionRepository.store(userWalletId, cryptoCurrency.id, actions) stakeKitActionRepository.store(userWalletId, cryptoCurrency.id, actions)
} }
.mapLeft { stakingErrorResolver.resolve(it) } .mapLeft { stakingErrorResolver.resolve(it) }
} }

View file

@ -6,7 +6,7 @@ import arrow.core.raise.either
import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.repositories.P2PEthPoolRepository import com.tangem.domain.staking.repositories.P2PEthPoolRepository
import com.tangem.domain.staking.repositories.StakingErrorResolver 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.coroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -15,7 +15,7 @@ import kotlinx.coroutines.launch
* Fetches both StakeKit yields and P2P vaults * Fetches both StakeKit yields and P2P vaults
*/ */
class FetchStakingOptionsUseCase( class FetchStakingOptionsUseCase(
private val stakingRepository: StakingRepository, private val stakeKitRepository: StakeKitRepository,
private val p2pRepository: P2PEthPoolRepository, private val p2pRepository: P2PEthPoolRepository,
private val stakingErrorResolver: StakingErrorResolver, private val stakingErrorResolver: StakingErrorResolver,
) { ) {
@ -24,7 +24,7 @@ class FetchStakingOptionsUseCase(
catch( catch(
block = { block = {
coroutineScope { coroutineScope {
launch { stakingRepository.fetchYields() } launch { stakeKitRepository.fetchYields() }
launch { p2pRepository.fetchVaults() } launch { p2pRepository.fetchVaults() }
} }
}, },

View file

@ -5,19 +5,19 @@ import arrow.core.raise.catch
import arrow.core.raise.either import arrow.core.raise.either
import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver 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 * Use case for getting enabled tokens
*/ */
class FetchStakingTokensUseCase( class FetchStakingTokensUseCase(
private val stakingRepository: StakingRepository, private val stakeKitRepository: StakeKitRepository,
private val stakingErrorResolver: StakingErrorResolver, private val stakingErrorResolver: StakingErrorResolver,
) { ) {
suspend operator fun invoke(): Either<StakingError, Unit> { suspend operator fun invoke(): Either<StakingError, Unit> {
return either { return either {
catch( catch(
block = { stakingRepository.fetchYields() }, block = { stakeKitRepository.fetchYields() },
catch = { stakingErrorResolver.resolve(it) }, catch = { stakingErrorResolver.resolve(it) },
) )
} }

View file

@ -7,7 +7,7 @@ import com.tangem.domain.core.utils.EitherFlow
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.model.stakekit.action.StakingAction 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.staking.repositories.StakingErrorResolver
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
@ -17,7 +17,7 @@ import kotlinx.coroutines.flow.map
* Use case for getting pending actions list. * Use case for getting pending actions list.
*/ */
class GetActionsUseCase( class GetActionsUseCase(
private val stakingActionRepository: StakingActionRepository, private val stakeKitActionRepository: StakeKitActionRepository,
private val stakingErrorResolver: StakingErrorResolver, private val stakingErrorResolver: StakingErrorResolver,
) { ) {
@ -25,7 +25,7 @@ class GetActionsUseCase(
userWalletId: UserWalletId, userWalletId: UserWalletId,
cryptoCurrencyId: CryptoCurrency.ID, cryptoCurrencyId: CryptoCurrency.ID,
): EitherFlow<StakingError, List<StakingAction>> { ): EitherFlow<StakingError, List<StakingAction>> {
return stakingActionRepository.get( return stakeKitActionRepository.get(
userWalletId = userWalletId, userWalletId = userWalletId,
cryptoCurrencyId = cryptoCurrencyId, cryptoCurrencyId = cryptoCurrencyId,
).map<List<StakingAction>, Either<StakingError, List<StakingAction>>> { it.right() } ).map<List<StakingAction>, Either<StakingError, List<StakingAction>>> { it.right() }

View file

@ -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.StakingError
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
import com.tangem.domain.staking.repositories.StakingErrorResolver import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.staking.repositories.StakeKitRepository
class GetConstructedStakingTransactionUseCase( class GetConstructedStakingTransactionUseCase(
private val stakingRepository: StakingRepository, private val stakeKitRepository: StakeKitRepository,
private val stakingErrorResolver: StakingErrorResolver, private val stakingErrorResolver: StakingErrorResolver,
) { ) {
@ -20,7 +20,7 @@ class GetConstructedStakingTransactionUseCase(
amount: Amount, amount: Amount,
transactionId: String, transactionId: String,
): Either<StakingError, Pair<StakingTransaction, TransactionData.Compiled>> = Either.catch { ): Either<StakingError, Pair<StakingTransaction, TransactionData.Compiled>> = Either.catch {
stakingRepository.constructTransaction(networkId, fee, amount, transactionId) stakeKitRepository.constructTransaction(networkId, fee, amount, transactionId)
}.mapLeft { }.mapLeft {
stakingErrorResolver.resolve(it) stakingErrorResolver.resolve(it)
} }

View file

@ -8,8 +8,8 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver 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.models.wallet.UserWalletId
import com.tangem.domain.staking.repositories.StakingRepository
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map

View file

@ -6,13 +6,13 @@ import com.tangem.domain.staking.model.StakingEntryInfo
import com.tangem.domain.staking.model.StakingOption import com.tangem.domain.staking.model.StakingOption
import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver 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. * Use case for getting entry info about staking on token screen.
*/ */
class GetStakingEntryInfoUseCase( class GetStakingEntryInfoUseCase(
private val stakingRepository: StakingRepository, private val stakeKitRepository: StakeKitRepository,
private val stakingErrorResolver: StakingErrorResolver, private val stakingErrorResolver: StakingErrorResolver,
) { ) {
@ -25,7 +25,7 @@ class GetStakingEntryInfoUseCase(
.catch { .catch {
when (stakingOption) { when (stakingOption) {
is StakingOption.StakeKit -> { is StakingOption.StakeKit -> {
stakingRepository.getEntryInfo( stakeKitRepository.getEntryInfo(
cryptoCurrencyId = cryptoCurrencyId, cryptoCurrencyId = cryptoCurrencyId,
symbol = symbol, symbol = symbol,
) )

View file

@ -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.ActionParams
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
import com.tangem.domain.staking.repositories.StakingErrorResolver 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 com.tangem.domain.models.wallet.UserWalletId
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -14,7 +14,7 @@ import kotlinx.coroutines.delay
* Use case for creating enter action * Use case for creating enter action
*/ */
class GetStakingTransactionsUseCase( class GetStakingTransactionsUseCase(
private val stakingRepository: StakingRepository, private val stakeKitRepository: StakeKitRepository,
private val stakingErrorResolver: StakingErrorResolver, private val stakingErrorResolver: StakingErrorResolver,
) { ) {
@ -24,7 +24,7 @@ class GetStakingTransactionsUseCase(
params: ActionParams, params: ActionParams,
): Either<StakingError, List<StakingTransaction>> { ): Either<StakingError, List<StakingTransaction>> {
return Either.catch { 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 // workaround, sometimes transaction is not created immediately after actions/enter
delay(PATCH_TRANSACTION_REQUEST_DELAY) delay(PATCH_TRANSACTION_REQUEST_DELAY)

View file

@ -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.StakingError
import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.repositories.StakingErrorResolver 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. * Use case for getting staking yield for staking scenario start.
*/ */
class GetYieldUseCase( class GetYieldUseCase(
private val stakingRepository: StakingRepository, private val stakeKitRepository: StakeKitRepository,
private val stakingErrorResolver: StakingErrorResolver, private val stakingErrorResolver: StakingErrorResolver,
) { ) {
suspend operator fun invoke(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Either<StakingError, Yield> { suspend operator fun invoke(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Either<StakingError, Yield> {
return Either return Either
.catch { stakingRepository.getYield(cryptoCurrencyId, symbol) } .catch { stakeKitRepository.getYield(cryptoCurrencyId, symbol) }
.mapLeft { stakingErrorResolver.resolve(it) } .mapLeft { stakingErrorResolver.resolve(it) }
} }
suspend operator fun invoke(yieldId: String): Either<StakingError, Yield> { suspend operator fun invoke(yieldId: String): Either<StakingError, Yield> {
return Either return Either
.catch { stakingRepository.getYield(yieldId) } .catch { stakeKitRepository.getYield(yieldId) }
.mapLeft { stakingErrorResolver.resolve(it) } .mapLeft { stakingErrorResolver.resolve(it) }
} }
} }

View file

@ -3,8 +3,8 @@ package com.tangem.domain.staking
import arrow.core.Either import arrow.core.Either
import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver 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.models.wallet.UserWalletId
import com.tangem.domain.staking.repositories.StakingRepository
class IsAnyTokenStakedUseCase( class IsAnyTokenStakedUseCase(
private val stakingRepository: StakingRepository, private val stakingRepository: StakingRepository,

View file

@ -4,19 +4,19 @@ import arrow.core.Either
import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.model.UnsubmittedTransactionMetadata import com.tangem.domain.staking.model.UnsubmittedTransactionMetadata
import com.tangem.domain.staking.repositories.StakingErrorResolver 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 * Use case for saving hash that failed to submit during staking confirmation
*/ */
class SaveUnsubmittedHashUseCase( class SaveUnsubmittedHashUseCase(
private val stakingTransactionHashRepository: StakingTransactionHashRepository, private val stakeKitTransactionHashRepository: StakeKitTransactionHashRepository,
private val stakingErrorResolver: StakingErrorResolver, private val stakingErrorResolver: StakingErrorResolver,
) { ) {
suspend operator fun invoke(transactionId: String, transactionHash: String): Either<StakingError, Unit> { suspend operator fun invoke(transactionId: String, transactionHash: String): Either<StakingError, Unit> {
return Either.catch { return Either.catch {
stakingTransactionHashRepository.storeUnsubmittedHash( stakeKitTransactionHashRepository.storeUnsubmittedHash(
unsubmittedTransactionMetadata = UnsubmittedTransactionMetadata( unsubmittedTransactionMetadata = UnsubmittedTransactionMetadata(
transactionId = transactionId, transactionId = transactionId,
transactionHash = transactionHash, transactionHash = transactionHash,

View file

@ -3,19 +3,19 @@ package com.tangem.domain.staking
import arrow.core.Either import arrow.core.Either
import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver 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 * Use case for commiting hashes that failed to submit during staking confirmation
*/ */
class SendUnsubmittedHashesUseCase( class SendUnsubmittedHashesUseCase(
private val stakingTransactionHashRepository: StakingTransactionHashRepository, private val stakeKitTransactionHashRepository: StakeKitTransactionHashRepository,
private val stakingErrorResolver: StakingErrorResolver, private val stakingErrorResolver: StakingErrorResolver,
) { ) {
suspend operator fun invoke(): Either<StakingError, Unit> { suspend operator fun invoke(): Either<StakingError, Unit> {
return Either return Either
.catch { stakingTransactionHashRepository.sendUnsubmittedHashes() } .catch { stakeKitTransactionHashRepository.sendUnsubmittedHashes() }
.mapLeft { stakingErrorResolver.resolve(it) } .mapLeft { stakingErrorResolver.resolve(it) }
} }
} }

View file

@ -4,20 +4,20 @@ import arrow.core.Either
import com.tangem.domain.staking.model.SubmitHashData import com.tangem.domain.staking.model.SubmitHashData
import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver 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 * Use case for submitting transaction hash to stakekit
*/ */
class SubmitHashUseCase( class SubmitHashUseCase(
private val stakingTransactionHashRepository: StakingTransactionHashRepository, private val stakeKitTransactionHashRepository: StakeKitTransactionHashRepository,
private val stakingErrorResolver: StakingErrorResolver, private val stakingErrorResolver: StakingErrorResolver,
) { ) {
suspend operator fun invoke(submitHashData: SubmitHashData): Either<StakingError, Unit> { suspend operator fun invoke(submitHashData: SubmitHashData): Either<StakingError, Unit> {
return Either return Either
.catch { .catch {
stakingTransactionHashRepository.submitHash( stakeKitTransactionHashRepository.submitHash(
transactionId = submitHashData.transactionId, transactionId = submitHashData.transactionId,
transactionHash = submitHashData.transactionHash, transactionHash = submitHashData.transactionHash,
) )

View file

@ -1,8 +1,10 @@
package com.tangem.domain.staking.repositories package com.tangem.domain.staking.repositories
import arrow.core.Either import arrow.core.Either
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.ethpool.* import com.tangem.domain.staking.model.ethpool.*
import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.StakingError
import kotlinx.coroutines.flow.Flow
interface P2PEthPoolRepository { interface P2PEthPoolRepository {
@ -113,4 +115,20 @@ interface P2PEthPoolRepository {
vaultAddress: String, vaultAddress: String,
period: Int? = null, period: Int? = null,
): Either<StakingError, List<P2PEthPoolReward>> ): Either<StakingError, List<P2PEthPoolReward>>
/**
* 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<StakingAvailability>
/**
* 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
} }

View file

@ -5,7 +5,7 @@ import com.tangem.domain.staking.model.stakekit.action.StakingAction
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface StakingActionRepository { interface StakeKitActionRepository {
suspend fun store(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, actions: List<StakingAction>) suspend fun store(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, actions: List<StakingAction>)

View file

@ -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<List<Yield>>
fun getStakingAvailability(rawCurrencyId: CryptoCurrency.RawID, symbol: String): Flow<StakingAvailability>
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<StakingAction>
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<StakingTransaction, TransactionData.Compiled>
}

View file

@ -2,7 +2,7 @@ package com.tangem.domain.staking.repositories
import com.tangem.domain.staking.model.UnsubmittedTransactionMetadata import com.tangem.domain.staking.model.UnsubmittedTransactionMetadata
interface StakingTransactionHashRepository { interface StakeKitTransactionHashRepository {
suspend fun submitHash(transactionId: String, transactionHash: String) suspend fun submitHash(transactionId: String, transactionHash: String)

View file

@ -1,36 +1,12 @@
package com.tangem.domain.staking.repositories 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.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.staking.model.StakingAvailability 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 import kotlinx.coroutines.flow.Flow
// TODO p2p make 3 repos: stakekit, p2p, common staking
@Suppress("TooManyFunctions")
interface StakingRepository { interface StakingRepository {
suspend fun fetchYields()
fun getEnabledYields(): Flow<List<Yield>>
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<StakingAvailability> fun getStakingAvailability(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Flow<StakingAvailability>
suspend fun getStakingAvailabilitySync( suspend fun getStakingAvailabilitySync(
@ -38,23 +14,5 @@ interface StakingRepository {
cryptoCurrency: CryptoCurrency, cryptoCurrency: CryptoCurrency,
): StakingAvailability ): StakingAvailability
suspend fun getActions(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
networkType: NetworkType,
stakingActionStatus: StakingActionStatus,
): List<StakingAction>
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<StakingTransaction, TransactionData.Compiled>
suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean
} }

View file

@ -1,7 +1,7 @@
package com.tangem.domain.staking.usecase package com.tangem.domain.staking.usecase
import com.tangem.domain.staking.model.stakekit.Yield 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.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@ -12,10 +12,10 @@ import kotlinx.coroutines.flow.map
* - key: currency staking key (network.backendId + "_" + symbol) * - key: currency staking key (network.backendId + "_" + symbol)
* - value: validators * - value: validators
*/ */
class StakingApyFlowUseCase(private val stakingRepository: StakingRepository) { class StakingApyFlowUseCase(private val stakeKitRepository: StakeKitRepository) {
operator fun invoke(): Flow<Map<String, List<Yield.Validator>>> { operator fun invoke(): Flow<Map<String, List<Yield.Validator>>> {
return stakingRepository.getEnabledYields() return stakeKitRepository.getEnabledYields()
.map { yields -> .map { yields ->
yields.associate { yield -> yields.associate { yield ->
val key = "${yield.token.coinGeckoId}_${yield.token.symbol}" val key = "${yield.token.coinGeckoId}_${yield.token.symbol}"

View file

@ -23,7 +23,6 @@
<ID>MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ internalUiState.value = stateFactory.getStateWithErrorDialog(stringReference(it)) Timber.e(it) }</ID> <ID>MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ internalUiState.value = stateFactory.getStateWithErrorDialog(stringReference(it)) Timber.e(it) }</ID>
<ID>MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ internalUiState.value = stateFactory.getStateWithUpdatedHidden( isBalanceHidden = it.isBalanceHidden, ) }</ID> <ID>MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ internalUiState.value = stateFactory.getStateWithUpdatedHidden( isBalanceHidden = it.isBalanceHidden, ) }</ID>
<ID>MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ sendButtonsEvents(it.states) internalUiState.value = stateFactory.getManageButtonsState(actions = it.states) }</ID> <ID>MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ sendButtonsEvents(it.states) internalUiState.value = stateFactory.getManageButtonsState(actions = it.states) }</ID>
<ID>MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ val stakingEntryInfo = if (it is StakingAvailability.Available) { getStakingEntryInfoUseCase( cryptoCurrencyId = cryptoCurrency.id, symbol = cryptoCurrency.symbol, ).getOrNull() } else { null } internalUiState.update { state -&gt; stateFactory.getStakingInfoState( state = state, stakingEntryInfo = stakingEntryInfo, stakingAvailability = it, cryptoCurrencyStatus = cryptoCurrencyStatus, ) } }</ID>
<ID>MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ val updatedState = stateFactory.getStateWithNotifications(it) notificationsAnalyticsSender.send(internalUiState.value, updatedState.notifications) internalUiState.value = updatedState }</ID> <ID>MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ val updatedState = stateFactory.getStateWithNotifications(it) notificationsAnalyticsSender.send(internalUiState.value, updatedState.notifications) internalUiState.value = updatedState }</ID>
<ID>MultilineLambdaItParameter:TokenDetailsOnrampTransactionStateConverter.kt$TokenDetailsOnrampTransactionStateConverter${ analyticsEventHandler.send(TokenOnrampAnalyticsEvent.GoToProvider) clickIntents.onGoToProviderClick(it) }</ID> <ID>MultilineLambdaItParameter:TokenDetailsOnrampTransactionStateConverter.kt$TokenDetailsOnrampTransactionStateConverter${ analyticsEventHandler.send(TokenOnrampAnalyticsEvent.GoToProvider) clickIntents.onGoToProviderClick(it) }</ID>
<ID>MultilineLambdaItParameter:TokenDetailsScreen.kt${ Notification( modifier = itemModifier.animateItem(), config = it.config, iconTint = when (it) { is TokenDetailsNotification.Informational -&gt; TangemTheme.colors.icon.accent is TokenDetailsNotification.UsedOutdatedData -&gt; TangemTheme.colors.text.attention else -&gt; null }, ) }</ID> <ID>MultilineLambdaItParameter:TokenDetailsScreen.kt${ Notification( modifier = itemModifier.animateItem(), config = it.config, iconTint = when (it) { is TokenDetailsNotification.Informational -&gt; TangemTheme.colors.icon.accent is TokenDetailsNotification.UsedOutdatedData -&gt; TangemTheme.colors.text.attention else -&gt; null }, ) }</ID>