diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index c72518ff14..3b3adab9bf 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -357,7 +357,10 @@ internal object TokensDomainModule { @Provides @Singleton - fun provideGetCurrencyCheckUseCase(currencyChecksRepository: CurrencyChecksRepository): GetCurrencyCheckUseCase { - return GetCurrencyCheckUseCase(currencyChecksRepository) + fun provideGetCurrencyCheckUseCase( + currencyChecksRepository: CurrencyChecksRepository, + dispatchers: CoroutineDispatcherProvider, + ): GetCurrencyCheckUseCase { + return GetCurrencyCheckUseCase(currencyChecksRepository, dispatchers) } } \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt index 51df42310b..5ba4b5f528 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt @@ -67,7 +67,7 @@ object NotificationsFactory { if (!isAccountFunded && reserveAmount != null && reserveAmount > sendingAmount) { add( NotificationUM.Error.ReserveAmount( - sendingAmount.format { + reserveAmount.format { crypto(cryptoCurrency) }, ), diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index 9600cda393..b646142403 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -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" diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt b/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt index 13715236c5..18a95d3a02 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt @@ -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) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/UnknownEnumMoshiAdapter.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/UnknownEnumMoshiAdapter.kt index 2cc525ffd0..97372a83b2 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/UnknownEnumMoshiAdapter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/UnknownEnumMoshiAdapter.kt @@ -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 { diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/error/StakeKitErrorResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/error/StakeKitErrorResponse.kt index fdb3436051..086837d4b1 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/error/StakeKitErrorResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/error/StakeKitErrorResponse.kt @@ -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, -} \ No newline at end of file +) \ No newline at end of file diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 9fa2f27509..745138a008 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -339,7 +339,7 @@ \nCoinMarketCap Blockchain, auf der die Kryptowährung ursprünglich erstellt wurde Natives Netzwerk - Die Verwendung von nicht nativen Netzwerken für Token ermöglicht eine blockchainübergreifende Interoperabilität, sodass Vermögenswerte in verschiedenen dezentralen Anwendungen und Smart Contracts plattformübergreifend genutzt werden können. Dies erfordert jedoch häufig eine Verwahrstelle oder einen intelligenten Vertrag, um den ursprünglichen Vermögenswert sicher zu verwahren, was zu einer Zentralisierung und einem Kontrahentenrisiko führt. + Die Verwendung nicht-nativer Netzwerke für Token ermöglicht Interoperabilität zwischen Blockchains, sodass Vermögenswerte plattformübergreifend in verschiedenen dezentralen Anwendungen und Smart Contracts genutzt werden können. Dies erfordert jedoch häufig einen Verwahrer oder Smart Contract, um den ursprünglichen Vermögenswert sicher aufzubewahren, was zu Zentralisierung und Kontrahentenrisiken führt. Nicht die ursprüngliche oder primäre Blockchain, in der der Token gehostet wird Nicht-native Netzwerke Netzwerke auswählen @@ -710,7 +710,7 @@ Marktbewertung Metriken Mindestanforderungen - Keine Belohnungen + Keine Belohnungen verfügbar Belohnungen beanspruchen Eine Möglichkeit, Staking-Belohnungen zu erhalten. Es kann automatisch oder manuell beansprucht werden. Belohnungszeitplan @@ -760,7 +760,7 @@ Täglich Alle %s Täglich - Alle %s + Alle %s Tage Jeder Pro Epoche Pro Ära diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index d9837db78c..73f301ed3c 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -520,7 +520,7 @@ Ваша seed-фраза - + слово %d слова %d слів %d слів @@ -741,7 +741,7 @@ Період, який ви повинні чекати після запиту на виведення коштів зі стейкінгу, перш ніж токени стануть доступними. Період блокування Відведений час для активації участі в стейкінгу. - Комісія мережі за схвалення токену, щоб верифікувати, що саме ви даєте дозвіл на використання вашого токена для стейкінгу. + Комісія за схвалення токену, щоб верифікувати, що саме ви дозволяєте використання токена для стейкінгу. Використовуючи функцію стейкінгу, ви погоджуєтесь з %1$s та %2$s Заблоковано Перемістити diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingErrorResolver.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingErrorResolver.kt index 8568980711..34ebad7aa5 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingErrorResolver.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingErrorResolver.kt @@ -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 } } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt index 7ed9eb23e3..52993093c5 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt @@ -127,7 +127,7 @@ class YieldConverter( stakedBalance = validatorDTO.stakedBalance, votingPower = validatorDTO.votingPower, preferred = validatorDTO.preferred, - isStrategicPartner = isStrategicPartner(address), + isStrategicPartner = isStrategicPartner(validatorDTO.address, validatorDTO.name), ) } @@ -180,11 +180,17 @@ class YieldConverter( } } - private fun isStrategicPartner(validatorAddress: String): Boolean = PARTNERS.any { it == validatorAddress } + private fun isStrategicPartner(validatorAddress: String?, validatorName: String): Boolean { + return PARTNERS.any { it == validatorAddress } || PARTNERS_NAMES.any { it.equals(validatorName, true) } + } private companion object { val PARTNERS = listOf( "cosmosvaloper1wrx0x9m9ykdhw9sg04v7uljme53wuj03aa5d4f", + "H2tJNyMHnRF6ahCQLQ1sSycM4FGchymuzyYzUqKEuydk", + ) + val PARTNERS_NAMES = listOf( + "Meria", ) } } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/error/StakeKitErrorConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/error/StakeKitErrorConverter.kt index 8b303f9292..fdc9226b7d 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/error/StakeKitErrorConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/error/StakeKitErrorConverter.kt @@ -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, ) : Converter { - @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) } } } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt index 2226847178..f3a7879d5a 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt @@ -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.* import com.tangem.data.staking.DefaultStakingErrorResolver @@ -83,10 +84,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, ) } } \ No newline at end of file diff --git a/domain/staking/build.gradle.kts b/domain/staking/build.gradle.kts index 5107e30cf7..9832ec7010 100644 --- a/domain/staking/build.gradle.kts +++ b/domain/staking/build.gradle.kts @@ -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) diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/StakingError.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/StakingError.kt index b02b33741f..0d0ca16611 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/StakingError.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/StakingError.kt @@ -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, + 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() } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/StakingAnalyticsEvents.kt b/domain/staking/src/main/java/com/tangem/domain/staking/analytics/StakingAnalyticsEvent.kt similarity index 50% rename from features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/StakingAnalyticsEvents.kt rename to domain/staking/src/main/java/com/tangem/domain/staking/analytics/StakingAnalyticsEvent.kt index cc612804f3..dbe640d0b0 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/StakingAnalyticsEvents.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/analytics/StakingAnalyticsEvent.kt @@ -1,11 +1,12 @@ -package com.tangem.features.staking.impl.analytics +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 -internal sealed class StakingAnalyticsEvents( +sealed class StakingAnalyticsEvent( event: String, params: Map = mapOf(), ) : AnalyticsEvent( @@ -16,37 +17,27 @@ internal sealed class StakingAnalyticsEvents( data class StakingInfoScreenOpened( val validatorsCount: Int, - val token: String, - ) : StakingAnalyticsEvents( + ) : StakingAnalyticsEvent( event = "Staking Info Screen Opened", params = mapOf( "Validators Count" to validatorsCount.toString(), - AnalyticsParam.TOKEN_PARAM to token, ), ) - data class WhatIsStaking(val token: String) : StakingAnalyticsEvents( + data object WhatIsStaking : StakingAnalyticsEvent( event = "Link - What Is Staking", - params = mapOf( - AnalyticsParam.TOKEN_PARAM to token, - ), ) - data class AmountScreenOpened(val token: String) : StakingAnalyticsEvents( + data object AmountScreenOpened : StakingAnalyticsEvent( event = "Amount Screen Opened", - params = mapOf( - AnalyticsParam.TOKEN_PARAM to token, - ), ) data class ConfirmationScreenOpened( - val token: String, val validator: String, val action: StakingActionType, - ) : StakingAnalyticsEvents( + ) : StakingAnalyticsEvent( event = "Confirmation Screen Opened", params = mapOf( - AnalyticsParam.TOKEN_PARAM to token, "Validator" to validator, "Action" to action.asAnalyticName, ), @@ -54,126 +45,108 @@ internal sealed class StakingAnalyticsEvents( data class StakeInProgressScreenOpened( val validator: String, - val token: String, val action: StakingActionType, - ) : StakingAnalyticsEvents( + ) : StakingAnalyticsEvent( 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( + data object RewardScreenOpened : StakingAnalyticsEvent( event = "Reward Screen Opened", - params = mapOf( - AnalyticsParam.TOKEN_PARAM to token, - ), ) data class AmountSelectCurrency( - val token: String, val isAppCurrency: Boolean, - ) : StakingAnalyticsEvents( + ) : StakingAnalyticsEvent( 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( + data object ButtonMax : StakingAnalyticsEvent( event = "Button - Max", - params = mapOf( - AnalyticsParam.TOKEN_PARAM to token, - ), ) data class ButtonCancel( val source: StakeScreenSource, - val token: String, - ) : StakingAnalyticsEvents( + ) : StakingAnalyticsEvent( 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( + ) : StakingAnalyticsEvent( event = "Validator Chosen", params = mapOf( - AnalyticsParam.TOKEN_PARAM to token, "Validator" to validator, ), ) data class ButtonValidator( val source: StakeScreenSource, - val token: String, - ) : StakingAnalyticsEvents( + ) : StakingAnalyticsEvent( event = "Button - Validator", params = mapOf( - AnalyticsParam.TOKEN_PARAM to token, AnalyticsParam.SOURCE to source.name, ), ) - data class ButtonRewards( - val token: String, - ) : StakingAnalyticsEvents( + data object ButtonRewards : StakingAnalyticsEvent( event = "Button - Rewards", - params = mapOf( - AnalyticsParam.TOKEN_PARAM to token, - ), ) data class ButtonAction( val action: StakingActionType, - val token: String, val validator: String, - ) : StakingAnalyticsEvents( + ) : StakingAnalyticsEvent( event = "Button - ${action.asAnalyticName}", params = mapOf( - AnalyticsParam.TOKEN_PARAM to token, "Validator" to validator, ), ) - data object ButtonShare : StakingAnalyticsEvents(event = "Button - Share") + data object ButtonShare : StakingAnalyticsEvent(event = "Button - Share") - data object ButtonExplore : StakingAnalyticsEvents(event = "Button - Explore") + data object ButtonExplore : StakingAnalyticsEvent(event = "Button - Explore") - data class StakekitError( - val token: String, - val stakeKitError: StakingError, - ) : StakingAnalyticsEvents( + data class StakeKitApiError( + val stakingError: StakingError.StakeKitApiError, + ) : StakingAnalyticsEvent( event = "Errors", - params = mapOf( - AnalyticsParam.TOKEN_PARAM to token, - AnalyticsParam.ERROR_TYPE to if (stakeKitError is StakingError.UnknownError) { - stakeKitError.message ?: "Unknown" - } else { - stakeKitError.javaClass.simpleName - }, - ), + params = buildMap { + addIfValueIsNotNull(AnalyticsParam.ERROR_MESSAGE, stakingError.message) + addIfValueIsNotNull(AnalyticsParam.ERROR_CODE, stakingError.code) + addIfValueIsNotNull(AnalyticsParam.METHOD_NAME, stakingError.methodName) + }, ) - data class TransactionError( - val token: String, - ) : StakingAnalyticsEvents( + data class StakeKitApiUnknownError( + val stakeKitUnknownError: StakingError.StakeKitUnknownError, + ) : StakingAnalyticsEvent( + event = "Errors", + params = buildMap { + addIfValueIsNotNull(AnalyticsParam.ERROR_DESCRIPTION, stakeKitUnknownError.jsonString) + }, + ) + + fun MutableMap.addIfValueIsNotNull(key: String, value: Any?) { + if (value != null) { + put(key, value.toString()) + } + } + + data object TransactionError : StakingAnalyticsEvent( event = "Error - Transaction Rejected", - params = mapOf(AnalyticsParam.TOKEN_PARAM to token), ) } enum class StakeScreenSource { - Info, - Amount, - Confirmation, - Validators, + Info, Amount, Confirmation, Validators, } \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyCheck.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyCheck.kt index 04018ed4aa..780c6c48fe 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyCheck.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyCheck.kt @@ -8,4 +8,5 @@ data class CryptoCurrencyCheck( val reserveAmount: BigDecimal?, val existentialDeposit: BigDecimal?, val utxoAmountLimit: UtxoAmountLimit?, + val isAccountFunded: Boolean, ) \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt index 4e74a21591..aa488b2671 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt @@ -4,10 +4,13 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext import java.math.BigDecimal class GetCurrencyCheckUseCase( private val currencyChecksRepository: CurrencyChecksRepository, + private val dispatchers: CoroutineDispatcherProvider, ) { suspend operator fun invoke( @@ -15,27 +18,38 @@ class GetCurrencyCheckUseCase( currencyStatus: CryptoCurrencyStatus, amount: BigDecimal?, fee: BigDecimal?, + recipientAddress: String? = null, ): CryptoCurrencyCheck { - val network = currencyStatus.currency.network - val dustValue = currencyChecksRepository.getDustValue(userWalletId, network) - val reserveAmount = currencyChecksRepository.getReserveAmount(userWalletId, network) - val existentialDeposit = currencyChecksRepository.getExistentialDeposit(userWalletId, network) - val utxoAmountLimit = if (amount != null && fee != null) { - currencyChecksRepository.checkUtxoAmountLimit( - userWalletId = userWalletId, - network = network, - amount = amount, - fee = fee, - ) - } else { - null - } + return withContext(dispatchers.io) { + val network = currencyStatus.currency.network + val dustValue = currencyChecksRepository.getDustValue(userWalletId, network) + val reserveAmount = currencyChecksRepository.getReserveAmount(userWalletId, network) + val existentialDeposit = currencyChecksRepository.getExistentialDeposit(userWalletId, network) + val isAccountFunded = recipientAddress?.let { + currencyChecksRepository.checkIfAccountFunded( + userWalletId, + network, + recipientAddress, + ) + } ?: false + val utxoAmountLimit = if (amount != null && fee != null) { + currencyChecksRepository.checkUtxoAmountLimit( + userWalletId = userWalletId, + network = network, + amount = amount, + fee = fee, + ) + } else { + null + } - return CryptoCurrencyCheck( - dustValue = dustValue, - reserveAmount = reserveAmount, - existentialDeposit = existentialDeposit, - utxoAmountLimit = utxoAmountLimit, - ) + CryptoCurrencyCheck( + dustValue = dustValue, + reserveAmount = reserveAmount, + existentialDeposit = existentialDeposit, + utxoAmountLimit = utxoAmountLimit, + isAccountFunded = isAccountFunded, + ) + } } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index 23f483ef38..48ec5be5b1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -45,7 +45,7 @@ import kotlinx.coroutines.flow.map import java.math.BigDecimal @Suppress("LongParameterList", "LargeClass") -internal class SendNotificationFactory constructor( +internal class SendNotificationFactory( private val analyticsEventHandler: AnalyticsEventHandler, private val validateTransactionUseCase: ValidateTransactionUseCase, private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase, @@ -90,11 +90,14 @@ internal class SendNotificationFactory constructor( reduceAmountBy = reduceAmountBy, ) val feeError = (feeState.feeSelectorState as? FeeSelectorState.Error)?.error + + val recipientAddress = state.recipientState?.addressTextField?.value val currencyCheck = getCurrencyCheckUseCase( userWalletId = userWalletId, currencyStatus = cryptoCurrencyStatus, amount = sendingAmount, fee = feeValue, + recipientAddress = recipientAddress, ) buildList { addErrorNotifications( @@ -187,7 +190,7 @@ internal class SendNotificationFactory constructor( reserveAmount = currencyCheck.reserveAmount, sendingAmount = sendingAmount, cryptoCurrency = currency, - isAccountFunded = false, + isAccountFunded = currencyCheck.isAccountFunded, ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/StakingParamsInterceptor.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/StakingParamsInterceptor.kt new file mode 100644 index 0000000000..a11ff1ac92 --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/StakingParamsInterceptor.kt @@ -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) { + params[AnalyticsParam.TOKEN_PARAM] = tokenSymbol + } + + companion object { + const val ID = "StakingParamsInterceptorId" + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt index 7fcb116702..114d86d579 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt @@ -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 @@ -59,7 +57,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), ), ) @@ -106,9 +102,8 @@ internal class StakingAnalyticSender( val validatorName = validatorState?.chosenValidator?.name ?: return analyticsEventHandler.send( - StakingAnalyticsEvents.ButtonAction( + StakingAnalyticsEvent.ButtonAction( action = getStakingActionType(value), - token = value.cryptoCurrencySymbol, validator = validatorName, ), ) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt index e0e42070ac..bcddf3b0df 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt @@ -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( @@ -68,16 +68,12 @@ internal class StakingStateRouter( } 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) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt index 0b19f03422..a56aa88c94 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt @@ -135,7 +135,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor( } if (result.isNullOrEmpty()) { - onStakingFeeError(StakingError.UnknownError()) + onStakingFeeError(StakingError.DomainError("Error estimating fee")) return } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt index e61018217b..3a7bacaf46 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt @@ -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.SubmitHashData import com.tangem.domain.staking.model.stakekit.* @@ -19,11 +18,7 @@ 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.FeeState -import com.tangem.features.staking.impl.presentation.state.StakingStateController -import com.tangem.features.staking.impl.presentation.state.StakingStates -import com.tangem.features.staking.impl.presentation.state.StakingUiState +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.isComposePendingActions import com.tangem.utils.extensions.orZero @@ -46,7 +41,6 @@ internal class StakingTransactionSender @AssistedInject constructor( private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val submitHashUseCase: SubmitHashUseCase, private val saveUnsubmittedHashUseCase: SaveUnsubmittedHashUseCase, - private val analyticsEventHandler: AnalyticsEventHandler, @Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus, @Assisted private val userWallet: UserWallet, @Assisted private val yield: Yield, @@ -84,7 +78,7 @@ internal class StakingTransactionSender @AssistedInject constructor( ) if (fullTransactionsData.isNullOrEmpty()) { - onConstructError(StakingError.UnknownError()) + onConstructError(StakingError.DomainError("fullTransactionsData is null or empty")) return } @@ -193,12 +187,6 @@ internal class StakingTransactionSender @AssistedInject constructor( type = action?.type, ), ).getOrElse { - analyticsEventHandler.send( - StakingAnalyticsEvents.StakekitError( - token = state.cryptoCurrencySymbol, - stakeKitError = it, - ), - ) onConstructError(it) return emptyList() } @@ -243,13 +231,7 @@ internal class StakingTransactionSender @AssistedInject constructor( transactionHash = transactionHash, ), ) - .onLeft { error -> - analyticsEventHandler.send( - StakingAnalyticsEvents.StakekitError( - token = stateController.value.cryptoCurrencySymbol, - stakeKitError = error, - ), - ) + .onLeft { saveUnsubmittedHashUseCase.invoke( transactionId = transaction.id, transactionHash = transactionHash, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingErrorTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingErrorTransformer.kt index 5acae90ea3..85f0bf2323 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingErrorTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingErrorTransformer.kt @@ -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()), + ) } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index 68fef18387..f53849a3f9 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -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 @@ -46,8 +47,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.* @@ -73,6 +75,7 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import timber.log.Timber import java.math.BigDecimal import java.util.concurrent.CopyOnWriteArrayList import javax.inject.Inject @@ -107,6 +110,7 @@ internal class StakingViewModel @Inject constructor( private val analyticsEventHandler: AnalyticsEventHandler, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, private val getActionsUseCase: GetActionsUseCase, + private val paramsInterceptorHolder: ParamsInterceptorHolder, @DelayedWork private val coroutineScope: CoroutineScope, savedStateHandle: SavedStateHandle, ) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents { @@ -205,6 +209,7 @@ internal class StakingViewModel @Inject constructor( override fun onCleared() { super.onCleared() + paramsInterceptorHolder.removeParamsInterceptor(StakingParamsInterceptor.ID) approvalJobHolder.cancel() feeJobHolder.cancel() sendTransactionJobHolder.cancel() @@ -239,18 +244,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) }, @@ -278,12 +277,6 @@ internal class StakingViewModel @Inject constructor( transactionsInProgress.addAll(constructedTransactions) }, onConstructError = { error -> - analyticsEventHandler.send( - StakingAnalyticsEvents.StakekitError( - token = value.cryptoCurrencySymbol, - stakeKitError = error, - ), - ) stakingEventFactory.createStakingErrorAlert(error) stateController.update(SetConfirmationStateResetAssentTransformer) }, @@ -293,7 +286,7 @@ internal class StakingViewModel @Inject constructor( stateController.update(SetConfirmationStateCompletedTransformer(txUrl)) }, onSendError = { error -> - analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol)) + analyticsEventHandler.send(StakingAnalyticsEvent.TransactionError) stakingEventFactory.createSendTransactionErrorAlert(error) stateController.update(SetConfirmationStateResetAssentTransformer) }, @@ -339,7 +332,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) } @@ -381,22 +374,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() @@ -404,9 +396,8 @@ 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( @@ -418,9 +409,7 @@ internal class StakingViewModel @Inject constructor( } 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 @@ -428,9 +417,8 @@ internal class StakingViewModel @Inject constructor( onActiveStake(rewards.first()) } else { analyticsEventHandler.send( - StakingAnalyticsEvents.ButtonValidator( + StakingAnalyticsEvent.ButtonValidator( source = StakeScreenSource.Info, - token = value.cryptoCurrencySymbol, ), ) stateController.update { @@ -470,9 +458,8 @@ internal class StakingViewModel @Inject constructor( override fun onActiveStakeAnalytic() { analyticsEventHandler.send( - StakingAnalyticsEvents.ButtonValidator( + StakingAnalyticsEvent.ButtonValidator( source = StakeScreenSource.Info, - token = value.cryptoCurrencySymbol, ), ) } @@ -522,7 +509,8 @@ internal class StakingViewModel @Inject constructor( userWalletId = userWalletId, ).fold( ifLeft = { error -> - analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol)) + Timber.e(error.toString()) + analyticsEventHandler.send(StakingAnalyticsEvent.TransactionError) stateController.update( SetConfirmationStateAssentApprovalTransformer( appCurrencyProvider = Provider { appCurrency }, @@ -543,7 +531,8 @@ internal class StakingViewModel @Inject constructor( network = tokenCryptoCurrency.network, ).fold( ifLeft = { error -> - analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol)) + Timber.e(error.toString()) + analyticsEventHandler.send(StakingAnalyticsEvent.TransactionError) stateController.update( SetConfirmationStateAssentApprovalTransformer( appCurrencyProvider = Provider { appCurrency }, @@ -673,7 +662,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 @@ -687,7 +676,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) @@ -769,10 +758,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, ), ) } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index d010c3fb81..a907455383 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-836" +tangemBlockchainSdk = "develop-841" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-391" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^