Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-04 14:16:10 +03:00
parent 0fdcdda8d5
commit f58543481d
8 changed files with 55 additions and 25 deletions

View file

@ -21,8 +21,10 @@ dependencies {
/** Domain modules */
implementation(projects.domain.tokens.models)
implementation(projects.domain.staking)
implementation(projects.domain.wallets)
implementation(projects.domain.wallets.models)
implementation(projects.domain.legacy)
implementation(projects.domain.models)
/** Feature Api modules */
implementation(projects.features.staking.api)
@ -39,8 +41,8 @@ dependencies {
implementation(deps.moshi)
implementation(deps.moshi.kotlin)
implementation(projects.libs.blockchainSdk)
implementation(projects.libs.crypto)
implementation(deps.tangem.card.core)
implementation(deps.tangem.blockchain) {

View file

@ -1,6 +1,7 @@
package com.tangem.data.staking
import android.util.Base64
import arrow.core.getOrElse
import arrow.core.raise.catch
import com.squareup.moshi.Moshi
import com.tangem.blockchain.common.Blockchain
@ -51,9 +52,11 @@ import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.orZero
import com.tangem.lib.crypto.BlockchainUtils.isSolana
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
@ -69,6 +72,7 @@ internal class DefaultStakingRepository(
private val dispatchers: CoroutineDispatcherProvider,
private val stakingFeatureToggle: StakingFeatureToggles,
private val walletManagersFacade: WalletManagersFacade,
private val getUserWalletUseCase: GetUserWalletUseCase,
moshi: Moshi,
) : StakingRepository {
@ -159,17 +163,19 @@ internal class DefaultStakingRepository(
}
}
override suspend fun getStakingAvailabilityForActions(
cryptoCurrencyId: CryptoCurrency.ID,
symbol: String,
override suspend fun getStakingAvailability(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): StakingAvailability {
val rawCurrencyId = cryptoCurrencyId.rawCurrencyId ?: return StakingAvailability.Unavailable
if (checkForInvalidCardBatch(userWalletId, cryptoCurrency)) return StakingAvailability.Unavailable
val rawCurrencyId = cryptoCurrency.id.rawCurrencyId ?: return StakingAvailability.Unavailable
return withContext(dispatchers.io) {
val yields = getEnabledYields() ?: return@withContext StakingAvailability.Unavailable
val prefetchedYield = findPrefetchedYield(yields, rawCurrencyId, symbol)
val isSupported = isStakingSupported(getIntegrationKey(cryptoCurrencyId))
val prefetchedYield = findPrefetchedYield(yields, rawCurrencyId, cryptoCurrency.symbol)
val isSupported = isStakingSupported(getIntegrationKey(cryptoCurrency.id))
when {
prefetchedYield != null && isSupported -> {
@ -183,6 +189,21 @@ internal class DefaultStakingRepository(
}
}
private fun checkForInvalidCardBatch(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean {
val userWallet = getUserWalletUseCase(userWalletId).getOrElse {
error("Failed to get user wallet")
}
return when {
isSolana(cryptoCurrency.network.id.value) -> {
INVALID_BATCHES_FOR_SOLANA.contains(userWallet.scanResponse.card.batchId)
}
else -> {
false
}
}
}
override suspend fun createAction(
userWalletId: UserWalletId,
network: Network,
@ -625,6 +646,8 @@ internal class DefaultStakingRepository(
const val ETHEREUM_POLYGON_APPROVE_SPENDER = "0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908"
val INVALID_BATCHES_FOR_SOLANA = listOf("AC01", "CB79")
// uncomment items as implementation is ready
val integrationIdMap = mapOf(
Blockchain.Solana.run { id + toCoinId() } to SOLANA_INTEGRATION_ID,

View file

@ -14,6 +14,7 @@ import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
@ -37,6 +38,7 @@ internal object StakingDataModule {
stakingFeatureToggle: StakingFeatureToggles,
cacheRegistry: CacheRegistry,
walletManagersFacade: WalletManagersFacade,
getUserWalletUseCase: GetUserWalletUseCase,
@NetworkMoshi moshi: Moshi,
): StakingRepository {
return DefaultStakingRepository(
@ -48,6 +50,7 @@ internal object StakingDataModule {
cacheRegistry = cacheRegistry,
stakingFeatureToggle = stakingFeatureToggle,
walletManagersFacade = walletManagersFacade,
getUserWalletUseCase = getUserWalletUseCase,
moshi = moshi,
)
}

View file

@ -6,6 +6,7 @@ import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
/**
* Use case for getting info about staking capability in tangem app.
@ -16,11 +17,16 @@ class GetStakingAvailabilityUseCase(
) {
suspend operator fun invoke(
cryptoCurrencyId: CryptoCurrency.ID,
symbol: String,
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): Either<StakingError, StakingAvailability> {
return Either
.catch { stakingRepository.getStakingAvailabilityForActions(cryptoCurrencyId, symbol) }
.catch {
stakingRepository.getStakingAvailability(
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
)
}
.mapLeft { stakingErrorResolver.resolve(it) }
}
}

View file

@ -32,10 +32,7 @@ interface StakingRepository {
suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield
suspend fun getStakingAvailabilityForActions(
cryptoCurrencyId: CryptoCurrency.ID,
symbol: String,
): StakingAvailability
suspend fun getStakingAvailability(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): StakingAvailability
suspend fun fetchSingleYieldBalance(
userWalletId: UserWalletId,

View file

@ -129,14 +129,13 @@ class GetCryptoCurrencyActionsUseCase(
// staking
if (stakingFeatureToggles.isStakingEnabled) {
if (isStakingAvailable(cryptoCurrency)) {
if (isStakingAvailable(userWallet, cryptoCurrency)) {
val yield = kotlin.runCatching {
stakingRepository.getYield(
cryptoCurrencyId = cryptoCurrency.id,
symbol = cryptoCurrency.symbol,
)
}.getOrNull()
activeList.add(
TokenActionsState.ActionState.Stake(
unavailabilityReason = ScenarioUnavailabilityReason.None,
@ -301,10 +300,10 @@ class GetCryptoCurrencyActionsUseCase(
return networkAddress != null && networkAddress.defaultAddress.value.isNotEmpty()
}
private suspend fun isStakingAvailable(cryptoCurrency: CryptoCurrency): Boolean {
return stakingRepository.getStakingAvailabilityForActions(
cryptoCurrencyId = cryptoCurrency.id,
symbol = cryptoCurrency.symbol,
private suspend fun isStakingAvailable(userWallet: UserWallet, cryptoCurrency: CryptoCurrency): Boolean {
return stakingRepository.getStakingAvailability(
userWalletId = userWallet.walletId,
cryptoCurrency = cryptoCurrency,
) is StakingAvailability.Available
}
}

View file

@ -113,9 +113,9 @@ class MockStakingRepository : StakingRepository {
isAvailable = false,
)
override suspend fun getStakingAvailabilityForActions(
cryptoCurrencyId: CryptoCurrency.ID,
symbol: String,
override suspend fun getStakingAvailability(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): StakingAvailability = StakingAvailability.Unavailable
override suspend fun fetchSingleYieldBalance(

View file

@ -393,8 +393,8 @@ internal class TokenDetailsViewModel @Inject constructor(
private fun updateStakingInfo() {
viewModelScope.launch {
val stakingAvailability = getStakingAvailabilityUseCase(
cryptoCurrencyId = cryptoCurrency.id,
symbol = cryptoCurrency.symbol,
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
).getOrElse { StakingAvailability.Unavailable }
internalUiState.value = stateFactory.getStateWithUpdatedStakingAvailability(stakingAvailability)