Updated on 2026-08-14

This commit is contained in:
Tangem 2024-07-15 17:27:27 +03:00
parent ec7dcde2d3
commit 75f8a45cd4
23 changed files with 569 additions and 65 deletions

View file

@ -594,7 +594,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
lifecycleScope.launch {
sendUnsubmittedHashesUseCase.invoke()
.onRight { Timber.d("Submitting hashes succeeded") }
.onLeft { Timber.e(it) }
.onLeft { Timber.e(it.toString()) }
}
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.tap.di.domain
import com.tangem.domain.staking.*
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository
import dagger.Module
import dagger.Provides
@ -14,69 +15,121 @@ internal object StakingDomainModule {
@Provides
@Singleton
fun provideGetStakingAvailabilityUseCase(stakingRepository: StakingRepository): GetStakingAvailabilityUseCase {
fun provideGetStakingAvailabilityUseCase(
stakingRepository: StakingRepository,
stakingErrorResolver: StakingErrorResolver,
): GetStakingAvailabilityUseCase {
return GetStakingAvailabilityUseCase(
stakingRepository = stakingRepository,
stakingErrorResolver = stakingErrorResolver,
)
}
@Provides
@Singleton
fun provideGetStakingEntryInfoUseCase(stakingRepository: StakingRepository): GetStakingEntryInfoUseCase {
fun provideGetStakingEntryInfoUseCase(
stakingRepository: StakingRepository,
stakingErrorResolver: StakingErrorResolver,
): GetStakingEntryInfoUseCase {
return GetStakingEntryInfoUseCase(
stakingRepository = stakingRepository,
stakingErrorResolver = stakingErrorResolver,
)
}
@Provides
@Singleton
fun provideGetYieldUseCase(stakingRepository: StakingRepository): GetYieldUseCase {
fun provideGetYieldUseCase(
stakingRepository: StakingRepository,
stakingErrorResolver: StakingErrorResolver,
): GetYieldUseCase {
return GetYieldUseCase(
stakingRepository = stakingRepository,
stakingErrorResolver = stakingErrorResolver,
)
}
@Provides
@Singleton
fun provideGetStakingTokensUseCase(stakingRepository: StakingRepository): FetchStakingTokensUseCase {
fun provideGetStakingTokensUseCase(
stakingRepository: StakingRepository,
stakingErrorResolver: StakingErrorResolver,
): FetchStakingTokensUseCase {
return FetchStakingTokensUseCase(
stakingRepository = stakingRepository,
stakingErrorResolver = stakingErrorResolver,
)
}
@Provides
@Singleton
fun provideFetchStakingYieldBalanceUseCase(stakingRepository: StakingRepository): FetchStakingYieldBalanceUseCase {
return FetchStakingYieldBalanceUseCase(stakingRepository = stakingRepository)
fun provideFetchStakingYieldBalanceUseCase(
stakingRepository: StakingRepository,
stakingErrorResolver: StakingErrorResolver,
): FetchStakingYieldBalanceUseCase {
return FetchStakingYieldBalanceUseCase(
stakingRepository = stakingRepository,
stakingErrorResolver = stakingErrorResolver,
)
}
@Provides
@Singleton
fun provideGetStakingYieldBalanceUseCase(stakingRepository: StakingRepository): GetStakingYieldBalanceUseCase {
return GetStakingYieldBalanceUseCase(stakingRepository = stakingRepository)
fun provideGetStakingYieldBalanceUseCase(
stakingRepository: StakingRepository,
stakingErrorResolver: StakingErrorResolver,
): GetStakingYieldBalanceUseCase {
return GetStakingYieldBalanceUseCase(
stakingRepository = stakingRepository,
stakingErrorResolver = stakingErrorResolver,
)
}
@Provides
@Singleton
fun provideCreateEnterActionUseCase(stakingRepository: StakingRepository): InitializeStakingProcessUseCase {
return InitializeStakingProcessUseCase(stakingRepository)
fun provideCreateEnterActionUseCase(
stakingRepository: StakingRepository,
stakingErrorResolver: StakingErrorResolver,
): InitializeStakingProcessUseCase {
return InitializeStakingProcessUseCase(
stakingRepository = stakingRepository,
stakingErrorResolver = stakingErrorResolver,
)
}
@Provides
@Singleton
fun provideSubmitHashUseCase(stakingRepository: StakingRepository): SubmitHashUseCase {
return SubmitHashUseCase(stakingRepository)
fun provideSubmitHashUseCase(
stakingRepository: StakingRepository,
stakingErrorResolver: StakingErrorResolver,
): SubmitHashUseCase {
return SubmitHashUseCase(
stakingRepository = stakingRepository,
stakingErrorResolver = stakingErrorResolver,
)
}
@Provides
@Singleton
fun provideSaveUnsubmittedHashUseCase(stakingRepository: StakingRepository): SaveUnsubmittedHashUseCase {
return SaveUnsubmittedHashUseCase(stakingRepository)
fun provideSaveUnsubmittedHashUseCase(
stakingRepository: StakingRepository,
stakingErrorResolver: StakingErrorResolver,
): SaveUnsubmittedHashUseCase {
return SaveUnsubmittedHashUseCase(
stakingRepository = stakingRepository,
stakingErrorResolver = stakingErrorResolver,
)
}
@Provides
@Singleton
fun provideSendUnsubmittedHashesUseCase(stakingRepository: StakingRepository): SendUnsubmittedHashesUseCase {
return SendUnsubmittedHashesUseCase(stakingRepository)
fun provideSendUnsubmittedHashesUseCase(
stakingRepository: StakingRepository,
stakingErrorResolver: StakingErrorResolver,
): SendUnsubmittedHashesUseCase {
return SendUnsubmittedHashesUseCase(
stakingRepository = stakingRepository,
stakingErrorResolver = stakingErrorResolver,
)
}
}

View file

@ -125,7 +125,7 @@ internal class MainViewModel @Inject constructor(
private fun fetchStakingTokens() {
viewModelScope.launch(dispatchers.main) {
fetchStakingTokensUseCase()
.onLeft { Timber.e(it, "Unable to fetch the staking tokens list") }
.onLeft { Timber.e(it.toString(), "Unable to fetch the staking tokens list") }
.onRight { Timber.d("Staking token list was fetched successfully") }
}
}

View file

@ -0,0 +1,143 @@
package com.tangem.datasource.api.stakekit.models.response.model.error
import com.squareup.moshi.Json
class StakeKitErrorResponse(
@Json(name = "details")
val details: StakeKitErrorDetailsDTO? = null,
@Json(name = "message")
val message: StakeKitErrorMessageDTO? = null,
@Json(name = "level")
val level: String? = null, // unused
)
data class StakeKitErrorDetailsDTO(
@Json(name = "arguments")
val arguments: String? = null,
@Json(name = "amount")
val amount: String? = null,
@Json(name = "yieldId")
val yieldId: String? = null,
)
enum class StakeKitErrorMessageDTO {
@Json(name = "MissingArgumentsError")
MISSING_ARGUMENTS_ERROR,
@Json(name = "MinimumAmountNotReached")
MINIMUM_AMOUNT_NOT_REACHED,
@Json(name = "YieldUnderMaintenanceError")
YIELD_UNDER_MAINTENANCE_ERROR,
@Json(name = "InsufficientFundsError")
INSUFFICIENT_FUNDS_ERROR,
@Json(name = "StakedPositionNotFoundError")
STAKED_POSITION_NOT_FOUND_ERROR,
@Json(name = "InvalidAmountSubmittedError")
INVALID_AMOUNT_SUBMITTED_ERROR,
@Json(name = "BalanceUnavailableError")
BALANCE_UNAVAILABLE_ERROR,
@Json(name = "GasPriceUnavailableError")
GAS_PRICE_UNAVAILABLE_ERROR,
@Json(name = "NotImplementedError")
NOT_IMPLEMENTED_ERROR,
@Json(name = "TokenNotFoundError")
TOKEN_NOT_FOUND_ERROR,
@Json(name = "BroadcastTransactionError")
BROADCAST_TRANSACTION_ERROR,
@Json(name = "MissingGasPriceStrategyError")
MISSING_GAS_PRICE_STRATEGY_ERROR,
@Json(name = "SubstrateMalformedTransactionHashError")
SUBSTRATE_MALFORMED_TRANSACTION_HASH_ERROR,
@Json(name = "TronMaximumAmountOfValidatorsExceededError")
TRON_MAXIMUM_AMOUNT_OF_VALIDATORS_EXCEEDED_ERROR,
@Json(name = "SubstratePoolNotFoundError")
SUBSTRATE_POOL_NOT_FOUND_ERROR,
@Json(name = "SubstrateBondedAmountTooLowError")
SUBSTRATE_BONDED_AMOUNT_TOO_LOW_ERROR,
@Json(name = "TronMissingResourceTypeArgumentError")
TRON_MISSING_RESOURCE_TYPE_ARGUMENT_ERROR,
@Json(name = "AaveV3PoolFrozenError")
AAVE_V3_POOL_FROZEN_ERROR,
@Json(name = "AaveV3TokenPairNotFoundError")
AAVE_V3_TOKEN_PAIR_NOT_FOUND_ERROR,
@Json(name = "YearnVaultAtMaxCapacityError")
YEARN_VAULT_AT_MAX_CAPACITY_ERROR,
@Json(name = "StETHNoWithdrawalRequestsFoundError")
STETH_NO_WITHDRAWAL_REQUESTS_FOUND_ERROR,
@Json(name = "MorphoLendingPoolPausedError")
MORPHO_LENDING_POOL_PAUSED_ERROR,
@Json(name = "NonceUnavailableError")
NONCE_UNAVAILABLE_ERROR,
@Json(name = "CosmosAcccountNotFoundError")
COSMOS_ACCOUNT_NOT_FOUND_ERROR,
@Json(name = "AvalancheMissingAdditionalAddressesArgumentError")
AVALANCHE_MISSING_ADDITIONAL_ADDRESSES_ARGUMENT_ERROR,
@Json(name = "AvalancheValidatorInfoNotFoundError")
AVALANCHE_VALIDATOR_INFO_NOT_FOUND_ERROR,
@Json(name = "SolanaTransactionSignatureVerificationFailureError")
SOLANA_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE_ERROR,
@Json(name = "SolanaUnableTocreateStakeAccountError")
SOLANA_UNABLE_TO_CREATE_STAKE_ACCOUNT_ERROR,
@Json(name = "SolanaStakeAmountTooLowError")
SOLANA_STAKE_AMOUNT_TOO_LOW_ERROR,
@Json(name = "SolanaUnstakeAmountTooLowError")
SOLANA_UNSTAKE_AMOUNT_TOO_LOW_ERROR,
@Json(name = "SolanaStakeAccountsNotFoundError")
SOLANA_STAKE_ACCOUNTS_NOT_FOUND_ERROR,
@Json(name = "SolanaEligibleStakeAccountsNotFoundError")
SOLANA_ELIGIBLE_STAKE_ACCOUNTS_NOT_FOUND_ERROR,
@Json(name = "TezosNoBalanceDelegatedError")
TEZOS_NO_BALANCE_DELEGATED_ERROR,
@Json(name = "TezosMissingPubkeyArgumentError")
TEZOS_MISSING_PUBKEY_ARGUMENT_ERROR,
@Json(name = "TezosEstimateRevealGasLimitError")
TEZOS_ESTIMATE_REVEAL_GAS_LIMIT_ERROR,
@Json(name = "TezosBalanceAlreadyDelegatedError")
TEZOS_BALANCE_ALREADY_DELEGATED_ERROR,
@Json(name = "BinanceAccountNotFoundError")
BINANCE_ACCOUNT_NOT_FOUND_ERROR,
@Json(name = "BinanceMissingAccountNumberOrSequenceError")
BINANCE_MISSING_ACCOUNT_NUMBER_OR_SEQUENCE_ERROR,
@Json(name = "GRTStakingDisabledError")
GRT_STAKING_DISABLED_ERROR,
@Json(name = "GRTStakingDisabledLedgerLiveError")
GRT_STAKING_DISABLED_LEDGER_LIVE_ERROR,
}

View file

@ -0,0 +1,19 @@
package com.tangem.data.staking
import com.tangem.data.staking.converters.error.StakeKitErrorConverter
import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.domain.staking.model.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver
internal class DefaultStakingErrorResolver(
private val stakeKitErrorConverter: StakeKitErrorConverter,
) : StakingErrorResolver {
override fun resolve(throwable: Throwable): StakingError {
return if (throwable is ApiResponseError.HttpException) {
stakeKitErrorConverter.convert(throwable.errorBody.orEmpty())
} else {
StakingError.UnknownError
}
}
}

View file

@ -0,0 +1,108 @@
package com.tangem.data.staking.converters.error
import com.squareup.moshi.JsonAdapter
import com.tangem.datasource.api.stakekit.models.response.model.error.StakeKitErrorMessageDTO
import com.tangem.datasource.api.stakekit.models.response.model.error.StakeKitErrorResponse
import com.tangem.domain.staking.model.StakingError
import com.tangem.utils.converter.Converter
internal class StakeKitErrorConverter(
private val jsonAdapter: JsonAdapter<StakeKitErrorResponse>,
) : Converter<String, StakingError> {
@Suppress("CyclomaticComplexMethod", "LongMethod")
override fun convert(value: String): StakingError {
return try {
val stakeKitErrorResponse = jsonAdapter.fromJson(value) ?: return StakingError.UnknownError
when (stakeKitErrorResponse.message) {
StakeKitErrorMessageDTO.MINIMUM_AMOUNT_NOT_REACHED -> StakingError.MinimumAmountNotReachedError(
amount = stakeKitErrorResponse.details?.amount ?: "",
)
StakeKitErrorMessageDTO.MISSING_ARGUMENTS_ERROR -> StakingError.MissingArgumentsError(
arguments = stakeKitErrorResponse.details?.arguments ?: "",
)
StakeKitErrorMessageDTO.YIELD_UNDER_MAINTENANCE_ERROR -> StakingError.YieldUnderMaintenanceError(
yieldId = stakeKitErrorResponse.details?.yieldId ?: "",
)
StakeKitErrorMessageDTO.INSUFFICIENT_FUNDS_ERROR ->
StakingError.InsufficientFundsError
StakeKitErrorMessageDTO.STAKED_POSITION_NOT_FOUND_ERROR ->
StakingError.StakedPositionNotFoundError
StakeKitErrorMessageDTO.INVALID_AMOUNT_SUBMITTED_ERROR ->
StakingError.InvalidAmountSubmittedError
StakeKitErrorMessageDTO.BALANCE_UNAVAILABLE_ERROR ->
StakingError.BalanceUnavailableError
StakeKitErrorMessageDTO.GAS_PRICE_UNAVAILABLE_ERROR ->
StakingError.GasPriceUnavailableError
StakeKitErrorMessageDTO.NOT_IMPLEMENTED_ERROR ->
StakingError.NotImplementedError
StakeKitErrorMessageDTO.TOKEN_NOT_FOUND_ERROR ->
StakingError.TokenNotFoundError
StakeKitErrorMessageDTO.BROADCAST_TRANSACTION_ERROR ->
StakingError.BroadcastTransactionError
StakeKitErrorMessageDTO.MISSING_GAS_PRICE_STRATEGY_ERROR ->
StakingError.MissingGasPriceStrategyError
StakeKitErrorMessageDTO.SUBSTRATE_MALFORMED_TRANSACTION_HASH_ERROR ->
StakingError.SubstrateMalformedTransactionHashError
StakeKitErrorMessageDTO.TRON_MAXIMUM_AMOUNT_OF_VALIDATORS_EXCEEDED_ERROR ->
StakingError.TronMaximumAmountOfValidatorsExceededError
StakeKitErrorMessageDTO.SUBSTRATE_POOL_NOT_FOUND_ERROR ->
StakingError.SubstratePoolNotFoundError
StakeKitErrorMessageDTO.SUBSTRATE_BONDED_AMOUNT_TOO_LOW_ERROR ->
StakingError.SubstrateBondedAmountTooLowError
StakeKitErrorMessageDTO.TRON_MISSING_RESOURCE_TYPE_ARGUMENT_ERROR ->
StakingError.TronMissingResourceTypeArgumentError
StakeKitErrorMessageDTO.AAVE_V3_POOL_FROZEN_ERROR ->
StakingError.AaveV3PoolFrozenError
StakeKitErrorMessageDTO.AAVE_V3_TOKEN_PAIR_NOT_FOUND_ERROR ->
StakingError.AaveV3TokenPairNotFoundError
StakeKitErrorMessageDTO.YEARN_VAULT_AT_MAX_CAPACITY_ERROR ->
StakingError.YearnVaultAtMaxCapacityError
StakeKitErrorMessageDTO.STETH_NO_WITHDRAWAL_REQUESTS_FOUND_ERROR ->
StakingError.StETHNoWithdrawalRequestsFoundError
StakeKitErrorMessageDTO.MORPHO_LENDING_POOL_PAUSED_ERROR ->
StakingError.MorphoLendingPoolPausedError
StakeKitErrorMessageDTO.NONCE_UNAVAILABLE_ERROR ->
StakingError.NonceUnavailableError
StakeKitErrorMessageDTO.COSMOS_ACCOUNT_NOT_FOUND_ERROR ->
StakingError.CosmosAcccountNotFoundError
StakeKitErrorMessageDTO.AVALANCHE_MISSING_ADDITIONAL_ADDRESSES_ARGUMENT_ERROR ->
StakingError.AvalancheMissingAdditionalAddressesArgumentError
StakeKitErrorMessageDTO.AVALANCHE_VALIDATOR_INFO_NOT_FOUND_ERROR ->
StakingError.AvalancheValidatorInfoNotFoundError
StakeKitErrorMessageDTO.SOLANA_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE_ERROR ->
StakingError.SolanaTransactionSignatureVerificationFailureError
StakeKitErrorMessageDTO.SOLANA_UNABLE_TO_CREATE_STAKE_ACCOUNT_ERROR ->
StakingError.SolanaUnableTocreateStakeAccountError
StakeKitErrorMessageDTO.SOLANA_STAKE_AMOUNT_TOO_LOW_ERROR ->
StakingError.SolanaStakeAmountTooLowError
StakeKitErrorMessageDTO.SOLANA_UNSTAKE_AMOUNT_TOO_LOW_ERROR ->
StakingError.SolanaUnstakeAmountTooLowError
StakeKitErrorMessageDTO.SOLANA_STAKE_ACCOUNTS_NOT_FOUND_ERROR ->
StakingError.SolanaStakeAccountsNotFoundError
StakeKitErrorMessageDTO.SOLANA_ELIGIBLE_STAKE_ACCOUNTS_NOT_FOUND_ERROR ->
StakingError.SolanaEligibleStakeAccountsNotFoundError
StakeKitErrorMessageDTO.TEZOS_NO_BALANCE_DELEGATED_ERROR ->
StakingError.TezosNoBalanceDelegatedError
StakeKitErrorMessageDTO.TEZOS_MISSING_PUBKEY_ARGUMENT_ERROR ->
StakingError.TezosMissingPubkeyArgumentError
StakeKitErrorMessageDTO.TEZOS_ESTIMATE_REVEAL_GAS_LIMIT_ERROR ->
StakingError.TezosEstimateRevealGasLimitError
StakeKitErrorMessageDTO.TEZOS_BALANCE_ALREADY_DELEGATED_ERROR ->
StakingError.TezosBalanceAlreadyDelegatedError
StakeKitErrorMessageDTO.BINANCE_ACCOUNT_NOT_FOUND_ERROR ->
StakingError.BinanceAccountNotFoundError
StakeKitErrorMessageDTO.BINANCE_MISSING_ACCOUNT_NUMBER_OR_SEQUENCE_ERROR ->
StakingError.BinanceMissingAccountNumberOrSequenceError
StakeKitErrorMessageDTO.GRT_STAKING_DISABLED_ERROR ->
StakingError.GRTStakingDisabledError
StakeKitErrorMessageDTO.GRT_STAKING_DISABLED_LEDGER_LIVE_ERROR ->
StakingError.GRTStakingDisabledLedgerLiveError
else -> StakingError.UnknownError
}
} catch (e: Exception) {
StakingError.UnknownError
}
}
}

View file

@ -1,11 +1,17 @@
package com.tangem.data.staking.di
import com.squareup.moshi.Moshi
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.staking.DefaultStakingErrorResolver
import com.tangem.data.staking.DefaultStakingRepository
import com.tangem.data.staking.converters.error.StakeKitErrorConverter
import com.tangem.datasource.api.stakekit.StakeKitApi
import com.tangem.datasource.api.stakekit.models.response.model.error.StakeKitErrorResponse
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.token.StakingBalanceStore
import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -40,4 +46,13 @@ internal object StakingDataModule {
stakingFeatureToggle = stakingFeatureToggle,
)
}
@Provides
@Singleton
internal fun provideStakingErrorResolver(@NetworkMoshi moshi: Moshi): StakingErrorResolver {
val jsonAdapter = moshi.adapter(StakeKitErrorResponse::class.java)
return DefaultStakingErrorResolver(
stakeKitErrorConverter = StakeKitErrorConverter(jsonAdapter),
)
}
}

View file

@ -0,0 +1,92 @@
package com.tangem.domain.staking.model
sealed class StakingError {
// region stakekit errors
data class MinimumAmountNotReachedError(val amount: String) : StakingError()
data class MissingArgumentsError(val arguments: String) : StakingError()
data class YieldUnderMaintenanceError(val yieldId: String) : StakingError()
data object InsufficientFundsError : StakingError()
data object StakedPositionNotFoundError : StakingError()
data object InvalidAmountSubmittedError : StakingError()
data object BalanceUnavailableError : StakingError()
data object GasPriceUnavailableError : StakingError()
data object NotImplementedError : StakingError()
data object TokenNotFoundError : StakingError()
data object BroadcastTransactionError : StakingError()
data object MissingGasPriceStrategyError : StakingError()
data object SubstrateMalformedTransactionHashError : StakingError()
data object TronMaximumAmountOfValidatorsExceededError : StakingError()
data object SubstratePoolNotFoundError : StakingError()
data object SubstrateBondedAmountTooLowError : StakingError()
data object TronMissingResourceTypeArgumentError : StakingError()
data object AaveV3PoolFrozenError : StakingError()
data object AaveV3TokenPairNotFoundError : StakingError()
data object YearnVaultAtMaxCapacityError : StakingError()
data object StETHNoWithdrawalRequestsFoundError : StakingError()
data object MorphoLendingPoolPausedError : StakingError()
data object NonceUnavailableError : StakingError()
data object CosmosAcccountNotFoundError : StakingError()
data object AvalancheMissingAdditionalAddressesArgumentError : StakingError()
data object AvalancheValidatorInfoNotFoundError : StakingError()
data object SolanaTransactionSignatureVerificationFailureError : StakingError()
data object SolanaUnableTocreateStakeAccountError : StakingError()
data object SolanaStakeAmountTooLowError : StakingError()
data object SolanaUnstakeAmountTooLowError : StakingError()
data object SolanaStakeAccountsNotFoundError : StakingError()
data object SolanaEligibleStakeAccountsNotFoundError : StakingError()
data object TezosNoBalanceDelegatedError : StakingError()
data object TezosMissingPubkeyArgumentError : StakingError()
data object TezosEstimateRevealGasLimitError : StakingError()
data object TezosBalanceAlreadyDelegatedError : StakingError()
data object BinanceAccountNotFoundError : StakingError()
data object BinanceMissingAccountNumberOrSequenceError : StakingError()
data object GRTStakingDisabledError : StakingError()
data object GRTStakingDisabledLedgerLiveError : StakingError()
// endregion
data class DataError(val cause: Throwable) : StakingError()
data object UnknownError : StakingError()
}

View file

@ -3,7 +3,8 @@ 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.model.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository
/**
@ -11,12 +12,13 @@ import com.tangem.domain.staking.repositories.StakingRepository
*/
class FetchStakingTokensUseCase(
private val stakingRepository: StakingRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
suspend operator fun invoke(isRefresh: Boolean = false): Either<Throwable, Unit> {
suspend operator fun invoke(isRefresh: Boolean = false): Either<StakingError, Unit> {
return either {
catch(
block = { stakingRepository.fetchEnabledYields(isRefresh) },
catch = { StakingTokensError.DataError(it) },
catch = { stakingErrorResolver.resolve(it) },
)
}
}

View file

@ -1,23 +1,35 @@
package com.tangem.domain.staking
import arrow.core.Either
import arrow.core.raise.catch
import arrow.core.raise.either
import com.tangem.domain.staking.model.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
import com.tangem.domain.wallets.models.UserWalletId
class FetchStakingYieldBalanceUseCase(
private val stakingRepository: StakingRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
suspend operator fun invoke(
userWalletId: UserWalletId,
address: CryptoCurrencyAddress,
refresh: Boolean = false,
): Either<Throwable, Unit> = Either.catch {
stakingRepository.fetchSingleYieldBalance(
userWalletId = userWalletId,
address = address,
refresh = refresh,
)
): Either<StakingError, Unit> {
return either {
catch(
block = {
stakingRepository.fetchSingleYieldBalance(
userWalletId = userWalletId,
address = address,
refresh = refresh,
)
},
catch = { stakingErrorResolver.resolve(it) },
)
}
}
}

View file

@ -1,6 +1,10 @@
package com.tangem.domain.staking
import arrow.core.Either
import arrow.core.raise.catch
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.model.CryptoCurrency
@ -9,9 +13,15 @@ import com.tangem.domain.tokens.model.CryptoCurrency
*/
class GetStakingAvailabilityUseCase(
private val stakingRepository: StakingRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
suspend operator fun invoke(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): StakingAvailability {
return stakingRepository.getStakingAvailabilityForActions(cryptoCurrencyId, symbol)
suspend operator fun invoke(
cryptoCurrencyId: CryptoCurrency.ID,
symbol: String,
): Either<StakingError, StakingAvailability> {
return Either
.catch { stakingRepository.getStakingAvailabilityForActions(cryptoCurrencyId, symbol) }
.mapLeft { stakingErrorResolver.resolve(it) }
}
}

View file

@ -2,14 +2,21 @@ package com.tangem.domain.staking
import arrow.core.Either
import com.tangem.domain.staking.model.StakingEntryInfo
import com.tangem.domain.staking.model.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository
/**
* Use case for getting entry info about staking on token screen.
*/
class GetStakingEntryInfoUseCase(private val stakingRepository: StakingRepository) {
class GetStakingEntryInfoUseCase(
private val stakingRepository: StakingRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
suspend operator fun invoke(integrationId: String): Either<Throwable, StakingEntryInfo> {
return Either.catch { stakingRepository.getEntryInfo(integrationId) }
suspend operator fun invoke(integrationId: String): Either<StakingError, StakingEntryInfo> {
return Either
.catch { stakingRepository.getEntryInfo(integrationId) }
.mapLeft { stakingErrorResolver.resolve(it) }
}
}

View file

@ -4,7 +4,9 @@ import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.domain.core.utils.EitherFlow
import com.tangem.domain.staking.model.StakingError
import com.tangem.domain.staking.model.YieldBalance
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
import com.tangem.domain.wallets.models.UserWalletId
@ -13,16 +15,17 @@ import kotlinx.coroutines.flow.map
class GetStakingYieldBalanceUseCase(
private val stakingRepository: StakingRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
operator fun invoke(
userWalletId: UserWalletId,
address: CryptoCurrencyAddress,
): EitherFlow<Throwable, YieldBalance> {
): EitherFlow<StakingError, YieldBalance> {
return stakingRepository.getSingleYieldBalanceFlow(
userWalletId = userWalletId,
address = address,
).map<YieldBalance, Either<Throwable, YieldBalance>> { it.right() }
.catch { emit(it.left()) }
).map<YieldBalance, Either<StakingError, YieldBalance>> { it.right() }
.catch { emit(stakingErrorResolver.resolve(it).left()) }
}
}

View file

@ -1,16 +1,24 @@
package com.tangem.domain.staking
import arrow.core.Either
import arrow.core.raise.catch
import com.tangem.domain.staking.model.StakingError
import com.tangem.domain.staking.model.Yield
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.model.CryptoCurrency
/**
* Use case for getting staking yield for staking scenario start.
*/
class GetYieldUseCase(private val stakingRepository: StakingRepository) {
class GetYieldUseCase(
private val stakingRepository: StakingRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
suspend operator fun invoke(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Either<Throwable, Yield> {
return Either.catch { stakingRepository.getYield(cryptoCurrencyId, symbol) }
suspend operator fun invoke(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Either<StakingError, Yield> {
return Either
.catch { stakingRepository.getYield(cryptoCurrencyId, symbol) }
.mapLeft { stakingErrorResolver.resolve(it) }
}
}

View file

@ -1,9 +1,11 @@
package com.tangem.domain.staking
import arrow.core.Either
import com.tangem.domain.staking.model.StakingError
import com.tangem.domain.staking.model.Token
import com.tangem.domain.staking.model.action.EnterAction
import com.tangem.domain.staking.model.transaction.StakingTransaction
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository
import kotlinx.coroutines.delay
import java.math.BigDecimal
@ -11,7 +13,10 @@ import java.math.BigDecimal
/**
* Use case for creating enter action
*/
class InitializeStakingProcessUseCase(private val stakingRepository: StakingRepository) {
class InitializeStakingProcessUseCase(
private val stakingRepository: StakingRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
suspend operator fun invoke(
integrationId: String,
@ -19,7 +24,7 @@ class InitializeStakingProcessUseCase(private val stakingRepository: StakingRepo
address: String,
validatorAddress: String,
token: Token,
): Either<Throwable, Pair<EnterAction, StakingTransaction>> {
): Either<StakingError, Pair<EnterAction, StakingTransaction>> {
return Either.catch {
val createAction = stakingRepository.createEnterAction(
integrationId = integrationId,
@ -36,6 +41,8 @@ class InitializeStakingProcessUseCase(private val stakingRepository: StakingRepo
val patchedTransaction = stakingRepository.constructTransaction(createdTransaction.id)
createAction to patchedTransaction
}.mapLeft {
stakingErrorResolver.resolve(it)
}
}

View file

@ -1,15 +1,20 @@
package com.tangem.domain.staking
import arrow.core.Either
import com.tangem.domain.staking.model.StakingError
import com.tangem.domain.staking.model.UnsubmittedTransactionMetadata
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository
/**
* Use case for saving hash that failed to submit during staking confirmation
*/
class SaveUnsubmittedHashUseCase(private val stakingRepository: StakingRepository) {
class SaveUnsubmittedHashUseCase(
private val stakingRepository: StakingRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
suspend operator fun invoke(transactionId: String, transactionHash: String): Either<Throwable, Unit> {
suspend operator fun invoke(transactionId: String, transactionHash: String): Either<StakingError, Unit> {
return Either.catch {
stakingRepository.storeUnsubmittedHash(
unsubmittedTransactionMetadata = UnsubmittedTransactionMetadata(
@ -17,6 +22,8 @@ class SaveUnsubmittedHashUseCase(private val stakingRepository: StakingRepositor
transactionHash = transactionHash,
),
)
}.mapLeft {
stakingErrorResolver.resolve(it)
}
}
}

View file

@ -1,16 +1,21 @@
package com.tangem.domain.staking
import arrow.core.Either
import com.tangem.domain.staking.model.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository
/**
* Use case for commiting hashes that failed to submit during staking confirmation
*/
class SendUnsubmittedHashesUseCase(private val stakingRepository: StakingRepository) {
class SendUnsubmittedHashesUseCase(
private val stakingRepository: StakingRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
suspend operator fun invoke(): Either<Throwable, Unit> {
return Either.catch {
stakingRepository.sendUnsubmittedHashes()
}
suspend operator fun invoke(): Either<StakingError, Unit> {
return Either
.catch { stakingRepository.sendUnsubmittedHashes() }
.mapLeft { stakingErrorResolver.resolve(it) }
}
}

View file

@ -1,19 +1,27 @@
package com.tangem.domain.staking
import arrow.core.Either
import com.tangem.domain.staking.model.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository
/**
* Use case for submitting transaction hash to stakekit
*/
class SubmitHashUseCase(private val stakingRepository: StakingRepository) {
class SubmitHashUseCase(
private val stakingRepository: StakingRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
suspend fun submitHash(transactionId: String, transactionHash: String): Either<Throwable, Unit> {
return Either.catch {
stakingRepository.submitHash(
transactionId = transactionId,
transactionHash = transactionHash,
)
}
suspend fun submitHash(transactionId: String, transactionHash: String): Either<StakingError, Unit> {
return Either
.catch {
stakingRepository.submitHash(
transactionId = transactionId,
transactionHash = transactionHash,
)
}.mapLeft {
stakingErrorResolver.resolve(it)
}
}
}

View file

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

View file

@ -0,0 +1,8 @@
package com.tangem.domain.staking.repositories
import com.tangem.domain.staking.model.*
interface StakingErrorResolver {
fun resolve(throwable: Throwable): StakingError
}

View file

@ -19,6 +19,7 @@ import com.tangem.domain.card.NetworkHasDerivationUseCase
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingEntryInfo
import com.tangem.domain.staking.model.StakingError
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.model.*
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
@ -239,7 +240,7 @@ internal class TokenDetailsStateFactory(
)
}
fun getStateWithStaking(stakingEither: Either<Throwable, StakingEntryInfo>): TokenDetailsState {
fun getStateWithStaking(stakingEither: Either<StakingError, StakingEntryInfo>): TokenDetailsState {
return currentStateProvider().copy(
stakingBlocksState = stakingStateConverter.convert(stakingEither),
)

View file

@ -3,6 +3,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
import arrow.core.Either
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.staking.model.StakingEntryInfo
import com.tangem.domain.staking.model.StakingError
import com.tangem.feature.tokendetails.presentation.tokendetails.state.StakingBlockUM
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
@ -12,9 +13,9 @@ import com.tangem.utils.converter.Converter
internal class TokenStakingStateConverter(
private val clickIntents: TokenDetailsClickIntents,
private val currentStateProvider: Provider<TokenDetailsState>,
) : Converter<Either<Throwable, StakingEntryInfo>, StakingBlockUM> {
) : Converter<Either<StakingError, StakingEntryInfo>, StakingBlockUM> {
override fun convert(value: Either<Throwable, StakingEntryInfo>): StakingBlockUM {
override fun convert(value: Either<StakingError, StakingEntryInfo>): StakingBlockUM {
val state = currentStateProvider()
if (state.stakingBlocksState is StakingBlockUM.Staked) return state.stakingBlocksState

View file

@ -389,7 +389,8 @@ internal class TokenDetailsViewModel @Inject constructor(
val stakingAvailability = getStakingAvailabilityUseCase(
cryptoCurrencyId = cryptoCurrency.id,
symbol = cryptoCurrency.symbol,
)
).getOrElse { StakingAvailability.Unavailable }
internalUiState.value = stateFactory.getStateWithUpdatedStakingAvailability(stakingAvailability)
if (stakingAvailability is StakingAvailability.Available) {
val stakingInfo = getStakingEntryInfoUseCase(stakingAvailability.integrationId)