Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-05 17:23:23 +02:00
parent 27a864a0a9
commit 0e29df4ce9
45 changed files with 731 additions and 137 deletions

View file

@ -1,9 +1,16 @@
plugins {
alias(deps.plugins.kotlin.jvm)
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
id("configuration")
}
android {
namespace = "com.tangem.domain.staking"
}
dependencies {
implementation(deps.kotlin.coroutines)
implementation(deps.arrow.core)
implementation(projects.domain.tokens.models)
}

View file

@ -1,6 +1,9 @@
package com.tangem.domain.staking
import arrow.core.Either
import arrow.core.raise.catch
import arrow.core.raise.either
import com.tangem.domain.staking.error.StakingTokensError
import com.tangem.domain.staking.repositories.StakingRepository
/**
@ -10,6 +13,11 @@ class FetchStakingTokensUseCase(
private val stakingRepository: StakingRepository,
) {
suspend operator fun invoke(): Either<Throwable, Unit> {
return Either.catch { stakingRepository.fetchEnabledTokens() }
return either {
catch(
block = { stakingRepository.fetchEnabledYields() },
catch = { StakingTokensError.DataError(it) },
)
}
}
}

View file

@ -2,15 +2,16 @@ package com.tangem.domain.staking
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.model.CryptoCurrency
/**
* Use case for getting info about staking availability for certain blockchain.
* Use case for getting info about staking capability in tangem app.
*/
class GetStakingAvailabilityUseCase(
private val stakingRepository: StakingRepository,
) {
operator fun invoke(blockchainNetworkId: String): StakingAvailability {
return stakingRepository.getStakingAvailability(blockchainNetworkId)
suspend operator fun invoke(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): StakingAvailability {
return stakingRepository.getStakingAvailabilityForActions(cryptoCurrencyId, symbol)
}
}

View file

@ -0,0 +1,6 @@
package com.tangem.domain.staking.error
sealed class StakingTokensError {
data class DataError(val cause: Throwable) : StakingTokensError()
}

View file

@ -5,4 +5,6 @@ sealed class StakingAvailability {
data class Available(val integrationId: String) : StakingAvailability()
data object Unavailable : StakingAvailability()
data object TemporaryDisabled : StakingAvailability()
}

View file

@ -0,0 +1,172 @@
package com.tangem.domain.staking.model
import java.math.BigDecimal
data class Yield(
val id: String,
val token: Token,
val tokens: List<Token>,
val args: Args,
val status: Status,
val apy: BigDecimal,
val rewardRate: Double,
val rewardType: RewardType,
val metadata: Metadata,
val validators: List<Validator>,
val isAvailable: Boolean,
) {
data class Status(
val enter: Boolean,
val exit: Boolean?,
)
data class Args(
val enter: Enter,
val exit: Enter?,
) {
data class Enter(
val addresses: Addresses,
val args: Map<String, AddressArgument>,
) {
data class Addresses(
val address: AddressArgument,
val additionalAddresses: Map<String, AddressArgument>? = null,
)
}
}
data class Validator(
val address: String,
val status: String,
val name: String,
val image: String?,
val website: String?,
val apr: Double?,
val commission: Double?,
val stakedBalance: String?,
val votingPower: Double?,
val preferred: Boolean,
)
data class Metadata(
val name: String,
val logoUri: String,
val description: String,
val documentation: String?,
val gasFeeToken: Token,
val token: Token,
val tokens: List<Token>,
val type: String,
val rewardSchedule: String,
val cooldownPeriod: Period,
val warmupPeriod: Period,
val rewardClaiming: String,
val defaultValidator: String?,
val minimumStake: Int?,
val supportsMultipleValidators: Boolean,
val revshare: Enabled,
val fee: Enabled,
) {
data class Period(
val days: Int,
)
data class Enabled(
val enabled: Boolean,
)
}
enum class RewardType {
APY, // compound rate
APR, // simple rate
UNKNOWN,
}
}
data class Token(
val name: String,
val network: NetworkType,
val symbol: String,
val decimals: Int,
val address: String?,
val coinGeckoId: String?,
val logoURI: String?,
val isPoints: Boolean?,
) {
enum class NetworkType {
AVALANCHE_C,
AVALANCHE_ATOMIC,
AVALANCHE_P,
ARBITRUM,
BINANCE,
CELO,
ETHEREUM,
ETHEREUM_GOERLI,
ETHEREUM_HOLESKY,
FANTOM,
HARMONY,
OPTIMISM,
POLYGON,
GNOSIS,
MOONRIVER,
OKC,
ZKSYNC,
VICTION,
AGORIC,
AKASH,
AXELAR,
BAND_PROTOCOL,
BITSONG,
CANTO,
CHIHUAHUA,
COMDEX,
COREUM,
COSMOS,
CRESCENT,
CRONOS,
CUDOS,
DESMOS,
DYDX,
EVMOS,
FETCH_AI,
GRAVITY_BRIDGE,
INJECTIVE,
IRISNET,
JUNO,
KAVA,
KI_NETWORK,
MARS_PROTOCOL,
NYM,
OKEX_CHAIN,
ONOMY,
OSMOSIS,
PERSISTENCE,
QUICKSILVER,
REGEN,
SECRET,
SENTINEL,
SOMMELIER,
STAFI,
STARGAZE,
STRIDE,
TERITORI,
TGRADE,
UMEE,
POLKADOT,
KUSAMA,
WESTEND,
BINANCEBEACON,
NEAR,
SOLANA,
TEZOS,
TRON,
UNKNOWN,
}
}
data class AddressArgument(
val required: Boolean,
val network: String? = null,
val minimum: Double? = null,
val maximum: Double? = null,
)

View file

@ -2,12 +2,18 @@ package com.tangem.domain.staking.repositories
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingEntryInfo
import com.tangem.domain.tokens.model.CryptoCurrency
interface StakingRepository {
fun getStakingAvailability(blockchainId: String): StakingAvailability
suspend fun getStakingAvailabilityForActions(
cryptoCurrencyId: CryptoCurrency.ID,
symbol: String,
): StakingAvailability
suspend fun getEntryInfo(integrationId: String): StakingEntryInfo
suspend fun fetchEnabledTokens()
suspend fun fetchEnabledYields()
fun isStakingSupported(currencyId: String): Boolean
}

View file

@ -14,6 +14,7 @@ dependencies {
api(projects.domain.core)
implementation(projects.domain.models)
implementation(projects.domain.legacy)
implementation(projects.domain.staking)
implementation(projects.libs.blockchainSdk)
implementation(projects.domain.tokens.models)
implementation(projects.domain.txhistory.models)

View file

@ -2,6 +2,8 @@ package com.tangem.domain.tokens
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.model.*
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
@ -28,6 +30,7 @@ class GetCryptoCurrencyActionsUseCase(
private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
private val stakingRepository: StakingRepository,
private val dispatchers: CoroutineDispatcherProvider,
) {
@ -121,6 +124,17 @@ class GetCryptoCurrencyActionsUseCase(
activeList.add(TokenActionsState.ActionState.Receive(scenario))
}
// staking
if (isStakingAvailable(cryptoCurrency)) {
activeList.add(TokenActionsState.ActionState.Stake(ScenarioUnavailabilityReason.None))
} else {
disabledList.add(
TokenActionsState.ActionState.Stake(
unavailabilityReason = ScenarioUnavailabilityReason.StakingUnavailable(cryptoCurrency.name),
),
)
}
// send
val sendUnavailabilityReason = getSendUnavailabilityReason(
cryptoCurrencyStatus = cryptoCurrencyStatus,
@ -234,6 +248,7 @@ class GetCryptoCurrencyActionsUseCase(
}
actionsList.add(TokenActionsState.ActionState.Receive(scenario))
}
actionsList.add(TokenActionsState.ActionState.Stake(ScenarioUnavailabilityReason.Unreachable))
actionsList.add(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None))
return actionsList
}
@ -264,4 +279,11 @@ class GetCryptoCurrencyActionsUseCase(
private fun isAddressAvailable(networkAddress: NetworkAddress?): Boolean {
return networkAddress != null && networkAddress.defaultAddress.value.isNotEmpty()
}
private suspend fun isStakingAvailable(cryptoCurrency: CryptoCurrency): Boolean {
return stakingRepository.getStakingAvailabilityForActions(
cryptoCurrencyId = cryptoCurrency.id,
symbol = cryptoCurrency.symbol,
) is StakingAvailability.Available
}
}

View file

@ -3,6 +3,9 @@ package com.tangem.domain.tokens.model
sealed class ScenarioUnavailabilityReason {
data object None : ScenarioUnavailabilityReason()
// staking-specific
data class StakingUnavailable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason()
// send&sell-specific
data class PendingTransaction(
val withdrawalScenario: WithdrawalScenario,

View file

@ -20,6 +20,8 @@ data class TokenActionsState(
data class Receive(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
data class Stake(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
data class Swap(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
data class Send(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()