Updated on 2026-08-14
This commit is contained in:
parent
330ae73357
commit
e7efe7b76a
4636 changed files with 234864 additions and 63507 deletions
1
data/staking/.gitignore
vendored
Normal file
1
data/staking/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
64
data/staking/build.gradle.kts
Normal file
64
data/staking/build.gradle.kts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import com.tangem.plugin.configuration.configurations.extension.kaptForObfuscatingVariants
|
||||
|
||||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.data.staking"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Core modules */
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
|
||||
/** Common modules */
|
||||
implementation(projects.data.common)
|
||||
|
||||
/** Domain modules */
|
||||
implementation(projects.core.configToggles)
|
||||
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)
|
||||
|
||||
// region DI
|
||||
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
// endregion
|
||||
|
||||
// region Others dependencies
|
||||
|
||||
implementation(deps.androidx.datastore)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.moshi)
|
||||
implementation(deps.moshi.kotlin)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.firebase.crashlytics)
|
||||
kaptForObfuscatingVariants(deps.moshi.kotlin.codegen)
|
||||
kaptForObfuscatingVariants(deps.retrofit.response.type.keeper)
|
||||
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.libs.crypto)
|
||||
|
||||
implementation(tangemDeps.card.core)
|
||||
implementation(tangemDeps.blockchain) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.data.staking
|
||||
|
||||
import com.tangem.datasource.local.token.StakingActionsStore
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingAction
|
||||
import com.tangem.domain.staking.repositories.StakingActionRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultStakingActionRepository(
|
||||
private val stakingActionsStore: StakingActionsStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : StakingActionRepository {
|
||||
|
||||
override suspend fun store(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyId: CryptoCurrency.ID,
|
||||
actions: List<StakingAction>,
|
||||
) {
|
||||
withContext(dispatchers.io) {
|
||||
stakingActionsStore.store(userWalletId, cryptoCurrencyId, actions)
|
||||
}
|
||||
}
|
||||
|
||||
override fun get(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Flow<List<StakingAction>> {
|
||||
return stakingActionsStore.get(userWalletId, cryptoCurrencyId)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.data.staking
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.staking.converters.error.StakeKitErrorConverter
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.domain.staking.analytics.StakingAnalyticsEvent
|
||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||
import com.tangem.domain.staking.repositories.StakingErrorResolver
|
||||
|
||||
internal class DefaultStakingErrorResolver(
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val stakeKitErrorConverter: StakeKitErrorConverter,
|
||||
) : StakingErrorResolver {
|
||||
|
||||
override fun resolve(throwable: Throwable): StakingError {
|
||||
val error = if (throwable is ApiResponseError.HttpException) {
|
||||
stakeKitErrorConverter.convert(throwable.errorBody.orEmpty())
|
||||
} else {
|
||||
StakingError.DomainError(throwable.message)
|
||||
}
|
||||
|
||||
when (error) {
|
||||
is StakingError.StakeKitApiError -> {
|
||||
analyticsEventHandler.send(StakingAnalyticsEvent.StakeKitApiError(error))
|
||||
}
|
||||
is StakingError.StakeKitUnknownError -> {
|
||||
analyticsEventHandler.send(StakingAnalyticsEvent.StakeKitApiUnknownError(error))
|
||||
}
|
||||
is StakingError.DomainError -> {
|
||||
analyticsEventHandler.send(StakingAnalyticsEvent.DomainError(error))
|
||||
}
|
||||
}
|
||||
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,739 @@
|
|||
package com.tangem.data.staking
|
||||
|
||||
import android.util.Base64
|
||||
import arrow.core.getOrElse
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.TransactionStatus
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toCoinId
|
||||
import com.tangem.blockchainsdk.utils.toMigratedCoinId
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toCompressedPublicKey
|
||||
import com.tangem.data.common.api.safeApiCall
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.staking.converters.YieldBalanceListConverter
|
||||
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.model.NetworkTypeDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
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.StakingBalanceStore
|
||||
import com.tangem.datasource.local.token.StakingYieldsStore
|
||||
import com.tangem.datasource.local.token.converter.StakingNetworkTypeConverter
|
||||
import com.tangem.datasource.local.token.converter.TokenConverter
|
||||
import com.tangem.domain.staking.model.StakingApproval
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.StakingEntryInfo
|
||||
import com.tangem.domain.staking.model.stakekit.NetworkType
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalanceList
|
||||
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.action.StakingActionType
|
||||
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.toggles.StakingFeatureToggles
|
||||
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.lib.crypto.BlockchainUtils.isSolana
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.plus
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import timber.log.Timber
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
|
||||
internal class DefaultStakingRepository(
|
||||
private val stakeKitApi: StakeKitApi,
|
||||
private val stakingYieldsStore: StakingYieldsStore,
|
||||
private val stakingBalanceStore: StakingBalanceStore,
|
||||
private val cacheRegistry: CacheRegistry,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val stakingFeatureToggles: StakingFeatureToggles,
|
||||
moshi: Moshi,
|
||||
) : StakingRepository {
|
||||
|
||||
private val transactionStatusConverter = StakingTransactionStatusConverter()
|
||||
private val transactionTypeConverter = StakingTransactionTypeConverter()
|
||||
private val actionStatusConverter = ActionStatusConverter()
|
||||
|
||||
private val transactionConverter = StakingTransactionConverter(
|
||||
transactionStatusConverter = transactionStatusConverter,
|
||||
transactionTypeConverter = transactionTypeConverter,
|
||||
)
|
||||
private val enterActionResponseConverter = EnterActionResponseConverter(
|
||||
actionStatusConverter = actionStatusConverter,
|
||||
transactionConverter = transactionConverter,
|
||||
)
|
||||
|
||||
private val tronStakeKitTransactionAdapter by lazy { moshi.adapter(TronStakeKitTransaction::class.java) }
|
||||
private val networkTypeAdapter by lazy { moshi.adapter(NetworkTypeDTO::class.java) }
|
||||
private val stakingActionStatusAdapter by lazy { moshi.adapter(StakingActionStatusDTO::class.java) }
|
||||
|
||||
override fun getIntegrationKey(cryptoCurrencyId: CryptoCurrency.ID): String = with(cryptoCurrencyId) {
|
||||
rawNetworkId.plus(rawCurrencyId)
|
||||
}
|
||||
|
||||
override fun getSupportedIntegrationId(cryptoCurrencyId: CryptoCurrency.ID): String? {
|
||||
return integrationIdMap.getOrDefault(getIntegrationKey(cryptoCurrencyId), null)
|
||||
}
|
||||
|
||||
override suspend fun fetchEnabledYields(refresh: Boolean) {
|
||||
withContext(dispatchers.io) {
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = YIELDS_STORE_KEY,
|
||||
skipCache = refresh,
|
||||
block = {
|
||||
when (val stakingTokensWithYields = stakeKitApi.getEnabledYields(preferredValidatorsOnly = false)) {
|
||||
is ApiResponse.Success -> stakingYieldsStore.store(
|
||||
stakingTokensWithYields.data.data.filter {
|
||||
it.isAvailable ?: false
|
||||
},
|
||||
)
|
||||
else -> {
|
||||
stakingYieldsStore.store(emptyList())
|
||||
throw (stakingTokensWithYields as ApiResponse.Error).cause
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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 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(
|
||||
apr = requireNotNull(yield.preferredValidators.maxByOrNull { it.apr.orZero() }?.apr),
|
||||
rewardSchedule = yield.metadata.rewardSchedule,
|
||||
tokenSymbol = yield.token.symbol,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getStakingAvailability(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Flow<StakingAvailability> {
|
||||
return channelFlow {
|
||||
if (!checkFeatureToggleEnabled(cryptoCurrency.network.id)) {
|
||||
send(StakingAvailability.Unavailable)
|
||||
return@channelFlow
|
||||
}
|
||||
|
||||
if (checkForInvalidCardBatch(userWalletId, cryptoCurrency)) {
|
||||
send(StakingAvailability.Unavailable)
|
||||
return@channelFlow
|
||||
}
|
||||
|
||||
val rawCurrencyId = cryptoCurrency.id.rawCurrencyId
|
||||
if (rawCurrencyId == null) {
|
||||
send(StakingAvailability.Unavailable)
|
||||
return@channelFlow
|
||||
}
|
||||
|
||||
val isSupportedInMobileApp = getSupportedIntegrationId(cryptoCurrency.id).isNullOrEmpty().not()
|
||||
|
||||
getEnabledYields()
|
||||
.distinctUntilChanged()
|
||||
.onEach { yields ->
|
||||
if (yields.isEmpty()) {
|
||||
send(StakingAvailability.TemporaryUnavailable)
|
||||
return@onEach
|
||||
}
|
||||
|
||||
val prefetchedYield = findPrefetchedYield(
|
||||
yields = yields,
|
||||
currencyId = rawCurrencyId,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
)
|
||||
when {
|
||||
prefetchedYield != null && isSupportedInMobileApp -> {
|
||||
send(StakingAvailability.Available(prefetchedYield.id))
|
||||
}
|
||||
prefetchedYield == null && isSupportedInMobileApp -> {
|
||||
send(StakingAvailability.TemporaryUnavailable)
|
||||
}
|
||||
else -> send(StakingAvailability.Unavailable)
|
||||
}
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkFeatureToggleEnabled(networkId: Network.ID): Boolean {
|
||||
return when (Blockchain.fromId(networkId.value)) {
|
||||
Blockchain.TON -> stakingFeatureToggles.isTonStakingEnabled
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
params: ActionParams,
|
||||
): StakingAction {
|
||||
return withContext(dispatchers.io) {
|
||||
val response = when (params.actionCommonType) {
|
||||
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) {
|
||||
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 fetchSingleYieldBalance(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
refresh: Boolean,
|
||||
) = withContext(dispatchers.io) {
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getYieldBalancesKey(userWalletId),
|
||||
skipCache = refresh,
|
||||
block = {
|
||||
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
|
||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network)
|
||||
|
||||
if (integrationId == null || address.isNullOrBlank()) {
|
||||
cacheRegistry.invalidate(getYieldBalancesKey(userWalletId))
|
||||
Timber.w(
|
||||
"IntegrationId or address is null fetching ${cryptoCurrency.name} staking balance",
|
||||
)
|
||||
return@invokeOnExpire
|
||||
}
|
||||
|
||||
val requestBody = getBalanceRequestData(address, integrationId)
|
||||
val result = stakeKitApi.getSingleYieldBalance(
|
||||
integrationId = requestBody.integrationId,
|
||||
body = requestBody,
|
||||
).getOrThrow()
|
||||
|
||||
stakingBalanceStore.store(
|
||||
userWalletId = userWalletId,
|
||||
integrationId = requestBody.integrationId,
|
||||
address = address,
|
||||
item = YieldBalanceWrapperDTO(
|
||||
balances = result,
|
||||
integrationId = requestBody.integrationId,
|
||||
addresses = requestBody.addresses,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun getSingleYieldBalanceFlow(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Flow<YieldBalance> = channelFlow {
|
||||
launch(dispatchers.io) {
|
||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
|
||||
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
|
||||
?: error("Could not get integrationId")
|
||||
stakingBalanceStore.get(userWalletId, address, integrationId)
|
||||
.distinctUntilChanged()
|
||||
.collectLatest {
|
||||
if (it != null) {
|
||||
send(it)
|
||||
} else {
|
||||
FirebaseCrashlytics.getInstance()
|
||||
.log("No yield balance available for currency ${cryptoCurrency.id.value}")
|
||||
send(YieldBalance.Error(integrationId, address))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
withContext(dispatchers.io) {
|
||||
fetchSingleYieldBalance(
|
||||
userWalletId,
|
||||
cryptoCurrency,
|
||||
)
|
||||
}
|
||||
}.cancellable()
|
||||
|
||||
override suspend fun getSingleYieldBalanceSync(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): YieldBalance = withContext(dispatchers.io) {
|
||||
fetchSingleYieldBalance(userWalletId, cryptoCurrency)
|
||||
|
||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
|
||||
|
||||
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
|
||||
?: error("Could not get integrationId")
|
||||
|
||||
stakingBalanceStore.getSyncOrNull(userWalletId, address, integrationId)
|
||||
?: YieldBalance.Error(integrationId, address)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
override suspend fun fetchMultiYieldBalance(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
refresh: Boolean,
|
||||
) = withContext(dispatchers.io) {
|
||||
if (refresh) {
|
||||
stakingBalanceStore.refresh(
|
||||
userWalletId = userWalletId,
|
||||
addressWithIntegrationIdMap = cryptoCurrencies
|
||||
.mapNotNull { currency ->
|
||||
val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network)
|
||||
val integrationId = integrationIdMap[getIntegrationKey(currency.id)]
|
||||
|
||||
if (integrationId != null) {
|
||||
addresses to integrationId
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
.flatMap { (addresses, integrationId) ->
|
||||
addresses.map { address -> integrationId to address.value }
|
||||
}
|
||||
.toMap(),
|
||||
)
|
||||
}
|
||||
|
||||
val yieldDTOs = withTimeoutOrNull(YIELDS_WATITING_TIMEOUT) {
|
||||
runCatching { stakingYieldsStore.get().firstOrNull() }.getOrNull()
|
||||
}
|
||||
|
||||
if (yieldDTOs == null) {
|
||||
Timber.i("No enabled yields for $userWalletId")
|
||||
stakingBalanceStore.store(userWalletId, emptySet())
|
||||
|
||||
return@withContext
|
||||
}
|
||||
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getYieldBalancesKey(userWalletId),
|
||||
skipCache = refresh,
|
||||
block = {
|
||||
val yields = YieldConverter.convertListIgnoreErrors(
|
||||
input = yieldDTOs,
|
||||
onError = { Timber.e("Error converting one of the items in enabled yields: $it") },
|
||||
)
|
||||
|
||||
val availableCurrencies = cryptoCurrencies
|
||||
.mapNotNull { currency ->
|
||||
val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network)
|
||||
val integrationId = integrationIdMap[getIntegrationKey(currency.id)]
|
||||
|
||||
if (integrationId != null && yields.any { it.id == integrationId }) {
|
||||
addresses to integrationId
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
.flatMap { (addresses, integrationId) ->
|
||||
addresses.map { address -> address to integrationId }
|
||||
}
|
||||
.map { getBalanceRequestData(it.first.value, it.second) }
|
||||
.ifEmpty {
|
||||
stakingBalanceStore.store(userWalletId, emptySet())
|
||||
|
||||
cacheRegistry.invalidate(getYieldBalancesKey(userWalletId))
|
||||
|
||||
return@invokeOnExpire
|
||||
}
|
||||
|
||||
val yieldBalances = safeApiCall(
|
||||
call = {
|
||||
stakeKitApi
|
||||
.getMultipleYieldBalances(availableCurrencies)
|
||||
.bind()
|
||||
},
|
||||
onError = {
|
||||
Timber.e(it, "Unable to fetch yield balances")
|
||||
cacheRegistry.invalidate(getYieldBalancesKey(userWalletId))
|
||||
emptySet()
|
||||
},
|
||||
)
|
||||
|
||||
stakingBalanceStore.store(userWalletId, yieldBalances)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun getMultiYieldBalanceUpdates(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): Flow<YieldBalanceList> {
|
||||
return stakingBalanceStore.get(userWalletId)
|
||||
.map(YieldBalanceListConverter::convert)
|
||||
.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
override fun getMultiYieldBalanceUpdatesLegacy(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): Flow<YieldBalanceList> = channelFlow {
|
||||
stakingBalanceStore.get(userWalletId)
|
||||
.onEach {
|
||||
val balances = YieldBalanceListConverter.convert(it)
|
||||
send(balances)
|
||||
}
|
||||
.launchIn(scope = this + dispatchers.io)
|
||||
|
||||
withContext(dispatchers.io) {
|
||||
fetchMultiYieldBalance(userWalletId, cryptoCurrencies, refresh = false)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getMultiYieldBalanceSync(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): YieldBalanceList = withContext(dispatchers.io) {
|
||||
fetchMultiYieldBalance(userWalletId, cryptoCurrencies)
|
||||
|
||||
stakingBalanceStore.getSyncOrNull(userWalletId)?.let(YieldBalanceListConverter::convert)
|
||||
?: YieldBalanceList.Error
|
||||
}
|
||||
|
||||
override suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean {
|
||||
return withContext(dispatchers.io) {
|
||||
stakingBalanceStore.getSyncOrNull(userWalletId)
|
||||
?.let {
|
||||
it.isNotEmpty() &&
|
||||
it.any { yieldBalance ->
|
||||
(yieldBalance as? YieldBalance.Data)?.balance?.items?.isNotEmpty() == true
|
||||
}
|
||||
}
|
||||
?: false
|
||||
}
|
||||
}
|
||||
|
||||
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 = TokenConverter.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
|
||||
}
|
||||
}
|
||||
|
||||
override fun getStakingApproval(cryptoCurrency: CryptoCurrency): StakingApproval {
|
||||
return when (getIntegrationKey(cryptoCurrency.id)) {
|
||||
Blockchain.Ethereum.id + Blockchain.Polygon.toCoinId(),
|
||||
Blockchain.Ethereum.id + Blockchain.Polygon.toMigratedCoinId(),
|
||||
-> StakingApproval.Needed(ETHEREUM_POLYGON_APPROVE_SPENDER)
|
||||
else -> StakingApproval.Empty
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
-> 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)
|
||||
}
|
||||
Blockchain.TON -> TransactionData.Compiled.Data.RawString(unsignedTransaction)
|
||||
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") },
|
||||
)
|
||||
}
|
||||
|
||||
private 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 fun getBalanceRequestData(address: String, integrationId: String): YieldBalanceRequestBody {
|
||||
return YieldBalanceRequestBody(
|
||||
addresses = Address(
|
||||
address = address,
|
||||
additionalAddresses = null, // todo fill additional addresses metadata if needed
|
||||
explorerUrl = "", // todo fill exporer url [REDACTED_JIRA]
|
||||
),
|
||||
args = YieldBalanceRequestBody.YieldBalanceRequestArgs(
|
||||
validatorAddresses = listOf(), // todo add validators [REDACTED_JIRA]
|
||||
),
|
||||
integrationId = integrationId,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getYieldBalancesKey(userWalletId: UserWalletId) = "yield_balance_${userWalletId.stringValue}"
|
||||
|
||||
private fun getTronResource(network: Network): TronResource? {
|
||||
val blockchain = Blockchain.fromNetworkId(network.backendId)
|
||||
|
||||
return if (blockchain == Blockchain.Tron || blockchain == Blockchain.TronTestnet) {
|
||||
TronResource.ENERGY
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val YIELDS_STORE_KEY = "yields"
|
||||
|
||||
const val TON_INTEGRATION_ID = "ton-ton-tonwhales-pools-staking"
|
||||
const val SOLANA_INTEGRATION_ID = "solana-sol-native-multivalidator-staking"
|
||||
const val COSMOS_INTEGRATION_ID = "cosmos-atom-native-staking"
|
||||
const val ETHEREUM_POLYGON_INTEGRATION_ID = "ethereum-matic-native-staking"
|
||||
const val BINANCE_INTEGRATION_ID = "bsc-bnb-native-staking"
|
||||
const val POLKADOT_INTEGRATION_ID = "polkadot-dot-validator-staking"
|
||||
const val AVALANCHE_INTEGRATION_ID = "avalanche-avax-native-staking"
|
||||
const val TRON_INTEGRATION_ID = "tron-trx-native-staking"
|
||||
const val CRONOS_INTEGRATION_ID = "cronos-cro-native-staking"
|
||||
const val KAVA_INTEGRATION_ID = "kava-kava-native-staking"
|
||||
const val NEAR_INTEGRATION_ID = "near-near-native-staking"
|
||||
const val TEZOS_INTEGRATION_ID = "tezos-xtz-native-staking"
|
||||
|
||||
const val ETHEREUM_POLYGON_APPROVE_SPENDER = "0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908"
|
||||
|
||||
val YIELDS_WATITING_TIMEOUT = 15.seconds
|
||||
|
||||
val INVALID_BATCHES_FOR_SOLANA = listOf("AC01", "CB79")
|
||||
|
||||
// uncomment items as implementation is ready
|
||||
val integrationIdMap = mapOf(
|
||||
Blockchain.TON.run { id + toCoinId() } to TON_INTEGRATION_ID,
|
||||
Blockchain.Solana.run { id + toCoinId() } to SOLANA_INTEGRATION_ID,
|
||||
Blockchain.Cosmos.run { id + toCoinId() } to COSMOS_INTEGRATION_ID,
|
||||
Blockchain.Tron.run { id + toCoinId() } to TRON_INTEGRATION_ID,
|
||||
Blockchain.Ethereum.id + Blockchain.Polygon.toMigratedCoinId() to ETHEREUM_POLYGON_INTEGRATION_ID,
|
||||
// Blockchain.Ethereum.id + Blockchain.Polygon.toCoinId() to ETHEREUM_POLYGON_INTEGRATION_ID,
|
||||
Blockchain.BSC.run { id + toCoinId() } to BINANCE_INTEGRATION_ID,
|
||||
// Blockchain.Polkadot.run { id + toCoinId() } to POLKADOT_INTEGRATION_ID,
|
||||
// Blockchain.Avalanche.run { id + toCoinId() } to AVALANCHE_INTEGRATION_ID,
|
||||
// Blockchain.Cronos.run { id + toCoinId() } to CRONOS_INTEGRATION_ID,
|
||||
// Blockchain.Kava.run { id + toCoinId() } to KAVA_INTEGRATION_ID,
|
||||
// Blockchain.Near.run { id + toCoinId() } to NEAR_INTEGRATION_ID,
|
||||
// Blockchain.Tezos.run { id + toCoinId() } to TEZOS_INTEGRATION_ID,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package com.tangem.data.staking
|
||||
|
||||
import com.tangem.datasource.api.stakekit.StakeKitApi
|
||||
import com.tangem.datasource.api.stakekit.models.request.*
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectListSync
|
||||
import com.tangem.domain.staking.model.UnsubmittedTransactionMetadata
|
||||
import com.tangem.domain.staking.repositories.StakingTransactionHashRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
internal class DefaultStakingTransactionHashRepository(
|
||||
private val stakeKitApi: StakeKitApi,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : StakingTransactionHashRepository {
|
||||
|
||||
override suspend fun submitHash(transactionId: String, transactionHash: String) {
|
||||
withContext(dispatchers.io) {
|
||||
stakeKitApi.submitTransactionHash(
|
||||
transactionId = transactionId,
|
||||
body = SubmitTransactionHashRequestBody(
|
||||
hash = transactionHash,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun storeUnsubmittedHash(unsubmittedTransactionMetadata: UnsubmittedTransactionMetadata) {
|
||||
withContext(dispatchers.io) {
|
||||
appPreferencesStore.editData { preferences ->
|
||||
val savedTransactions = preferences.getObjectListOrDefault<UnsubmittedTransactionMetadata>(
|
||||
key = PreferencesKeys.UNSUBMITTED_TRANSACTIONS_KEY,
|
||||
default = emptyList(),
|
||||
)
|
||||
|
||||
preferences.setObjectList(
|
||||
key = PreferencesKeys.UNSUBMITTED_TRANSACTIONS_KEY,
|
||||
value = savedTransactions + unsubmittedTransactionMetadata,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendUnsubmittedHashes() {
|
||||
withContext(dispatchers.io) {
|
||||
val savedTransactions = appPreferencesStore.getObjectListSync<UnsubmittedTransactionMetadata>(
|
||||
key = PreferencesKeys.UNSUBMITTED_TRANSACTIONS_KEY,
|
||||
)
|
||||
|
||||
savedTransactions.forEach { transaction ->
|
||||
try {
|
||||
stakeKitApi.submitTransactionHash(
|
||||
transactionId = transaction.transactionId,
|
||||
body = SubmitTransactionHashRequestBody(hash = transaction.transactionHash),
|
||||
)
|
||||
|
||||
appPreferencesStore.editData { mutablePreferences ->
|
||||
val updatedTransactions =
|
||||
mutablePreferences.getObjectListOrDefault<UnsubmittedTransactionMetadata>(
|
||||
key = PreferencesKeys.UNSUBMITTED_TRANSACTIONS_KEY,
|
||||
default = emptyList(),
|
||||
).filterNot { it.transactionId == transaction.transactionId }
|
||||
|
||||
mutablePreferences.setObjectList(
|
||||
key = PreferencesKeys.UNSUBMITTED_TRANSACTIONS_KEY,
|
||||
value = updatedTransactions,
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
val logMessage = buildString {
|
||||
append("Error while submitting transaction with\n")
|
||||
append("StakeKit id = ${transaction.transactionId} and\n")
|
||||
append("transaction hash = ${transaction.transactionHash}")
|
||||
}
|
||||
Timber.e(logMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.data.staking.converters
|
||||
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalanceList
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object YieldBalanceListConverter : Converter<Set<YieldBalance>, YieldBalanceList> {
|
||||
|
||||
override fun convert(value: Set<YieldBalance>): YieldBalanceList {
|
||||
return if (value.isEmpty()) {
|
||||
YieldBalanceList.Empty
|
||||
} else {
|
||||
YieldBalanceList.Data(balances = value.toList())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
package com.tangem.data.staking.converters
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.AddressArgumentDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.MetadataDTO.RewardScheduleDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.ValidatorDTO.ValidatorStatusDTO
|
||||
import com.tangem.datasource.local.token.converter.TokenConverter
|
||||
import com.tangem.domain.staking.model.stakekit.AddressArgument
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.Yield.Metadata.RewardSchedule
|
||||
import com.tangem.domain.staking.model.stakekit.Yield.Validator.ValidatorStatus
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal object YieldConverter : Converter<YieldDTO, Yield> {
|
||||
|
||||
private val PARTNERS = listOf(
|
||||
"cosmosvaloper1wrx0x9m9ykdhw9sg04v7uljme53wuj03aa5d4f",
|
||||
"H2tJNyMHnRF6ahCQLQ1sSycM4FGchymuzyYzUqKEuydk",
|
||||
)
|
||||
|
||||
private val PARTNERS_NAMES = listOf("Meria")
|
||||
|
||||
override fun convert(value: YieldDTO): Yield {
|
||||
return Yield(
|
||||
id = value.id.asMandatory("id"),
|
||||
token = TokenConverter.convert(value.token.asMandatory("token")),
|
||||
tokens = value.tokens.asMandatory("tokens").map(TokenConverter::convert),
|
||||
args = convertArgs(value.args.asMandatory("args")),
|
||||
status = convertStatus(value.status.asMandatory("status")),
|
||||
apy = value.apy.asMandatory("apy"),
|
||||
rewardRate = value.rewardRate.asMandatory("rewardRate"),
|
||||
rewardType = convertRewardType(value.rewardType.asMandatory("rewardType")),
|
||||
metadata = convertMetadata(value.metadata.asMandatory("metadata")),
|
||||
validators = value.validators.asMandatory("validators")
|
||||
.asSequence()
|
||||
.distinctBy { it.address }
|
||||
.filter { it.status == ValidatorStatusDTO.ACTIVE }
|
||||
.map { convertValidator(it) }
|
||||
.sortedByDescending { it.apr }
|
||||
.sortedByDescending { it.isStrategicPartner }
|
||||
.toImmutableList(),
|
||||
isAvailable = value.isAvailable.asMandatory("isAvailable"),
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertArgs(argsDTO: YieldDTO.ArgsDTO): Yield.Args {
|
||||
return Yield.Args(
|
||||
enter = convertEnter(argsDTO.enter.asMandatory("enter")),
|
||||
exit = argsDTO.exit?.let { convertEnter(it) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertEnter(enterDTO: YieldDTO.ArgsDTO.Enter): Yield.Args.Enter {
|
||||
return Yield.Args.Enter(
|
||||
addresses = convertAddresses(enterDTO.addresses.asMandatory("addresses")),
|
||||
args = enterDTO.args.asMandatory("args")
|
||||
.mapKeys { convertArgType(it.key) }
|
||||
.mapValues { convertAddressArgument(it.value) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertAddresses(addressesDTO: YieldDTO.ArgsDTO.Enter.Addresses): Yield.Args.Enter.Addresses {
|
||||
return Yield.Args.Enter.Addresses(
|
||||
address = convertAddressArgument(addressesDTO.address.asMandatory("address")),
|
||||
additionalAddresses = addressesDTO.additionalAddresses
|
||||
?.mapKeys { convertArgType(it.key) }
|
||||
?.mapValues { convertAddressArgument(it.value) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertAddressArgument(addressArgumentDTO: AddressArgumentDTO): AddressArgument {
|
||||
return AddressArgument(
|
||||
required = addressArgumentDTO.required,
|
||||
network = addressArgumentDTO.network,
|
||||
minimum = addressArgumentDTO.minimum,
|
||||
maximum = addressArgumentDTO.maximum,
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertStatus(statusDTO: YieldDTO.StatusDTO): Yield.Status {
|
||||
return Yield.Status(
|
||||
enter = statusDTO.enter.asMandatory("enter"),
|
||||
exit = statusDTO.exit,
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertMetadata(metadataDTO: YieldDTO.MetadataDTO): Yield.Metadata {
|
||||
return Yield.Metadata(
|
||||
name = metadataDTO.name.asMandatory("name"),
|
||||
logoUri = metadataDTO.logoUri.asMandatory("logoUri"),
|
||||
description = metadataDTO.description.asMandatory("description"),
|
||||
documentation = metadataDTO.documentation,
|
||||
gasFeeToken = TokenConverter.convert(metadataDTO.gasFeeTokenDTO.asMandatory("gasFeeTokenDTO")),
|
||||
token = TokenConverter.convert(metadataDTO.tokenDTO.asMandatory("tokenDTO")),
|
||||
tokens = metadataDTO.tokensDTO.asMandatory("tokensDTO").map(TokenConverter::convert),
|
||||
type = metadataDTO.type.asMandatory("type"),
|
||||
rewardSchedule = convertRewardSchedule(metadataDTO.rewardSchedule.asMandatory("rewardSchedule")),
|
||||
cooldownPeriod = metadataDTO.cooldownPeriod?.let { convertPeriod(it) },
|
||||
warmupPeriod = convertPeriod(metadataDTO.warmupPeriod.asMandatory("warmupPeriod")),
|
||||
rewardClaiming = convertRewardClaiming(metadataDTO.rewardClaiming.asMandatory("rewardClaiming")),
|
||||
defaultValidator = metadataDTO.defaultValidator,
|
||||
minimumStake = metadataDTO.minimumStake,
|
||||
supportsMultipleValidators = metadataDTO.supportsMultipleValidators,
|
||||
revshare = convertEnabled(metadataDTO.revshare.asMandatory("revshare")),
|
||||
fee = convertEnabled(metadataDTO.fee.asMandatory("fee")),
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertPeriod(periodDTO: YieldDTO.MetadataDTO.PeriodDTO): Yield.Metadata.Period {
|
||||
return Yield.Metadata.Period(
|
||||
days = periodDTO.days.asMandatory("days"),
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertEnabled(enabledDTO: YieldDTO.MetadataDTO.EnabledDTO): Yield.Metadata.Enabled {
|
||||
return Yield.Metadata.Enabled(
|
||||
enabled = enabledDTO.enabled.asMandatory("enabled"),
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertValidator(validatorDTO: YieldDTO.ValidatorDTO): Yield.Validator {
|
||||
val address = validatorDTO.address.asMandatory("address")
|
||||
return Yield.Validator(
|
||||
address = address,
|
||||
status = convertValidatorStatus(validatorDTO.status.asMandatory("status")),
|
||||
name = validatorDTO.name.asMandatory("name"),
|
||||
image = validatorDTO.image,
|
||||
website = validatorDTO.website,
|
||||
apr = validatorDTO.apr,
|
||||
commission = validatorDTO.commission,
|
||||
stakedBalance = validatorDTO.stakedBalance,
|
||||
votingPower = validatorDTO.votingPower,
|
||||
preferred = validatorDTO.preferred.asMandatory("preferred"),
|
||||
isStrategicPartner = isStrategicPartner(validatorDTO.address, validatorDTO.name.asMandatory("name")),
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertRewardType(rewardTypeDTO: YieldDTO.RewardTypeDTO): Yield.RewardType {
|
||||
return when (rewardTypeDTO) {
|
||||
YieldDTO.RewardTypeDTO.APY -> Yield.RewardType.APY
|
||||
YieldDTO.RewardTypeDTO.APR -> Yield.RewardType.APR
|
||||
else -> Yield.RewardType.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertValidatorStatus(validatorStatusDTO: ValidatorStatusDTO): ValidatorStatus {
|
||||
return when (validatorStatusDTO) {
|
||||
ValidatorStatusDTO.ACTIVE -> ValidatorStatus.ACTIVE
|
||||
ValidatorStatusDTO.DEACTIVATING -> ValidatorStatus.DEACTIVATING
|
||||
ValidatorStatusDTO.INACTIVE -> ValidatorStatus.INACTIVE
|
||||
ValidatorStatusDTO.JAILED -> ValidatorStatus.JAILED
|
||||
ValidatorStatusDTO.FULL -> ValidatorStatus.FULL
|
||||
else -> ValidatorStatus.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertRewardSchedule(rewardTypeDTO: RewardScheduleDTO): RewardSchedule {
|
||||
return when (rewardTypeDTO) {
|
||||
RewardScheduleDTO.BLOCK -> RewardSchedule.BLOCK
|
||||
RewardScheduleDTO.WEEK -> RewardSchedule.WEEK
|
||||
RewardScheduleDTO.HOUR -> RewardSchedule.HOUR
|
||||
RewardScheduleDTO.DAY -> RewardSchedule.DAY
|
||||
RewardScheduleDTO.MONTH -> RewardSchedule.MONTH
|
||||
RewardScheduleDTO.ERA -> RewardSchedule.ERA
|
||||
RewardScheduleDTO.EPOCH -> RewardSchedule.EPOCH
|
||||
else -> RewardSchedule.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertRewardClaiming(
|
||||
rewardClaimingDTO: YieldDTO.MetadataDTO.RewardClaimingDTO,
|
||||
): Yield.Metadata.RewardClaiming {
|
||||
return when (rewardClaimingDTO) {
|
||||
YieldDTO.MetadataDTO.RewardClaimingDTO.AUTO -> Yield.Metadata.RewardClaiming.AUTO
|
||||
YieldDTO.MetadataDTO.RewardClaimingDTO.MANUAL -> Yield.Metadata.RewardClaiming.MANUAL
|
||||
else -> Yield.Metadata.RewardClaiming.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertArgType(value: String): Yield.Args.ArgType {
|
||||
return when (value) {
|
||||
"address" -> Yield.Args.ArgType.ADDRESS
|
||||
"amount" -> Yield.Args.ArgType.AMOUNT
|
||||
else -> Yield.Args.ArgType.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
private fun isStrategicPartner(validatorAddress: String?, validatorName: String): Boolean {
|
||||
return PARTNERS.any { it == validatorAddress } || PARTNERS_NAMES.any { it.equals(validatorName, true) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.data.staking.converters.action
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
|
||||
class ActionStatusConverter : TwoWayConverter<StakingActionStatusDTO, StakingActionStatus> {
|
||||
|
||||
override fun convert(value: StakingActionStatusDTO): StakingActionStatus {
|
||||
return when (value) {
|
||||
StakingActionStatusDTO.CANCELED -> StakingActionStatus.CANCELED
|
||||
StakingActionStatusDTO.CREATED -> StakingActionStatus.CREATED
|
||||
StakingActionStatusDTO.WAITING_FOR_NEXT -> StakingActionStatus.WAITING_FOR_NEXT
|
||||
StakingActionStatusDTO.PROCESSING -> StakingActionStatus.PROCESSING
|
||||
StakingActionStatusDTO.FAILED -> StakingActionStatus.FAILED
|
||||
StakingActionStatusDTO.SUCCESS -> StakingActionStatus.SUCCESS
|
||||
else -> StakingActionStatus.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
override fun convertBack(value: StakingActionStatus): StakingActionStatusDTO {
|
||||
return when (value) {
|
||||
StakingActionStatus.CANCELED -> StakingActionStatusDTO.CANCELED
|
||||
StakingActionStatus.CREATED -> StakingActionStatusDTO.CREATED
|
||||
StakingActionStatus.WAITING_FOR_NEXT -> StakingActionStatusDTO.WAITING_FOR_NEXT
|
||||
StakingActionStatus.PROCESSING -> StakingActionStatusDTO.PROCESSING
|
||||
StakingActionStatus.FAILED -> StakingActionStatusDTO.FAILED
|
||||
StakingActionStatus.SUCCESS -> StakingActionStatusDTO.SUCCESS
|
||||
else -> StakingActionStatusDTO.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.data.staking.converters.action
|
||||
|
||||
import com.tangem.data.staking.converters.transaction.StakingTransactionConverter
|
||||
import com.tangem.datasource.api.stakekit.models.response.ActionDTO
|
||||
import com.tangem.datasource.local.token.converter.StakingActionTypeConverter
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingAction
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
class EnterActionResponseConverter(
|
||||
private val actionStatusConverter: ActionStatusConverter,
|
||||
private val transactionConverter: StakingTransactionConverter,
|
||||
) : Converter<ActionDTO, StakingAction> {
|
||||
|
||||
override fun convert(value: ActionDTO): StakingAction {
|
||||
return StakingAction(
|
||||
id = value.id,
|
||||
integrationId = value.integrationId,
|
||||
status = actionStatusConverter.convert(value.status),
|
||||
type = StakingActionTypeConverter.convert(value.type),
|
||||
currentStepIndex = value.currentStepIndex,
|
||||
amount = value.amount,
|
||||
validatorAddress = value.validatorAddress,
|
||||
validatorAddresses = value.validatorAddresses,
|
||||
transactions = value.transactions?.map(transactionConverter::convert),
|
||||
createdAt = value.createdAt,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.data.staking.converters.error
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.error.StakeKitErrorResponse
|
||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class StakeKitErrorConverter(
|
||||
private val jsonAdapter: JsonAdapter<StakeKitErrorResponse>,
|
||||
) : Converter<String, StakingError> {
|
||||
|
||||
override fun convert(value: String): StakingError {
|
||||
return try {
|
||||
val stakeKitErrorResponse = jsonAdapter.fromJson(value)
|
||||
?: return StakingError.StakeKitUnknownError(value)
|
||||
|
||||
return StakingError.StakeKitApiError(
|
||||
message = stakeKitErrorResponse.message,
|
||||
code = stakeKitErrorResponse.code,
|
||||
methodName = stakeKitErrorResponse.path,
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
StakingError.StakeKitUnknownError(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.data.staking.converters.transaction
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingGasEstimateDTO
|
||||
import com.tangem.datasource.local.token.converter.TokenConverter
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object GasEstimateConverter : Converter<StakingGasEstimateDTO, StakingGasEstimate> {
|
||||
|
||||
override fun convert(value: StakingGasEstimateDTO): StakingGasEstimate {
|
||||
return StakingGasEstimate(
|
||||
amount = value.amount,
|
||||
token = TokenConverter.convert(value.token),
|
||||
gasLimit = value.gasLimit,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.data.staking.converters.transaction
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionDTO
|
||||
import com.tangem.datasource.local.token.converter.StakingNetworkTypeConverter
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
class StakingTransactionConverter(
|
||||
private val transactionStatusConverter: StakingTransactionStatusConverter,
|
||||
private val transactionTypeConverter: StakingTransactionTypeConverter,
|
||||
) : Converter<StakingTransactionDTO, StakingTransaction> {
|
||||
|
||||
override fun convert(value: StakingTransactionDTO): StakingTransaction {
|
||||
return StakingTransaction(
|
||||
id = value.id,
|
||||
network = StakingNetworkTypeConverter.convert(value.network),
|
||||
status = transactionStatusConverter.convert(value.status),
|
||||
type = transactionTypeConverter.convert(value.type),
|
||||
hash = value.hash,
|
||||
signedTransaction = value.signedTransaction,
|
||||
unsignedTransaction = value.unsignedTransaction,
|
||||
stepIndex = value.stepIndex,
|
||||
error = value.error,
|
||||
gasEstimate = value.gasEstimate?.let(GasEstimateConverter::convert),
|
||||
stakeId = value.stakeId,
|
||||
explorerUrl = value.explorerUrl,
|
||||
ledgerHwAppId = value.ledgerHwAppId,
|
||||
isMessage = value.isMessage,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.data.staking.converters.transaction
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionStatusDTO
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionStatus
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
class StakingTransactionStatusConverter : Converter<StakingTransactionStatusDTO, StakingTransactionStatus> {
|
||||
|
||||
override fun convert(value: StakingTransactionStatusDTO): StakingTransactionStatus {
|
||||
return when (value) {
|
||||
StakingTransactionStatusDTO.NOT_FOUND -> StakingTransactionStatus.NOT_FOUND
|
||||
StakingTransactionStatusDTO.CREATED -> StakingTransactionStatus.CREATED
|
||||
StakingTransactionStatusDTO.BLOCKED -> StakingTransactionStatus.BLOCKED
|
||||
StakingTransactionStatusDTO.WAITING_FOR_SIGNATURE -> StakingTransactionStatus.WAITING_FOR_SIGNATURE
|
||||
StakingTransactionStatusDTO.SIGNED -> StakingTransactionStatus.SIGNED
|
||||
StakingTransactionStatusDTO.BROADCASTED -> StakingTransactionStatus.BROADCASTED
|
||||
StakingTransactionStatusDTO.PENDING -> StakingTransactionStatus.PENDING
|
||||
StakingTransactionStatusDTO.CONFIRMED -> StakingTransactionStatus.CONFIRMED
|
||||
StakingTransactionStatusDTO.FAILED -> StakingTransactionStatus.FAILED
|
||||
StakingTransactionStatusDTO.SKIPPED -> StakingTransactionStatus.SKIPPED
|
||||
StakingTransactionStatusDTO.UNKNOWN -> StakingTransactionStatus.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.data.staking.converters.transaction
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionTypeDTO
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionType
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
class StakingTransactionTypeConverter : Converter<StakingTransactionTypeDTO, StakingTransactionType> {
|
||||
|
||||
override fun convert(value: StakingTransactionTypeDTO): StakingTransactionType {
|
||||
return when (value) {
|
||||
StakingTransactionTypeDTO.SWAP -> StakingTransactionType.SWAP
|
||||
StakingTransactionTypeDTO.DEPOSIT -> StakingTransactionType.DEPOSIT
|
||||
StakingTransactionTypeDTO.APPROVAL -> StakingTransactionType.APPROVAL
|
||||
StakingTransactionTypeDTO.STAKE -> StakingTransactionType.STAKE
|
||||
StakingTransactionTypeDTO.CLAIM_UNSTAKED -> StakingTransactionType.CLAIM_UNSTAKED
|
||||
StakingTransactionTypeDTO.CLAIM_REWARDS -> StakingTransactionType.CLAIM_REWARDS
|
||||
StakingTransactionTypeDTO.RESTAKE_REWARDS -> StakingTransactionType.RESTAKE_REWARDS
|
||||
StakingTransactionTypeDTO.UNSTAKE -> StakingTransactionType.UNSTAKE
|
||||
StakingTransactionTypeDTO.SPLIT -> StakingTransactionType.SPLIT
|
||||
StakingTransactionTypeDTO.MERGE -> StakingTransactionType.MERGE
|
||||
StakingTransactionTypeDTO.LOCK -> StakingTransactionType.LOCK
|
||||
StakingTransactionTypeDTO.UNLOCK -> StakingTransactionType.UNLOCK
|
||||
StakingTransactionTypeDTO.SUPPLY -> StakingTransactionType.SUPPLY
|
||||
StakingTransactionTypeDTO.BRIDGE -> StakingTransactionType.BRIDGE
|
||||
StakingTransactionTypeDTO.VOTE -> StakingTransactionType.VOTE
|
||||
StakingTransactionTypeDTO.REVOKE -> StakingTransactionType.REVOKE
|
||||
StakingTransactionTypeDTO.RESTAKE -> StakingTransactionType.RESTAKE
|
||||
StakingTransactionTypeDTO.REBOND -> StakingTransactionType.REBOND
|
||||
StakingTransactionTypeDTO.WITHDRAW -> StakingTransactionType.WITHDRAW
|
||||
StakingTransactionTypeDTO.CREATE_ACCOUNT -> StakingTransactionType.CREATE_ACCOUNT
|
||||
StakingTransactionTypeDTO.REVEAL -> StakingTransactionType.REVEAL
|
||||
StakingTransactionTypeDTO.MIGRATE -> StakingTransactionType.MIGRATE
|
||||
StakingTransactionTypeDTO.UTXO_P_TO_C_IMPORT -> StakingTransactionType.UTXO_P_TO_C_IMPORT
|
||||
StakingTransactionTypeDTO.UTXO_C_TO_P_IMPORT -> StakingTransactionType.UTXO_C_TO_P_IMPORT
|
||||
StakingTransactionTypeDTO.UNFREEZE_LEGACY -> StakingTransactionType.UNFREEZE_LEGACY
|
||||
StakingTransactionTypeDTO.UNFREEZE_LEGACY_BANDWIDTH -> StakingTransactionType.UNFREEZE_LEGACY_BANDWIDTH
|
||||
StakingTransactionTypeDTO.UNFREEZE_LEGACY_ENERGY -> StakingTransactionType.UNFREEZE_LEGACY_ENERGY
|
||||
StakingTransactionTypeDTO.UNFREEZE_BANDWIDTH -> StakingTransactionType.UNFREEZE_BANDWIDTH
|
||||
StakingTransactionTypeDTO.UNFREEZE_ENERGY -> StakingTransactionType.UNFREEZE_ENERGY
|
||||
StakingTransactionTypeDTO.FREEZE_BANDWIDTH -> StakingTransactionType.FREEZE_BANDWIDTH
|
||||
StakingTransactionTypeDTO.FREEZE_ENERGY -> StakingTransactionType.FREEZE_ENERGY
|
||||
StakingTransactionTypeDTO.UNDELEGATE_BANDWIDTH -> StakingTransactionType.UNDELEGATE_BANDWIDTH
|
||||
StakingTransactionTypeDTO.UNDELEGATE_ENERGY -> StakingTransactionType.UNDELEGATE_ENERGY
|
||||
StakingTransactionTypeDTO.P2P_NODE_REQUEST -> StakingTransactionType.P2P_NODE_REQUEST
|
||||
StakingTransactionTypeDTO.LUGANODES_PROVISION -> StakingTransactionType.LUGANODES_PROVISION
|
||||
StakingTransactionTypeDTO.LUGANODES_EXIT_REQUEST -> StakingTransactionType.LUGANODES_EXIT_REQUEST
|
||||
StakingTransactionTypeDTO.INFSTONES_PROVISION -> StakingTransactionType.INFSTONES_PROVISION
|
||||
StakingTransactionTypeDTO.INFSTONES_EXIT_REQUEST -> StakingTransactionType.INFSTONES_EXIT_REQUEST
|
||||
StakingTransactionTypeDTO.INFSTONES_CLAIM_REQUEST -> StakingTransactionType.INFSTONES_CLAIM_REQUEST
|
||||
StakingTransactionTypeDTO.UNKNOWN -> StakingTransactionType.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
package com.tangem.data.staking.di
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.staking.DefaultStakingActionRepository
|
||||
import com.tangem.data.staking.DefaultStakingErrorResolver
|
||||
import com.tangem.data.staking.DefaultStakingRepository
|
||||
import com.tangem.data.staking.DefaultStakingTransactionHashRepository
|
||||
import com.tangem.data.staking.converters.error.StakeKitErrorConverter
|
||||
import com.tangem.data.staking.toggles.DefaultStakingFeatureToggles
|
||||
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.StakingActionsStore
|
||||
import com.tangem.datasource.local.token.StakingBalanceStore
|
||||
import com.tangem.datasource.local.token.StakingYieldsStore
|
||||
import com.tangem.domain.staking.repositories.StakingActionRepository
|
||||
import com.tangem.domain.staking.repositories.StakingErrorResolver
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.staking.repositories.StakingTransactionHashRepository
|
||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object StakingDataModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStakingRepository(
|
||||
stakeKitApi: StakeKitApi,
|
||||
stakingYieldsStore: StakingYieldsStore,
|
||||
stakingBalanceStore: StakingBalanceStore,
|
||||
cacheRegistry: CacheRegistry,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
getUserWalletUseCase: GetUserWalletUseCase,
|
||||
stakingFeatureToggles: StakingFeatureToggles,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
): StakingRepository {
|
||||
return DefaultStakingRepository(
|
||||
stakeKitApi = stakeKitApi,
|
||||
stakingYieldsStore = stakingYieldsStore,
|
||||
stakingBalanceStore = stakingBalanceStore,
|
||||
cacheRegistry = cacheRegistry,
|
||||
dispatchers = dispatchers,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
getUserWalletUseCase = getUserWalletUseCase,
|
||||
stakingFeatureToggles = stakingFeatureToggles,
|
||||
moshi = moshi,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStakingTransactionHashRepository(
|
||||
stakeKitApi: StakeKitApi,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): StakingTransactionHashRepository {
|
||||
return DefaultStakingTransactionHashRepository(
|
||||
stakeKitApi = stakeKitApi,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStakingActionRepository(
|
||||
stakingActionsStore: StakingActionsStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): StakingActionRepository {
|
||||
return DefaultStakingActionRepository(
|
||||
stakingActionsStore = stakingActionsStore,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun provideStakingErrorResolver(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
analyticsEventHandler: AnalyticsEventHandler,
|
||||
): StakingErrorResolver {
|
||||
val jsonAdapter = moshi.adapter(StakeKitErrorResponse::class.java)
|
||||
return DefaultStakingErrorResolver(
|
||||
stakeKitErrorConverter = StakeKitErrorConverter(jsonAdapter),
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun provideFeatureToggles(featureTogglesManager: FeatureTogglesManager): StakingFeatureToggles {
|
||||
return DefaultStakingFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.data.staking.toggles
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||
|
||||
internal class DefaultStakingFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : StakingFeatureToggles {
|
||||
|
||||
override val isTonStakingEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "STAKING_TON_ENABLED")
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue