Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-28 11:28:21 +02:00
parent a08f570a34
commit 72ea156756
18 changed files with 276 additions and 590 deletions

View file

@ -179,6 +179,7 @@ sealed class AnalyticsParam {
const val ERROR_CODE = "Error Code"
const val ERROR_KEY = "Error Key"
const val ERROR_TYPE = "Error Type"
const val ERROR_MESSAGE = "Error Message"
const val CREATION_TYPE = "Creation type"
const val DAPP_NAME = "DApp Name"
const val DAPP_URL = "DApp Url"

View file

@ -2,6 +2,7 @@ package com.tangem.core.analytics.di
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.api.ParamsInterceptorHolder
import com.tangem.core.analytics.filter.OneTimeEventFilter
import com.tangem.domain.analytics.repository.AnalyticsRepository
import dagger.Module
@ -20,6 +21,12 @@ internal object AnalyticsModule {
return Analytics // todo replace after refactoring calling Analytics in whole project
}
@Singleton
@Provides
fun provideParamsInterceptorHolder(): ParamsInterceptorHolder {
return Analytics // todo replace after refactoring calling Analytics in whole project
}
@Provides
fun provideOneTimeEventFilter(analyticsRepository: AnalyticsRepository): OneTimeEventFilter {
return OneTimeEventFilter(analyticsRepository)

View file

@ -11,8 +11,6 @@ import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.RewardT
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.ValidatorDTO.ValidatorStatusDTO
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionTypeDTO
import com.tangem.datasource.api.stakekit.models.response.model.error.AccessDeniedErrorTypeDTO
import com.tangem.datasource.api.stakekit.models.response.model.error.StakeKitErrorMessageDTO
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionStatusDTO
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionTypeDTO
@ -40,9 +38,6 @@ fun Moshi.Builder.addStakeKitEnumFallbackAdapters(): Moshi.Builder {
StakingTransactionStatusDTO::class.java to StakingTransactionStatusDTO.UNKNOWN,
StakingTransactionTypeDTO::class.java to StakingTransactionTypeDTO.UNKNOWN,
ValidatorStatusDTO::class.java to ValidatorStatusDTO.UNKNOWN,
// error enums
AccessDeniedErrorTypeDTO::class.java to AccessDeniedErrorTypeDTO.UNKNOWN,
StakeKitErrorMessageDTO::class.java to StakeKitErrorMessageDTO.UNKNOWN,
)
return apply {

View file

@ -3,18 +3,21 @@ package com.tangem.datasource.api.stakekit.models.response.model.error
import com.squareup.moshi.Json
class StakeKitErrorResponse(
@Json(name = "message")
val message: String? = null,
@Json(name = "code")
val code: Int? = null,
@Json(name = "path")
val path: String? = null,
@Json(name = "details")
val details: StakeKitErrorDetailsDTO? = null,
@Json(name = "message")
val message: StakeKitErrorMessageDTO? = null,
@Json(name = "level")
val level: String? = null, // unused
// 403
@Json(name = "type")
val type: AccessDeniedErrorTypeDTO? = null,
@Json(name = "code")
val code: String? = null,
val type: String? = null,
@Json(name = "countryCode")
val countryCode: String?,
@Json(name = "regionCode")
@ -30,135 +33,4 @@ data class StakeKitErrorDetailsDTO(
val amount: String? = null,
@Json(name = "yieldId")
val yieldId: String? = null,
)
enum class AccessDeniedErrorTypeDTO {
@Json(name = "GEO_LOCATION")
GEO_LOCATION,
UNKNOWN,
}
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,
UNKNOWN,
}
)

View file

@ -1,19 +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 {
return if (throwable is ApiResponseError.HttpException) {
val error = if (throwable is ApiResponseError.HttpException) {
stakeKitErrorConverter.convert(throwable.errorBody.orEmpty())
} else {
StakingError.UnknownError()
StakingError.DomainError(throwable.message)
}
when (error) {
is StakingError.StakeKitApiError -> {
analyticsEventHandler.send(StakingAnalyticsEvent.StakeKitApiError(error))
}
is StakingError.StakeKitUnknownError -> {
analyticsEventHandler.send(StakingAnalyticsEvent.StakeKitApiUnknownError(error))
}
else -> {
// intentionally do nothing
}
}
return error
}
}

View file

@ -1,8 +1,6 @@
package com.tangem.data.staking.converters.error
import com.squareup.moshi.JsonAdapter
import com.tangem.datasource.api.stakekit.models.response.model.error.AccessDeniedErrorTypeDTO
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.stakekit.StakingError
import com.tangem.utils.converter.Converter
@ -11,105 +9,18 @@ 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(value)
val stakeKitErrorResponse = jsonAdapter.fromJson(value)
?: return StakingError.StakeKitUnknownError(value)
if (stakeKitErrorResponse.type == AccessDeniedErrorTypeDTO.GEO_LOCATION) {
StakingError.UnavailableDueToGeolocationError(
tags = stakeKitErrorResponse.tags ?: emptyList(),
)
}
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(value)
}
return StakingError.StakeKitApiError(
message = stakeKitErrorResponse.message,
code = stakeKitErrorResponse.code,
methodName = stakeKitErrorResponse.path,
)
} catch (e: Exception) {
StakingError.UnknownError(value)
StakingError.StakeKitUnknownError(value)
}
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.data.staking.di
import com.squareup.moshi.Moshi
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.staking.DefaultStakingErrorResolver
import com.tangem.data.staking.DefaultStakingPendingTransactionRepository
@ -79,10 +80,14 @@ internal object StakingDataModule {
@Provides
@Singleton
internal fun provideStakingErrorResolver(@NetworkMoshi moshi: Moshi): StakingErrorResolver {
internal fun provideStakingErrorResolver(
@NetworkMoshi moshi: Moshi,
analyticsEventHandler: AnalyticsEventHandler,
): StakingErrorResolver {
val jsonAdapter = moshi.adapter(StakeKitErrorResponse::class.java)
return DefaultStakingErrorResolver(
stakeKitErrorConverter = StakeKitErrorConverter(jsonAdapter),
analyticsEventHandler = analyticsEventHandler,
)
}
}

View file

@ -12,6 +12,7 @@ android {
dependencies {
api(projects.domain.staking.models)
api(projects.domain.core)
api(projects.core.analytics)
implementation(deps.kotlin.serialization)
implementation(deps.jodatime)

View file

@ -4,93 +4,17 @@ sealed class StakingError {
// region stakekit errors
data class MinimumAmountNotReachedError(val amount: String) : StakingError()
data class StakeKitApiError(
val message: String?,
val code: Int?,
val methodName: 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()
data class UnavailableDueToGeolocationError(
val tags: List<String>,
data class StakeKitUnknownError(
val jsonString: String? = null,
) : StakingError()
// endregion
data class DataError(val cause: Throwable) : StakingError()
data class UnknownError(val message: String? = null) : StakingError()
data class DomainError(val message: String?) : StakingError()
}

View file

@ -0,0 +1,152 @@
package com.tangem.domain.staking.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.domain.staking.analytics.StakingAnalyticsEvent.ButtonRewards.addIfValueIsNotNull
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
sealed class StakingAnalyticsEvent(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent(
category = "Staking",
event = event,
params = params,
) {
data class StakingInfoScreenOpened(
val validatorsCount: Int,
) : StakingAnalyticsEvent(
event = "Staking Info Screen Opened",
params = mapOf(
"Validators Count" to validatorsCount.toString(),
),
)
data object WhatIsStaking : StakingAnalyticsEvent(
event = "Link - What Is Staking",
)
data object AmountScreenOpened : StakingAnalyticsEvent(
event = "Amount Screen Opened",
)
data class ConfirmationScreenOpened(
val validator: String,
val action: StakingActionType,
) : StakingAnalyticsEvent(
event = "Confirmation Screen Opened",
params = mapOf(
"Validator" to validator,
"Action" to action.asAnalyticName,
),
)
data class StakeInProgressScreenOpened(
val validator: String,
val action: StakingActionType,
) : StakingAnalyticsEvent(
event = "Stake In Progress Screen Opened",
params = mapOf(
"Validator" to validator,
"Action" to action.asAnalyticName,
),
)
data object RewardScreenOpened : StakingAnalyticsEvent(
event = "Reward Screen Opened",
)
data class AmountSelectCurrency(
val isAppCurrency: Boolean,
) : StakingAnalyticsEvent(
event = "Selected Currency",
params = mapOf(
AnalyticsParam.TYPE to if (isAppCurrency) "App Currency" else "Token",
),
)
data object ButtonMax : StakingAnalyticsEvent(
event = "Button - Max",
)
data class ButtonCancel(
val source: StakeScreenSource,
) : StakingAnalyticsEvent(
event = "Button - Cancel",
params = mapOf(
AnalyticsParam.SOURCE to source.name,
),
)
data class ValidatorChosen(
val validator: String,
) : StakingAnalyticsEvent(
event = "Validator Chosen",
params = mapOf(
"Validator" to validator,
),
)
data class ButtonValidator(
val source: StakeScreenSource,
) : StakingAnalyticsEvent(
event = "Button - Validator",
params = mapOf(
AnalyticsParam.SOURCE to source.name,
),
)
data object ButtonRewards : StakingAnalyticsEvent(
event = "Button - Rewards",
)
data class ButtonAction(
val action: StakingActionType,
val validator: String,
) : StakingAnalyticsEvent(
event = "Button - ${action.asAnalyticName}",
params = mapOf(
"Validator" to validator,
),
)
data object ButtonShare : StakingAnalyticsEvent(event = "Button - Share")
data object ButtonExplore : StakingAnalyticsEvent(event = "Button - Explore")
data class StakeKitApiError(
val stakingError: StakingError.StakeKitApiError,
) : StakingAnalyticsEvent(
event = "Errors",
params = buildMap {
addIfValueIsNotNull(AnalyticsParam.ERROR_MESSAGE, stakingError.message)
addIfValueIsNotNull(AnalyticsParam.ERROR_CODE, stakingError.code)
addIfValueIsNotNull(AnalyticsParam.METHOD_NAME, stakingError.methodName)
},
)
data class StakeKitApiUnknownError(
val stakeKitUnknownError: StakingError.StakeKitUnknownError,
) : StakingAnalyticsEvent(
event = "Errors",
params = buildMap {
addIfValueIsNotNull(AnalyticsParam.ERROR_DESCRIPTION, stakeKitUnknownError.jsonString)
},
)
fun MutableMap<String, String>.addIfValueIsNotNull(key: String, value: Any?) {
if (value != null) {
put(key, value.toString())
}
}
data object TransactionError : StakingAnalyticsEvent(
event = "Error - Transaction Rejected",
)
}
enum class StakeScreenSource {
Info, Amount, Confirmation, Validators,
}

View file

@ -1,179 +0,0 @@
package com.tangem.features.staking.impl.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
internal sealed class StakingAnalyticsEvents(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent(
category = "Staking",
event = event,
params = params,
) {
data class StakingInfoScreenOpened(
val validatorsCount: Int,
val token: String,
) : StakingAnalyticsEvents(
event = "Staking Info Screen Opened",
params = mapOf(
"Validators Count" to validatorsCount.toString(),
AnalyticsParam.TOKEN_PARAM to token,
),
)
data class WhatIsStaking(val token: String) : StakingAnalyticsEvents(
event = "Link - What Is Staking",
params = mapOf(
AnalyticsParam.TOKEN_PARAM to token,
),
)
data class AmountScreenOpened(val token: String) : StakingAnalyticsEvents(
event = "Amount Screen Opened",
params = mapOf(
AnalyticsParam.TOKEN_PARAM to token,
),
)
data class ConfirmationScreenOpened(
val token: String,
val validator: String,
val action: StakingActionType,
) : StakingAnalyticsEvents(
event = "Confirmation Screen Opened",
params = mapOf(
AnalyticsParam.TOKEN_PARAM to token,
"Validator" to validator,
"Action" to action.asAnalyticName,
),
)
data class StakeInProgressScreenOpened(
val validator: String,
val token: String,
val action: StakingActionType,
) : StakingAnalyticsEvents(
event = "Stake In Progress Screen Opened",
params = mapOf(
AnalyticsParam.TOKEN_PARAM to token,
"Validator" to validator,
"Action" to action.asAnalyticName,
),
)
data class RewardScreenOpened(val token: String) : StakingAnalyticsEvents(
event = "Reward Screen Opened",
params = mapOf(
AnalyticsParam.TOKEN_PARAM to token,
),
)
data class AmountSelectCurrency(
val token: String,
val isAppCurrency: Boolean,
) : StakingAnalyticsEvents(
event = "Selected Currency",
params = mapOf(
AnalyticsParam.TOKEN_PARAM to token,
AnalyticsParam.TYPE to if (isAppCurrency) "App Currency" else "Token",
),
)
data class ButtonMax(val token: String) : StakingAnalyticsEvents(
event = "Button - Max",
params = mapOf(
AnalyticsParam.TOKEN_PARAM to token,
),
)
data class ButtonCancel(
val source: StakeScreenSource,
val token: String,
) : StakingAnalyticsEvents(
event = "Button - Cancel",
params = mapOf(
AnalyticsParam.TOKEN_PARAM to token,
AnalyticsParam.SOURCE to source.name,
),
)
data class ValidatorChosen(
val token: String,
val validator: String,
) : StakingAnalyticsEvents(
event = "Validator Chosen",
params = mapOf(
AnalyticsParam.TOKEN_PARAM to token,
"Validator" to validator,
),
)
data class ButtonValidator(
val source: StakeScreenSource,
val token: String,
) : StakingAnalyticsEvents(
event = "Button - Validator",
params = mapOf(
AnalyticsParam.TOKEN_PARAM to token,
AnalyticsParam.SOURCE to source.name,
),
)
data class ButtonRewards(
val token: String,
) : StakingAnalyticsEvents(
event = "Button - Rewards",
params = mapOf(
AnalyticsParam.TOKEN_PARAM to token,
),
)
data class ButtonAction(
val action: StakingActionType,
val token: String,
val validator: String,
) : StakingAnalyticsEvents(
event = "Button - ${action.asAnalyticName}",
params = mapOf(
AnalyticsParam.TOKEN_PARAM to token,
"Validator" to validator,
),
)
data object ButtonShare : StakingAnalyticsEvents(event = "Button - Share")
data object ButtonExplore : StakingAnalyticsEvents(event = "Button - Explore")
data class StakekitError(
val token: String,
val stakeKitError: StakingError,
) : StakingAnalyticsEvents(
event = "Errors",
params = mapOf(
AnalyticsParam.TOKEN_PARAM to token,
if (stakeKitError is StakingError.UnknownError) {
AnalyticsParam.ERROR_DESCRIPTION to (stakeKitError.message ?: "Unknown")
} else {
AnalyticsParam.ERROR_TYPE to stakeKitError.javaClass.simpleName
},
),
)
data class TransactionError(
val token: String,
) : StakingAnalyticsEvents(
event = "Error - Transaction Rejected",
params = mapOf(AnalyticsParam.TOKEN_PARAM to token),
)
}
enum class StakeScreenSource {
Info,
Amount,
Confirmation,
Validators,
}

View file

@ -0,0 +1,23 @@
package com.tangem.features.staking.impl.analytics
import com.tangem.core.analytics.api.ParamsInterceptor
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.domain.staking.analytics.StakingAnalyticsEvent
class StakingParamsInterceptor(private val tokenSymbol: String) : ParamsInterceptor {
override fun id() = ID
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean {
return event is StakingAnalyticsEvent
}
override fun intercept(params: MutableMap<String, String>) {
params[AnalyticsParam.TOKEN_PARAM] = tokenSymbol
}
companion object {
const val ID = "StakingParamsInterceptorId"
}
}

View file

@ -7,8 +7,8 @@ import com.tangem.core.analytics.models.Basic
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.staking.impl.analytics.StakeScreenSource
import com.tangem.features.staking.impl.analytics.StakingAnalyticsEvents
import com.tangem.domain.staking.analytics.StakeScreenSource
import com.tangem.domain.staking.analytics.StakingAnalyticsEvent
import com.tangem.features.staking.impl.presentation.state.*
internal class StakingAnalyticSender(
@ -24,9 +24,8 @@ internal class StakingAnalyticSender(
?.size ?: 0
analyticsEventHandler.send(
StakingAnalyticsEvents.StakingInfoScreenOpened(
StakingAnalyticsEvent.StakingInfoScreenOpened(
validatorsCount = validatorCount,
token = value.cryptoCurrencySymbol,
),
)
}
@ -39,8 +38,7 @@ internal class StakingAnalyticSender(
if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) return
analyticsEventHandler.send(
StakingAnalyticsEvents.ConfirmationScreenOpened(
token = value.cryptoCurrencySymbol,
StakingAnalyticsEvent.ConfirmationScreenOpened(
validator = validatorName,
action = getStakingActionType(value),
),
@ -49,7 +47,7 @@ internal class StakingAnalyticSender(
fun screenCancel(value: StakingUiState) {
analyticsEventHandler.send(
StakingAnalyticsEvents.ButtonCancel(
StakingAnalyticsEvent.ButtonCancel(
source = when (value.currentStep) {
StakingStep.InitialInfo -> StakeScreenSource.Info
StakingStep.Amount -> StakeScreenSource.Amount
@ -58,7 +56,6 @@ internal class StakingAnalyticSender(
StakingStep.RewardsValidators,
-> StakeScreenSource.Validators
},
token = value.cryptoCurrencyName,
),
)
}
@ -93,9 +90,8 @@ internal class StakingAnalyticSender(
),
)
analyticsEventHandler.send(
StakingAnalyticsEvents.StakeInProgressScreenOpened(
StakingAnalyticsEvent.StakeInProgressScreenOpened(
validator = validatorName,
token = value.cryptoCurrencySymbol,
action = getStakingActionType(value),
),
)
@ -107,9 +103,8 @@ internal class StakingAnalyticSender(
val validatorName = validatorState?.chosenValidator?.name ?: return
analyticsEventHandler.send(
StakingAnalyticsEvents.ButtonAction(
StakingAnalyticsEvent.ButtonAction(
action = getStakingActionType(value),
token = value.cryptoCurrencySymbol,
validator = validatorName,
),
)

View file

@ -3,7 +3,7 @@ package com.tangem.features.staking.impl.presentation.state
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.features.staking.impl.analytics.StakingAnalyticsEvents
import com.tangem.domain.staking.analytics.StakingAnalyticsEvent
import com.tangem.features.staking.impl.analytics.utils.StakingAnalyticSender
internal class StakingStateRouter(
@ -60,16 +60,12 @@ internal class StakingStateRouter(
}
private fun showRewardsValidators() {
analyticsEventsHandler.send(
StakingAnalyticsEvents.RewardScreenOpened(stateController.value.cryptoCurrencySymbol),
)
analyticsEventsHandler.send(StakingAnalyticsEvent.RewardScreenOpened)
stateController.update { it.copy(currentStep = StakingStep.RewardsValidators) }
}
private fun showAmount() {
analyticsEventsHandler.send(
StakingAnalyticsEvents.AmountScreenOpened(stateController.value.cryptoCurrencySymbol),
)
analyticsEventsHandler.send(StakingAnalyticsEvent.AmountScreenOpened)
stateController.update { it.copy(currentStep = StakingStep.Amount) }
}

View file

@ -135,7 +135,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
}
if (result.isNullOrEmpty()) {
onStakingFeeError(StakingError.UnknownError())
onStakingFeeError(StakingError.DomainError("Error estimating fee"))
return
}

View file

@ -4,7 +4,6 @@ import arrow.core.getOrElse
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.staking.*
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.SubmitHashData
@ -20,7 +19,6 @@ import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.staking.impl.analytics.StakingAnalyticsEvents
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount
import com.tangem.features.staking.impl.presentation.state.utils.isSolanaWithdraw
@ -46,7 +44,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
private val submitHashUseCase: SubmitHashUseCase,
private val saveUnsubmittedHashUseCase: SaveUnsubmittedHashUseCase,
private val savePendingTransactionUseCase: SavePendingTransactionUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
@Assisted private val userWallet: UserWallet,
@Assisted private val yield: Yield,
@ -84,7 +81,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
)
if (fullTransactionsData.isNullOrEmpty()) {
onConstructError(StakingError.UnknownError())
onConstructError(StakingError.DomainError("fullTransactionsData is null or empty"))
return
}
@ -198,12 +195,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
type = action?.type,
),
).getOrElse {
analyticsEventHandler.send(
StakingAnalyticsEvents.StakekitError(
token = state.cryptoCurrencySymbol,
stakeKitError = it,
),
)
onConstructError(it)
return emptyList()
}
@ -255,13 +246,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
pendingTransaction = pendingTransaction,
),
)
.onLeft { error ->
analyticsEventHandler.send(
StakingAnalyticsEvents.StakekitError(
token = stateController.value.cryptoCurrencySymbol,
stakeKitError = error,
),
)
.onLeft {
saveUnsubmittedHashUseCase.invoke(
transactionId = transaction.id,
transactionHash = transactionHash,

View file

@ -32,14 +32,8 @@ internal class AddStakingErrorTransformer(
}
private fun convertToNotification(error: StakingError): NotificationUM {
return when (error) {
is StakingError.StakedPositionNotFoundError -> StakingNotification.Error.StakedPositionNotFoundError(
message = error.toString(),
)
// TODO staking
else -> StakingNotification.Error.Common(
subtitle = stringReference(error.toString()),
)
}
return StakingNotification.Error.Common(
subtitle = stringReference(error.toString()),
)
}
}

View file

@ -15,6 +15,7 @@ import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.api.ParamsInterceptorHolder
import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.haptic.VibratorHapticManager
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
@ -47,8 +48,9 @@ import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.staking.impl.analytics.StakeScreenSource
import com.tangem.features.staking.impl.analytics.StakingAnalyticsEvents
import com.tangem.domain.staking.analytics.StakeScreenSource
import com.tangem.domain.staking.analytics.StakingAnalyticsEvent
import com.tangem.features.staking.impl.analytics.StakingParamsInterceptor
import com.tangem.features.staking.impl.analytics.utils.StakingAnalyticSender
import com.tangem.features.staking.impl.navigation.InnerStakingRouter
import com.tangem.features.staking.impl.presentation.state.*
@ -106,6 +108,7 @@ internal class StakingViewModel @Inject constructor(
private val stakingBalanceUpdater: StakingBalanceUpdater.Factory,
private val analyticsEventHandler: AnalyticsEventHandler,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val paramsInterceptorHolder: ParamsInterceptorHolder,
@DelayedWork private val coroutineScope: CoroutineScope,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
@ -201,6 +204,7 @@ internal class StakingViewModel @Inject constructor(
override fun onCleared() {
super.onCleared()
paramsInterceptorHolder.removeParamsInterceptor(StakingParamsInterceptor.ID)
approvalJobHolder.cancel()
feeJobHolder.cancel()
sendTransactionJobHolder.cancel()
@ -297,18 +301,12 @@ internal class StakingViewModel @Inject constructor(
)
updateNotifications()
},
onStakingFeeError = { error ->
analyticsEventHandler.send(
StakingAnalyticsEvents.StakekitError(
value.cryptoCurrencySymbol,
error,
),
)
onStakingFeeError = {
stateController.update(AddStakingErrorTransformer())
updateNotifications(GetFeeError.UnknownError)
},
onFeeError = { error ->
analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol))
analyticsEventHandler.send(StakingAnalyticsEvent.TransactionError)
stateController.update(AddStakingErrorTransformer())
updateNotifications(error)
},
@ -336,13 +334,6 @@ internal class StakingViewModel @Inject constructor(
transactionsInProgress.addAll(constructedTransactions)
},
onConstructError = { error ->
Timber.e(error.toString())
analyticsEventHandler.send(
StakingAnalyticsEvents.StakekitError(
token = value.cryptoCurrencySymbol,
stakeKitError = error,
),
)
stakingEventFactory.createStakingErrorAlert(error)
stateController.update(SetConfirmationStateResetAssentTransformer)
},
@ -353,7 +344,7 @@ internal class StakingViewModel @Inject constructor(
},
onSendError = { error ->
Timber.e(error.toString())
analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol))
analyticsEventHandler.send(StakingAnalyticsEvent.TransactionError)
stakingEventFactory.createSendTransactionErrorAlert(error)
stateController.update(SetConfirmationStateResetAssentTransformer)
},
@ -399,7 +390,7 @@ internal class StakingViewModel @Inject constructor(
}
override fun onInitialInfoBannerClick() {
analyticsEventHandler.send(StakingAnalyticsEvents.WhatIsStaking(value.cryptoCurrencySymbol))
analyticsEventHandler.send(StakingAnalyticsEvent.WhatIsStaking)
innerRouter.openUrl(WHAT_IS_STAKING_ARTICLE_URL)
}
@ -446,22 +437,21 @@ internal class StakingViewModel @Inject constructor(
}
override fun onMaxValueClick() {
analyticsEventHandler.send(StakingAnalyticsEvents.ButtonMax(value.cryptoCurrencySymbol))
analyticsEventHandler.send(StakingAnalyticsEvent.ButtonMax)
stateController.update(AmountMaxValueStateTransformer(cryptoCurrencyStatus, yield))
}
override fun onCurrencyChangeClick(isFiat: Boolean) {
analyticsEventHandler.send(
StakingAnalyticsEvents.AmountSelectCurrency(value.cryptoCurrencySymbol, isFiat),
StakingAnalyticsEvent.AmountSelectCurrency(isFiat),
)
stateController.update(AmountCurrencyChangeStateTransformer(cryptoCurrencyStatus, isFiat))
}
override fun openValidators() {
analyticsEventHandler.send(
StakingAnalyticsEvents.ButtonValidator(
StakingAnalyticsEvent.ButtonValidator(
source = StakeScreenSource.Confirmation,
token = value.cryptoCurrencySymbol,
),
)
stakingStateRouter.showValidators()
@ -469,18 +459,15 @@ internal class StakingViewModel @Inject constructor(
override fun onValidatorSelect(validator: Yield.Validator) {
analyticsEventHandler.send(
StakingAnalyticsEvents.ValidatorChosen(
value.cryptoCurrencySymbol,
validator.name,
StakingAnalyticsEvent.ValidatorChosen(
validator = validator.name,
),
)
stateController.update(ValidatorSelectChangeTransformer(validator))
}
override fun openRewardsValidators() {
analyticsEventHandler.send(
StakingAnalyticsEvents.ButtonRewards(value.cryptoCurrencySymbol),
)
analyticsEventHandler.send(StakingAnalyticsEvent.ButtonRewards)
val rewardsValidators =
stateController.value.rewardsValidatorsState as? StakingStates.RewardsValidatorsState.Data
val rewards = rewardsValidators?.rewards
@ -488,9 +475,8 @@ internal class StakingViewModel @Inject constructor(
onActiveStake(rewards.first())
} else {
analyticsEventHandler.send(
StakingAnalyticsEvents.ButtonValidator(
StakingAnalyticsEvent.ButtonValidator(
source = StakeScreenSource.Info,
token = value.cryptoCurrencySymbol,
),
)
onNextClick(actionTypeToOverwrite = StakingActionCommonType.PENDING_REWARDS)
@ -542,9 +528,8 @@ internal class StakingViewModel @Inject constructor(
override fun onActiveStakeAnalytic() {
analyticsEventHandler.send(
StakingAnalyticsEvents.ButtonValidator(
StakingAnalyticsEvent.ButtonValidator(
source = StakeScreenSource.Info,
token = value.cryptoCurrencySymbol,
),
)
}
@ -595,7 +580,7 @@ internal class StakingViewModel @Inject constructor(
).fold(
ifLeft = { error ->
Timber.e(error.toString())
analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol))
analyticsEventHandler.send(StakingAnalyticsEvent.TransactionError)
stateController.update(
SetConfirmationStateAssentApprovalTransformer(
appCurrencyProvider = Provider { appCurrency },
@ -617,7 +602,7 @@ internal class StakingViewModel @Inject constructor(
).fold(
ifLeft = { error ->
Timber.e(error.toString())
analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol))
analyticsEventHandler.send(StakingAnalyticsEvent.TransactionError)
stateController.update(
SetConfirmationStateAssentApprovalTransformer(
appCurrencyProvider = Provider { appCurrency },
@ -747,7 +732,7 @@ internal class StakingViewModel @Inject constructor(
}
override fun onExploreClick() {
analyticsEventHandler.send(StakingAnalyticsEvents.ButtonExplore)
analyticsEventHandler.send(StakingAnalyticsEvent.ButtonExplore)
val confirmationDataState = uiState.value.confirmationState as? StakingStates.ConfirmationState.Data
val transactionDoneState = confirmationDataState?.transactionDoneState as? TransactionDoneState.Content
val txUrl = transactionDoneState?.txUrl
@ -761,7 +746,7 @@ internal class StakingViewModel @Inject constructor(
val transactionDoneState = confirmationDataState?.transactionDoneState as? TransactionDoneState.Content
val txUrl = transactionDoneState?.txUrl
analyticsEventHandler.send(StakingAnalyticsEvents.ButtonShare)
analyticsEventHandler.send(StakingAnalyticsEvent.ButtonShare)
if (txUrl != null) {
vibratorHapticManager.performOneTime(TangemHapticEffect.OneTime.Click)
stakingEventFactory.createShareDialog(txUrl = txUrl)
@ -838,10 +823,12 @@ internal class StakingViewModel @Inject constructor(
if (!isInitialInfoAnalyticSent) {
isInitialInfoAnalyticSent = true
val balances = status.value.yieldBalance as? YieldBalance.Data
paramsInterceptorHolder.addParamsInterceptor(
interceptor = StakingParamsInterceptor(status.currency.symbol),
)
analyticsEventHandler.send(
StakingAnalyticsEvents.StakingInfoScreenOpened(
StakingAnalyticsEvent.StakingInfoScreenOpened(
validatorsCount = balances?.getValidatorsCount() ?: 0,
token = status.currency.symbol,
),
)
}