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 5ee93bbafa..089dc2bf5d 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 @@ -500,4 +500,10 @@ internal object TokensDomainModule { dispatchers = dispatchers, ) } + + @Provides + @Singleton + fun provideGetAssetRequirementsUseCase(walletManagersFacade: WalletManagersFacade): GetAssetRequirementsUseCase { + return GetAssetRequirementsUseCase(walletManagersFacade) + } } \ No newline at end of file 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 6667534f25..8eed0785bc 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 @@ -223,5 +223,6 @@ sealed class AnalyticsParam { const val NONCE = "Nonce" const val STANDARD = "Standard" const val NO_COLLECTION = "No collection" + const val EMULATION_STATUS = "Emulation Status" } } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/WcSignUseCaseDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/WcSignUseCaseDelegate.kt index af4f00d088..73dc766bbb 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/WcSignUseCaseDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/WcSignUseCaseDelegate.kt @@ -41,12 +41,6 @@ internal class WcSignUseCaseDelegate( operator fun invoke(initModel: SignModel) = channelFlow { val state = MutableStateFlow(WcSignState(initModel, WcSignStep.PreSign)) - analytics.send( - WcAnalyticEvents.SignatureRequestReceived( - rawRequest = context.rawSdkRequest, - network = context.network, - ), - ) state .onEach { newState -> channel.send(newState) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetAssetRequirementsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetAssetRequirementsUseCase.kt new file mode 100644 index 0000000000..06c27b8c64 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetAssetRequirementsUseCase.kt @@ -0,0 +1,21 @@ +package com.tangem.domain.tokens + +import arrow.core.Either +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.transaction.models.AssetRequirementsCondition +import com.tangem.domain.walletmanager.WalletManagersFacade + +class GetAssetRequirementsUseCase( + private val walletManagersFacade: WalletManagersFacade, +) { + + suspend operator fun invoke( + userWalletId: UserWalletId, + currency: CryptoCurrency, + ): Either { + return Either.Companion.catch { + walletManagersFacade.getAssetRequirements(userWalletId, currency) + } + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/BaseActionsFactory.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/BaseActionsFactory.kt index ae077ffd24..2809346d58 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/BaseActionsFactory.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/BaseActionsFactory.kt @@ -56,15 +56,24 @@ internal open class BaseActionsFactory( * * @param userWallet the user's cold wallet * @param currency the cryptocurrency to check + * @param requirementsDeferred a deferred object containing the asset requirements condition */ protected suspend fun getOnrampUnavailabilityReason( userWallet: UserWallet, currency: CryptoCurrency, + requirementsDeferred: Deferred?, ): ScenarioUnavailabilityReason { - return rampStateManager.availableForBuy( + val onrampUnavailabilityReason = rampStateManager.availableForBuy( userWallet = userWallet, cryptoCurrency = currency, ) + val shouldCheckAssetRequirements = + onrampUnavailabilityReason == ScenarioUnavailabilityReason.None && requirementsDeferred != null + return if (shouldCheckAssetRequirements) { + getReceiveScenario(requirementsDeferred.await()) + } else { + onrampUnavailabilityReason + } } /** diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/CommonActionsFactory.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/CommonActionsFactory.kt index 05b1e1969e..180041ff13 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/CommonActionsFactory.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/CommonActionsFactory.kt @@ -52,7 +52,11 @@ internal class CommonActionsFactory( } val onrampUnavailabilityReasonDeferred = async { - getOnrampUnavailabilityReason(userWallet = userWallet, currency = cryptoCurrencyStatus.currency) + getOnrampUnavailabilityReason( + userWallet = userWallet, + currency = cryptoCurrencyStatus.currency, + requirementsDeferred = requirementsDeferred, + ) } val sendUnavailabilityReasonDeferred = async { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/OutdatedDataActionsFactory.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/OutdatedDataActionsFactory.kt index adf28072be..2e0d1cbf54 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/OutdatedDataActionsFactory.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/OutdatedDataActionsFactory.kt @@ -50,7 +50,11 @@ internal class OutdatedDataActionsFactory( } val onrampUnavailabilityReasonDeferred = async { - getOnrampUnavailabilityReason(userWallet = userWallet, currency = cryptoCurrencyStatus.currency) + getOnrampUnavailabilityReason( + userWallet = userWallet, + currency = cryptoCurrencyStatus.currency, + requirementsDeferred = requirementsDeferred, + ) } val sendUnavailabilityReasonDeferred = if (sources.networkSource == StatusSource.ACTUAL) { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/UnreachableActionsFactory.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/UnreachableActionsFactory.kt index c41c4826e4..e2c0e5ff7a 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/UnreachableActionsFactory.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/UnreachableActionsFactory.kt @@ -36,7 +36,11 @@ internal class UnreachableActionsFactory( } val onrampUnavailabilityReasonDeferred = async { - getOnrampUnavailabilityReason(userWallet = userWallet, currency = cryptoCurrencyStatus.currency) + getOnrampUnavailabilityReason( + userWallet = userWallet, + currency = cryptoCurrencyStatus.currency, + requirementsDeferred = requirementsDeferred, + ) } // endregion diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt index fa4b050106..d4bff77def 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt @@ -87,11 +87,11 @@ sealed class WcAnalyticEvents( ), ) - class SignatureRequestReceived( + class TransactionDetailsOpened( rawRequest: WcSdkSessionRequest, network: Network, ) : WcAnalyticEvents( - event = "Signature Request Received", + event = "Transaction Details Opened", params = mapOf( AnalyticsParam.Key.DAPP_NAME to rawRequest.dAppMetaData.name, AnalyticsParam.Key.DAPP_URL to rawRequest.dAppMetaData.url, @@ -100,6 +100,27 @@ sealed class WcAnalyticEvents( ), ) + class SignatureRequestReceived( + rawRequest: WcSdkSessionRequest, + network: Network, + emulationStatus: EmulationStatus, + ) : WcAnalyticEvents( + event = "Signature Request Received", + params = mapOf( + AnalyticsParam.Key.DAPP_NAME to rawRequest.dAppMetaData.name, + AnalyticsParam.Key.DAPP_URL to rawRequest.dAppMetaData.url, + AnalyticsParam.Key.METHOD_NAME to rawRequest.request.method, + AnalyticsParam.Key.BLOCKCHAIN to network.name, + AnalyticsParam.Key.EMULATION_STATUS to emulationStatus.status, + ), + ) { + enum class EmulationStatus(val status: String) { + Emulated("Emulated"), + Error("Error"), + CanNotEmulate("Can't emulate"), + } + } + class SignatureRequestHandled( rawRequest: WcSdkSessionRequest, network: Network, diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index b32858e319..efcd01bc52 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -45,6 +45,7 @@ dependencies { implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) implementation(projects.domain.settings) + implementation(projects.domain.transaction.models) /** DI */ implementation(deps.hilt.android) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index 2cdae1912c..6259c9cacc 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -17,9 +17,11 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.tokenlist.TokenList import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.settings.usercountry.models.UserCountry +import com.tangem.domain.tokens.GetAssetRequirementsUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason +import com.tangem.domain.transaction.models.AssetRequirementsCondition import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent @@ -53,6 +55,7 @@ internal class OnrampTokenListModel @Inject constructor( private val getWalletsUseCase: GetWalletsUseCase, private val rampStateManager: RampStateManager, private val getUserCountryUseCase: GetUserCountryUseCase, + private val getAssetRequirementsUseCase: GetAssetRequirementsUseCase, ) : Model() { val state: StateFlow = tokenListUMController.state @@ -204,17 +207,28 @@ internal class OnrampTokenListModel @Inject constructor( return coroutineScope { map { status -> async { - val isAvailable = checkAvailabilityByOperation(status = status) + val isOperationAvailable = checkAvailabilityByOperation(status = status) val isNotMissedDerivation = status.value !is CryptoCurrencyStatus.MissedDerivation val isNotLoading = status.value !is CryptoCurrencyStatus.Loading + val requirements = getAssetRequirementsUseCase( + userWalletId = userWallet.walletId, + currency = status.currency, + ).getOrNull() - val isNotUnreachable = when (params.filterOperation) { - OnrampOperation.BUY -> true // unreachable state is available for Buy operation - OnrampOperation.SELL -> status.value !is CryptoCurrencyStatus.Unreachable - OnrampOperation.SWAP -> status.value !is CryptoCurrencyStatus.Unreachable + val isNotTrustlineRequired = requirements !is AssetRequirementsCondition.RequiredTrustline + val isNotUnreachable = status.value !is CryptoCurrencyStatus.Unreachable + + val isAvailable = when (params.filterOperation) { + OnrampOperation.BUY -> { + isNotTrustlineRequired + } // unreachable state is available for Buy operation + OnrampOperation.SELL -> isNotUnreachable + OnrampOperation.SWAP -> { + isNotUnreachable && isNotTrustlineRequired + } } - status to (isAvailable && isNotMissedDerivation && isNotLoading && isNotUnreachable) + status to (isOperationAvailable && isNotMissedDerivation && isNotLoading && isAvailable) } } .awaitAll() diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/entity/FeeSelectorUM.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/entity/FeeSelectorUM.kt index bed3633556..6386d23721 100644 --- a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/entity/FeeSelectorUM.kt +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/entity/FeeSelectorUM.kt @@ -13,11 +13,18 @@ import java.math.BigInteger @Immutable sealed class FeeSelectorUM { - data object Loading : FeeSelectorUM() + abstract val isPrimaryButtonEnabled: Boolean - data class Error(val error: GetFeeError) : FeeSelectorUM() + data object Loading : FeeSelectorUM() { + override val isPrimaryButtonEnabled = false + } + + data class Error(val error: GetFeeError) : FeeSelectorUM() { + override val isPrimaryButtonEnabled = false + } data class Content( + override val isPrimaryButtonEnabled: Boolean, val fees: TransactionFee, val feeItems: ImmutableList, val selectedFeeItem: FeeItem, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/transformers/FeeSelectorCustomValueChangedTransformer.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/transformers/FeeSelectorCustomValueChangedTransformer.kt index 9f826d2181..9418304d3b 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/transformers/FeeSelectorCustomValueChangedTransformer.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/transformers/FeeSelectorCustomValueChangedTransformer.kt @@ -1,10 +1,12 @@ package com.tangem.features.send.v2.feeselector.model.transformers +import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.features.send.v2.api.entity.FeeItem import com.tangem.features.send.v2.api.entity.FeeSelectorUM import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents +import com.tangem.utils.extensions.isZero import com.tangem.utils.transformer.Transformer import kotlinx.collections.immutable.toImmutableList @@ -30,7 +32,16 @@ internal class FeeSelectorCustomValueChangedTransformer( fee = customFeeConverter.convertBack(updatedCustomValues), customValues = updatedCustomValues, ) + + val customFeeValue = updatedCustomValues.firstOrNull() + val isNotEmptyCustom = if (customFeeValue != null) { + !customFeeValue.value.parseToBigDecimal(customFeeValue.decimals).isZero() + } else { + false + } + return state.copy( + isPrimaryButtonEnabled = isNotEmptyCustom, feeItems = state.feeItems.map { if (it is FeeItem.Custom) newCustomFee else it }.toImmutableList(), selectedFeeItem = newCustomFee, ) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/transformers/FeeSelectorLoadedTransformer.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/transformers/FeeSelectorLoadedTransformer.kt index 684d7f6611..69a2e8ece2 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/transformers/FeeSelectorLoadedTransformer.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/transformers/FeeSelectorLoadedTransformer.kt @@ -49,6 +49,7 @@ internal class FeeSelectorLoadedTransformer( val nonce = ((prevState as? FeeSelectorUM.Content)?.feeNonce as? FeeNonce.Nonce)?.nonce return FeeSelectorUM.Content( + isPrimaryButtonEnabled = true, fees = fees, feeItems = feeItems, selectedFeeItem = selectedFee, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorBlockContent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorBlockContent.kt index 6aee042366..b0f08978aa 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorBlockContent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorBlockContent.kt @@ -203,6 +203,7 @@ private class FeeSelectorUMProvider : PreviewParameterProvider { override val values: Sequence = sequenceOf( FeeSelectorUM.Content( + isPrimaryButtonEnabled = true, feeItems = persistentListOf(lowFeeItem), selectedFeeItem = lowFeeItem, feeExtraInfo = FeeExtraInfo( @@ -218,6 +219,7 @@ private class FeeSelectorUMProvider : PreviewParameterProvider { fees = TransactionFee.Single(lowFeeItem.fee), ), FeeSelectorUM.Content( + isPrimaryButtonEnabled = false, feeItems = persistentListOf(maxFeeItem), selectedFeeItem = maxFeeItem, feeExtraInfo = FeeExtraInfo( diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorModalBottomSheet.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorModalBottomSheet.kt index cbf1e1c4b1..5b1406dd0b 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorModalBottomSheet.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorModalBottomSheet.kt @@ -87,6 +87,7 @@ internal fun FeeSelectorModalBottomSheet( modifier = Modifier .fillMaxWidth() .padding(16.dp), + enabled = state.isPrimaryButtonEnabled, text = stringResourceSafe(R.string.common_done), onClick = feeSelectorIntents::onDoneClick, ) @@ -484,6 +485,7 @@ private fun FeeSelectorBS_Preview( private class FeeSelectorUMContentProvider : CollectionPreviewParameterProvider( collection = listOf( FeeSelectorUM.Content( + isPrimaryButtonEnabled = false, feeItems = persistentListOf( FeeItem.Suggested( title = stringReference("Suggested by Tangem"), @@ -494,9 +496,6 @@ private class FeeSelectorUMContentProvider : CollectionPreviewParameterProvider< FeeItem.Fast(fee = Fee.Common(Amount(value = BigDecimal("0.03"), blockchain = Blockchain.Ethereum))), customFeeItem, ), - // selectedFeeItem = FeeItem.Market( - // amount = Amount(value = BigDecimal("0.02"), blockchain = Blockchain.Ethereum), - // ), selectedFeeItem = customFeeItem, feeExtraInfo = FeeExtraInfo( isFeeApproximate = true, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/model/converters/custom/ethereum/EthereumEIPCustomFeeConverter.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/model/converters/custom/ethereum/EthereumEIPCustomFeeConverter.kt index c72ed83f63..3147b5eabc 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/model/converters/custom/ethereum/EthereumEIPCustomFeeConverter.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/model/converters/custom/ethereum/EthereumEIPCustomFeeConverter.kt @@ -19,6 +19,7 @@ import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.eth import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.ethereum.EthereumCustomFeeConverter.Companion.GAS_DECIMALS import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.ethereum.EthereumCustomFeeConverter.Companion.GIGA_DECIMALS import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.setEmpty +import com.tangem.utils.extensions.isZero import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -91,7 +92,7 @@ internal class EthereumEIPCustomFeeConverter( val mutableCustomValues = customValues.toMutableList() return mutableCustomValues.apply { when (index) { - FEE_AMOUNT -> setOnAmountChange(value, index) + FEE_AMOUNT -> setOnAmountChange(feeValue, value, index) MAX_FEE -> setOnMaxFeeChange(value, index) GAS_LIMIT -> setOnGasLimitChange(value, index) else -> set(index, this[index].copy(value = value)) @@ -99,8 +100,12 @@ internal class EthereumEIPCustomFeeConverter( }.toImmutableList() } - private fun MutableList.setOnAmountChange(value: String, index: Int) { - val gasLimit = this[GAS_LIMIT].value.parseToBigDecimal(this[GAS_LIMIT].decimals) + private fun MutableList.setOnAmountChange( + feeValue: Fee.Ethereum.EIP1559, + value: String, + index: Int, + ) { + val gasLimitRaw = this[GAS_LIMIT].value.parseToBigDecimal(this[GAS_LIMIT].decimals) if (value.isBlank()) { setEmpty(FEE_AMOUNT) setEmpty(MAX_FEE) @@ -108,6 +113,27 @@ internal class EthereumEIPCustomFeeConverter( val newFeeAmountDecimal = value.parseToBigDecimal(this[FEE_AMOUNT].decimals) val newFeeAmount = newFeeAmountDecimal.movePointRight(GIGA_DECIMALS) // from ETH to GWEI + val gasLimit = if (gasLimitRaw.isZero()) { + val gasLimitTemp = feeValue.gasLimit.toBigDecimal() + set( + index = GAS_LIMIT, + element = this[GAS_LIMIT].copy(value = gasLimitTemp.parseBigDecimal(GIGA_DECIMALS)), + ) + gasLimitTemp + } else { + gasLimitRaw + } + + if (this[PRIORITY_FEE].value.isBlank()) { + set( + index = PRIORITY_FEE, + element = this[PRIORITY_FEE].copy( + value = feeValue.priorityFee.toBigDecimal().movePointLeft(GIGA_DECIMALS) + .parseBigDecimal(GIGA_DECIMALS), + ), + ) + } + val newMaxFee = newFeeAmount.divide(gasLimit, this[MAX_FEE].decimals, RoundingMode.HALF_UP) set( diff --git a/features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/SendConfirmationNotificationsTransformerV2Test.kt b/features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/SendConfirmationNotificationsTransformerV2Test.kt index 859eb4e9eb..873cfb305a 100644 --- a/features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/SendConfirmationNotificationsTransformerV2Test.kt +++ b/features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/SendConfirmationNotificationsTransformerV2Test.kt @@ -242,6 +242,7 @@ class SendConfirmationNotificationsTransformerV2Test { ) val transactionFee = TransactionFee.Single(fee) return FeeSelectorUM.Content( + isPrimaryButtonEnabled = true, fees = transactionFee, feeItems = persistentListOf(FeeItem.Market(fee)), selectedFeeItem = FeeItem.Market(fee), @@ -282,6 +283,7 @@ class SendConfirmationNotificationsTransformerV2Test { priority = priorityFee, ) return FeeSelectorUM.Content( + isPrimaryButtonEnabled = true, fees = transactionFee, feeItems = persistentListOf( FeeItem.Custom( @@ -364,6 +366,7 @@ class SendConfirmationNotificationsTransformerV2Test { priority = fee, ) return FeeSelectorUM.Content( + isPrimaryButtonEnabled = true, fees = transactionFee, feeItems = persistentListOf( FeeItem.Custom( @@ -434,6 +437,7 @@ class SendConfirmationNotificationsTransformerV2Test { priority = priorityFee, ) return FeeSelectorUM.Content( + isPrimaryButtonEnabled = true, fees = transactionFee, feeItems = persistentListOf( FeeItem.Custom( diff --git a/features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/TransformersComparisonTest.kt b/features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/TransformersComparisonTest.kt index f197f57b08..79cba32b78 100644 --- a/features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/TransformersComparisonTest.kt +++ b/features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/TransformersComparisonTest.kt @@ -72,6 +72,7 @@ class TransformersComparisonTest { val contentV1 = resultV1 as ConfirmUM.Content val contentV2 = resultV2 as ConfirmUM.Content + assertThat(contentV1.isPrimaryButtonEnabled).isEqualTo(contentV2.isPrimaryButtonEnabled) assertThat(contentV1.notifications.size).isEqualTo(contentV2.notifications.size) assertThat(contentV1.sendingFooter).isEqualTo(contentV2.sendingFooter) assertThat(contentV1.isPrimaryButtonEnabled).isEqualTo(contentV2.isPrimaryButtonEnabled) @@ -117,6 +118,8 @@ class TransformersComparisonTest { val contentV1 = resultV1 as ConfirmUM.Content val contentV2 = resultV2 as ConfirmUM.Content + assertThat(contentV1.isPrimaryButtonEnabled).isTrue() + assertThat(contentV2.isPrimaryButtonEnabled).isTrue() assertThat(contentV1.notifications).hasSize(1) assertThat(contentV2.notifications).hasSize(1) assertThat(contentV1.notifications.first()).isInstanceOf(NotificationUM.Warning.FeeTooLow::class.java) @@ -165,6 +168,8 @@ class TransformersComparisonTest { val contentV1 = resultV1 as ConfirmUM.Content val contentV2 = resultV2 as ConfirmUM.Content + assertThat(contentV1.isPrimaryButtonEnabled).isTrue() + assertThat(contentV2.isPrimaryButtonEnabled).isTrue() assertThat(contentV1.notifications).hasSize(1) assertThat(contentV2.notifications).hasSize(1) assertThat(contentV1.notifications.first()).isInstanceOf(NotificationUM.Warning.TooHigh::class.java) @@ -215,6 +220,8 @@ class TransformersComparisonTest { val contentV1 = resultV1 as ConfirmUM.Content val contentV2 = resultV2 as ConfirmUM.Content + assertThat(contentV1.isPrimaryButtonEnabled).isTrue() + assertThat(contentV2.isPrimaryButtonEnabled).isTrue() assertThat(contentV1.notifications).hasSize(2) assertThat(contentV2.notifications).hasSize(2) @@ -268,6 +275,7 @@ class TransformersComparisonTest { assertThat(contentV1.notifications).isEmpty() assertThat(contentV2.notifications).isEmpty() + assertThat(contentV1.isPrimaryButtonEnabled).isEqualTo(contentV2.isPrimaryButtonEnabled) assertThat(contentV1.sendingFooter).isEqualTo(contentV2.sendingFooter) } @@ -377,6 +385,7 @@ class TransformersComparisonTest { ) val transactionFee = TransactionFee.Single(fee) return FeeSelectorUMV2.Content( + isPrimaryButtonEnabled = true, fees = transactionFee, feeItems = persistentListOf( FeeItem.Market(fee), @@ -470,6 +479,7 @@ class TransformersComparisonTest { priority = fee, ) return FeeSelectorUMV2.Content( + isPrimaryButtonEnabled = true, fees = transactionFee, feeItems = persistentListOf( FeeItem.Custom( @@ -605,6 +615,7 @@ class TransformersComparisonTest { priority = priorityFee, ) return FeeSelectorUMV2.Content( + isPrimaryButtonEnabled = true, fees = transactionFee, feeItems = persistentListOf( FeeItem.Custom( @@ -740,6 +751,7 @@ class TransformersComparisonTest { priority = priorityFee, ) return FeeSelectorUMV2.Content( + isPrimaryButtonEnabled = true, fees = transactionFee, feeItems = persistentListOf( FeeItem.Custom( @@ -839,6 +851,7 @@ class TransformersComparisonTest { ) val transactionFee = TransactionFee.Single(fee) return FeeSelectorUMV2.Content( + isPrimaryButtonEnabled = true, fees = transactionFee, feeItems = persistentListOf( FeeItem.Market(fee), diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt index 4ea2e22fea..e730666446 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt @@ -379,6 +379,7 @@ private fun SendWithSwapSuccessContent_Preview() { ), feeFiatRateUM = null, feeNonce = FeeNonce.None, + isPrimaryButtonEnabled = false, ), confirmUM = ConfirmUM.Success( isPrimaryButtonEnabled = true, diff --git a/features/swap/data/build.gradle.kts b/features/swap/data/build.gradle.kts index 9c2a64eebe..5b0d422759 100644 --- a/features/swap/data/build.gradle.kts +++ b/features/swap/data/build.gradle.kts @@ -41,6 +41,7 @@ dependencies { implementation(projects.domain.models) implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) + implementation(projects.domain.transaction.models) /** Data */ implementation(projects.data.common) diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt index 69f6fe5d7e..a23bc00f4b 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt @@ -26,10 +26,11 @@ import com.tangem.datasource.crypto.DataSignatureVerifier import com.tangem.datasource.exchangeservice.swap.ExpressUtils import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.transaction.models.AssetRequirementsCondition +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.feature.swap.converters.* import com.tangem.feature.swap.domain.api.SwapRepository import com.tangem.feature.swap.domain.models.ExpressDataError @@ -134,7 +135,12 @@ internal class DefaultSwapRepository( contractAddress = initialCurrency.contractAddress, network = initialCurrency.network, ) - val currenciesList = currencyList.map { leastTokenInfoConverter.convert(it) } + val currenciesList = currencyList + .filter { + val requirements = walletManagersFacade.getAssetRequirements(userWallet.walletId, it) + requirements !is AssetRequirementsCondition.RequiredTrustline + } + .map { leastTokenInfoConverter.convert(it) } val pairsDeferred = async { getPairsInternal( diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index cc8f64ca53..abfa93bd3a 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -31,6 +31,7 @@ import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.transaction.error.GetFeeError +import com.tangem.domain.transaction.models.AssetRequirementsCondition import com.tangem.domain.transaction.usecase.* import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.wallets.usecase.GetUserWalletUseCase @@ -79,6 +80,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( private val getEthSpecificFeeUseCase: GetEthSpecificFeeUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase, + private val getAssetRequirementsUseCase: GetAssetRequirementsUseCase, private val amountFormatter: AmountFormatter, @Assisted private val userWalletId: UserWalletId, ) : SwapInteractor { @@ -139,7 +141,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( ) } - private fun getToCurrenciesGroup( + private suspend fun getToCurrenciesGroup( currency: CryptoCurrency, leastPairs: List, cryptoCurrenciesList: List, @@ -171,15 +173,18 @@ internal class SwapInteractorImpl @AssistedInject constructor( ) } - private fun findProvidersForPair( + private suspend fun findProvidersForPair( cryptoCurrencyStatuses: CryptoCurrencyStatus, swapPairsLeastList: List, tokenInfoForAvailable: (SwapPairLeast) -> LeastTokenInfo, ): List? { + val requirements = getAssetRequirementsUseCase.invoke(userWalletId, cryptoCurrencyStatuses.currency).getOrNull() + return swapPairsLeastList.firstNotNullOfOrNull { val listTokenInfo = tokenInfoForAvailable(it) if (cryptoCurrencyStatuses.currency.network.backendId == listTokenInfo.network && - cryptoCurrencyStatuses.currency.getContractAddress() == listTokenInfo.contractAddress + cryptoCurrencyStatuses.currency.getContractAddress() == listTokenInfo.contractAddress && + requirements !is AssetRequirementsCondition.RequiredTrustline ) { it.providers } else { diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcSelectNetworksComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcSelectNetworksComponent.kt index 633c8859bc..b5e00c8355 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcSelectNetworksComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcSelectNetworksComponent.kt @@ -32,12 +32,13 @@ import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.TangemSwitch import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent -import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetWithFooter import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.domain.models.network.Network @@ -145,8 +146,11 @@ private fun MissingRequiredBlock(missingNetworks: ImmutableList( - containerColor = TangemTheme.colors.background.tertiary, - config = TangemBottomSheetConfig( - isShown = true, - onDismissRequest = {}, - content = TangemBottomSheetConfigContent.Empty, - ), - title = { - TangemModalBottomSheetTitle( - title = stringReference("Choose wallet"), - onEndClick = {}, - endIconRes = R.drawable.ic_close_24, - ) - }, - content = { - WcSelectNetworksContent( - modifier = Modifier - .background(TangemTheme.colors.background.tertiary) - .padding(horizontal = TangemTheme.dimens.spacing16), - state = state, - ) - }, - ) + WcSelectNetworksBS(state = state, onBack = {}, onDismiss = {}) } } @@ -328,7 +310,7 @@ private class WcSelectNetworksProvider : CollectionPreviewParameterProvider) : WcNetworksInfo() } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcSelectNetworksUM.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcSelectNetworksUM.kt index 26a00e9e6e..5c2ed43886 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcSelectNetworksUM.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcSelectNetworksUM.kt @@ -8,5 +8,5 @@ internal data class WcSelectNetworksUM( val available: ImmutableList, val notAdded: ImmutableList, val onDone: () -> Unit, - val doneButtonEnabled: Boolean = true, + val doneButtonEnabled: Boolean, ) \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt index c745515dc4..bbac86dbfd 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt @@ -1,7 +1,9 @@ package com.tangem.features.walletconnect.connections.model import androidx.compose.runtime.Stable -import com.arkivanov.decompose.router.stack.* +import com.arkivanov.decompose.router.stack.StackNavigation +import com.arkivanov.decompose.router.stack.pop +import com.arkivanov.decompose.router.stack.pushNew import com.domain.blockaid.models.dapp.CheckDAppResult import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped @@ -10,18 +12,16 @@ import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.message.SnackbarMessage import com.tangem.core.ui.message.ToastMessage import com.tangem.domain.models.network.Network -import com.tangem.domain.walletconnect.WcAnalyticEvents -import com.tangem.domain.walletconnect.model.WcPairError -import com.tangem.domain.walletconnect.model.WcPairRequest -import com.tangem.domain.walletconnect.model.WcSessionApprove -import com.tangem.domain.walletconnect.model.WcSessionProposal -import com.tangem.domain.walletconnect.usecase.pair.WcPairState -import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.isLocked import com.tangem.domain.models.wallet.isMultiCurrency +import com.tangem.domain.walletconnect.WcAnalyticEvents +import com.tangem.domain.walletconnect.model.* +import com.tangem.domain.walletconnect.usecase.pair.WcPairState +import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.walletconnect.connections.components.WcPairComponent import com.tangem.features.walletconnect.connections.components.WcSelectNetworksComponent @@ -91,7 +91,9 @@ internal class WcPairModel @Inject constructor( appInfoUiState.transformerUpdate( WcConnectButtonProgressTransformer(showProgress = false), ) - pairState.result.onLeft(::processError) + pairState.result + .onLeft(::processError) + .onRight(::processSuccessfullyConnected) router.pop() } is WcPairState.Error -> { @@ -196,6 +198,13 @@ internal class WcPairModel @Inject constructor( stackNavigation.pushNew(WcAppInfoRoutes.Alert(WcAppInfoRoutes.Alert.Type.Verified(appName))) } + private fun processSuccessfullyConnected(session: WcSession) { + // TODO: [REDACTED_JIRA] localization + messageSender.send( + SnackbarMessage(message = stringReference("Connected to ${session.sdkModel.appMetaData.name}")), + ) + } + private fun processError(error: WcPairError) { val alert = when (error) { is WcPairError.UnsupportedDApp -> { @@ -236,6 +245,8 @@ internal class WcPairModel @Inject constructor( WcNetworksSelectedTransformer( missingNetworks = proposalNetwork.missingRequired, requiredNetworks = proposalNetwork.required, + availableNetworks = proposalNetwork.available, + notAddedNetworks = proposalNetwork.notAdded, additionallyEnabledNetworks = additionallyEnabledNetworks, ), ) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcSelectNetworksModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcSelectNetworksModel.kt index 1def1766ce..06a987c536 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcSelectNetworksModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcSelectNetworksModel.kt @@ -60,6 +60,9 @@ internal class WcSelectNetworksModel @Inject constructor( private fun getInitialState(): WcSelectNetworksUM { val editEnabled = params.missingRequiredNetworks.isEmpty() + val doneButtonEnabled = params.missingRequiredNetworks.isEmpty() && + (params.requiredNetworks.isNotEmpty() || params.availableNetworks.isNotEmpty()) + return WcSelectNetworksUM( missing = params.missingRequiredNetworks.map { network -> WcNetworkInfoItem.Required( @@ -98,7 +101,7 @@ internal class WcSelectNetworksModel @Inject constructor( ) }.toImmutableList(), onDone = ::onDone, - doneButtonEnabled = editEnabled, + doneButtonEnabled = doneButtonEnabled, ) } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoTransformer.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoTransformer.kt index 4527b5e065..41dd25542e 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoTransformer.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoTransformer.kt @@ -2,8 +2,8 @@ package com.tangem.features.walletconnect.connections.model.transformers import com.domain.blockaid.models.dapp.CheckDAppResult import com.tangem.domain.models.network.Network -import com.tangem.domain.walletconnect.model.WcSessionProposal import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.walletconnect.model.WcSessionProposal import com.tangem.features.walletconnect.connections.entity.WcAppInfoSecurityNotification import com.tangem.features.walletconnect.connections.entity.WcAppInfoUM import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig @@ -37,13 +37,22 @@ internal class WcAppInfoTransformer( value = WcNetworksInfoConverter.Input( missingNetworks = proposalNetwork.missingRequired, requiredNetworks = proposalNetwork.required, + availableNetworks = proposalNetwork.available, + notAddedNetworks = proposalNetwork.notAdded, additionallyEnabledNetworks = additionallyEnabledNetworks, ), ), onNetworksClick = onNetworksClick, connectButtonConfig = WcPrimaryButtonConfig( showProgress = false, - enabled = proposalNetwork.missingRequired.isEmpty(), + enabled = WcConnectButtonAvailabilityConverter.convert( + WcConnectButtonAvailabilityConverter.Input( + missingNetworks = proposalNetwork.missingRequired, + requiredNetworks = proposalNetwork.required, + availableNetworks = proposalNetwork.available, + selectedNetworks = additionallyEnabledNetworks, + ), + ), onClick = { onConnect(dAppSession.securityStatus) }, ), onDismiss = onDismiss, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoWalletChangedTransformer.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoWalletChangedTransformer.kt index b57674f3f1..54d753bd72 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoWalletChangedTransformer.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoWalletChangedTransformer.kt @@ -1,8 +1,8 @@ package com.tangem.features.walletconnect.connections.model.transformers import com.tangem.domain.models.network.Network -import com.tangem.domain.walletconnect.model.WcSessionProposal import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.walletconnect.model.WcSessionProposal import com.tangem.features.walletconnect.connections.entity.WcAppInfoUM import com.tangem.utils.transformer.Transformer @@ -19,11 +19,20 @@ internal class WcAppInfoWalletChangedTransformer( WcNetworksInfoConverter.Input( missingNetworks = proposalNetwork.missingRequired, requiredNetworks = proposalNetwork.required, + availableNetworks = proposalNetwork.available, + notAddedNetworks = proposalNetwork.notAdded, additionallyEnabledNetworks = additionallyEnabledNetworks, ), ), connectButtonConfig = prevState.connectButtonConfig.copy( - enabled = proposalNetwork.missingRequired.isEmpty(), + enabled = WcConnectButtonAvailabilityConverter.convert( + WcConnectButtonAvailabilityConverter.Input( + missingNetworks = proposalNetwork.missingRequired, + requiredNetworks = proposalNetwork.required, + availableNetworks = proposalNetwork.available, + selectedNetworks = additionallyEnabledNetworks, + ), + ), ), ) } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcConnectButtonAvailabilityConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcConnectButtonAvailabilityConverter.kt new file mode 100644 index 0000000000..72042b2e79 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcConnectButtonAvailabilityConverter.kt @@ -0,0 +1,24 @@ +package com.tangem.features.walletconnect.connections.model.transformers + +import com.tangem.domain.models.network.Network +import com.tangem.utils.converter.Converter + +@Suppress("DestructuringDeclarationWithTooManyEntries") +internal object WcConnectButtonAvailabilityConverter : Converter { + + override fun convert(value: Input): Boolean { + val missing = value.missingNetworks + val required = value.requiredNetworks + val available = value.availableNetworks + val selected = value.selectedNetworks + + return missing.isEmpty() && (required.isNotEmpty() || available.isNotEmpty() || selected.isNotEmpty()) + } + + data class Input( + val missingNetworks: Set, + val requiredNetworks: Set, + val availableNetworks: Set, + val selectedNetworks: Set, + ) +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcNetworksInfoConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcNetworksInfoConverter.kt index 26934b08b7..4d690bc279 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcNetworksInfoConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcNetworksInfoConverter.kt @@ -7,31 +7,38 @@ import com.tangem.features.walletconnect.connections.entity.WcNetworksInfo import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toImmutableList +@Suppress("DestructuringDeclarationWithTooManyEntries") internal object WcNetworksInfoConverter : Converter { override fun convert(value: Input): WcNetworksInfo { - return if (value.missingNetworks.isNotEmpty()) { - WcNetworksInfo.MissingRequiredNetworkInfo( - networks = value.missingNetworks.joinToString { it.name }, - ) - } else { - WcNetworksInfo.ContainsAllRequiredNetworks( - items = (value.requiredNetworks + value.additionallyEnabledNetworks) - .map { + val missing = value.missingNetworks + val required = value.requiredNetworks + val available = value.availableNetworks + val notAdded = value.notAddedNetworks + val additionallyEnabled = value.additionallyEnabledNetworks + return when { + missing.isNotEmpty() -> WcNetworksInfo.MissingRequiredNetworkInfo(missing.joinToString { it.name }) + required.isEmpty() && available.isEmpty() && notAdded.isNotEmpty() -> WcNetworksInfo.NoneNetworksAdded + else -> { + val combinedNetworks = required + additionallyEnabled + WcNetworksInfo.ContainsAllRequiredNetworks( + items = combinedNetworks.map { network -> WcNetworkInfoItem.Required( - id = it.rawId, - icon = it.iconResId, - name = it.name, - symbol = it.currencySymbol, + id = network.rawId, + icon = network.iconResId, + name = network.name, + symbol = network.currencySymbol, ) - } - .toImmutableList(), - ) + }.toImmutableList(), + ) + } } } data class Input( val missingNetworks: Set, val requiredNetworks: Set, + val availableNetworks: Set, + val notAddedNetworks: Set, val additionallyEnabledNetworks: Set, ) } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcNetworksSelectedTransformer.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcNetworksSelectedTransformer.kt index ee953b6130..7c916d8acd 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcNetworksSelectedTransformer.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcNetworksSelectedTransformer.kt @@ -7,19 +7,29 @@ import com.tangem.utils.transformer.Transformer internal class WcNetworksSelectedTransformer( private val missingNetworks: Set, private val requiredNetworks: Set, + private val availableNetworks: Set, + private val notAddedNetworks: Set, private val additionallyEnabledNetworks: Set, ) : Transformer { override fun transform(prevState: WcAppInfoUM): WcAppInfoUM { val contentState = prevState as? WcAppInfoUM.Content ?: return prevState return contentState.copy( connectButtonConfig = contentState.connectButtonConfig.copy( - enabled = missingNetworks.isEmpty() && - (requiredNetworks.isNotEmpty() || additionallyEnabledNetworks.isNotEmpty()), + enabled = WcConnectButtonAvailabilityConverter.convert( + WcConnectButtonAvailabilityConverter.Input( + missingNetworks = missingNetworks, + requiredNetworks = requiredNetworks, + availableNetworks = availableNetworks, + selectedNetworks = additionallyEnabledNetworks, + ), + ), ), networksInfo = WcNetworksInfoConverter.convert( value = WcNetworksInfoConverter.Input( missingNetworks = missingNetworks, requiredNetworks = requiredNetworks, + availableNetworks = availableNetworks, + notAddedNetworks = notAddedNetworks, additionallyEnabledNetworks = additionallyEnabledNetworks, ), ), diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/InternalComponents.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/InternalComponents.kt index 3150cbc5f6..0096e3ef3c 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/InternalComponents.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/InternalComponents.kt @@ -62,6 +62,8 @@ internal fun WcAppInfoItem( text = title, color = TangemTheme.colors.text.primary1, style = TangemTheme.typography.h3, + maxLines = 1, + overflow = TextOverflow.Ellipsis, ) if (verifiedDAppState is VerifiedDAppState.Verified) { Icon( diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcAppInfoBS.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcAppInfoBS.kt index af9a95cdf2..27957362b7 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcAppInfoBS.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcAppInfoBS.kt @@ -35,10 +35,7 @@ import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTi import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetWithFooter import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.components.notifications.NotificationConfig -import com.tangem.core.ui.extensions.clickableSingle -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringResourceSafe -import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -284,6 +281,19 @@ private fun WcAppInfoSecondBlock(state: WcAppInfoUM.Content, modifier: Modifier ) when (state.networksInfo) { is WcNetworksInfo.ContainsAllRequiredNetworks -> Unit + is WcNetworksInfo.NoneNetworksAdded -> { + HorizontalDivider(thickness = 1.dp, color = TangemTheme.colors.stroke.primary) + Notification( + config = NotificationConfig( + iconResId = R.drawable.ic_alert_circle_24, + // TODO: [REDACTED_JIRA] localization + title = stringReference("Specify selected networks"), + subtitle = stringReference("At least one network is required for dApp connection"), + ), + iconTint = TangemTheme.colors.icon.attention, + containerColor = TangemTheme.colors.background.action, + ) + } is WcNetworksInfo.MissingRequiredNetworkInfo -> { HorizontalDivider(thickness = 1.dp, color = TangemTheme.colors.stroke.primary) Notification( @@ -370,6 +380,7 @@ private fun SelectNetworksBlock(networksInfo: WcNetworksInfo, modifier: Modifier when (networksInfo) { is WcNetworksInfo.ContainsAllRequiredNetworks -> NetworkIcons(items = networksInfo.items) is WcNetworksInfo.MissingRequiredNetworkInfo -> Unit + is WcNetworksInfo.NoneNetworksAdded -> Unit } Icon( modifier = Modifier.size(width = 18.dp, height = 24.dp), diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcConnectionsContent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcConnectionsContent.kt index b0a695365f..25acf5033f 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcConnectionsContent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcConnectionsContent.kt @@ -193,6 +193,7 @@ private fun ConnectionItem(connection: WcConnectionsUM, modifier: Modifier = Mod private fun AppInfoItem(appInfo: WcConnectedAppInfo, modifier: Modifier = Modifier) { Row( modifier = modifier, + verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { AsyncImage( @@ -213,9 +214,12 @@ private fun AppInfoItem(appInfo: WcConnectedAppInfo, modifier: Modifier = Modifi verticalAlignment = Alignment.CenterVertically, ) { Text( + modifier = Modifier.weight(1f, fill = false), text = appInfo.name, color = TangemTheme.colors.text.primary1, style = TangemTheme.typography.subtitle2, + maxLines = 1, + overflow = TextOverflow.Ellipsis, ) if (appInfo.verifiedState is VerifiedDAppState.Verified) { Icon( diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/preview/WcConnectionsPreviewData.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/preview/WcConnectionsPreviewData.kt index d2cbeb2a8c..a527826b41 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/preview/WcConnectionsPreviewData.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/preview/WcConnectionsPreviewData.kt @@ -41,7 +41,7 @@ internal object WcConnectionsPreviewData { walletName = "Bitcoin", connectedApps = persistentListOf( WcConnectedAppInfo( - name = "React app", + name = "React app React app React app React app React app", iconUrl = "$BASE_URL/LiteCoin.png", subtitle = "https://react-app.walletconnect.com/", verifiedState = VerifiedDAppState.Verified {}, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt index dc8aa4bc23..1684d836c3 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt @@ -3,12 +3,14 @@ package com.tangem.features.walletconnect.transaction.model import androidx.compose.runtime.Stable import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.pushNew +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.clipboard.ClipboardManager +import com.tangem.domain.walletconnect.WcAnalyticEvents import com.tangem.domain.walletconnect.WcRequestUseCaseFactory import com.tangem.domain.walletconnect.usecase.method.WcAddNetworkUseCase import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams @@ -24,6 +26,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject +import kotlin.properties.Delegates @Suppress("LongParameterList") @Stable @@ -34,6 +37,7 @@ internal class WcAddNetworkModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val router: Router, private val clipboardManager: ClipboardManager, + private val analytics: AnalyticsEventHandler, private val useCaseFactory: WcRequestUseCaseFactory, private val wcAddEthereumChainUMConverter: WcAddEthereumChainUMConverter, ) : Model(), WcCommonTransactionModel { @@ -44,10 +48,11 @@ internal class WcAddNetworkModel @Inject constructor( val stackNavigation = StackNavigation() private val params = paramsContainer.require() + private var useCase by Delegates.notNull() init { modelScope.launch { - val useCase: WcAddNetworkUseCase = useCaseFactory.createUseCase(params.rawRequest) + useCase = useCaseFactory.createUseCase(params.rawRequest) .onLeft { router.push(WcHandleMethodErrorConverter.convert(it)) } .getOrNull() ?: return@launch _uiState.emit( @@ -75,6 +80,12 @@ internal class WcAddNetworkModel @Inject constructor( } fun showTransactionRequest() { + analytics.send( + WcAnalyticEvents.TransactionDetailsOpened( + rawRequest = useCase.rawSdkRequest, + network = useCase.network, + ), + ) stackNavigation.pushNew(WcTransactionRoutes.TransactionRequestInfo) } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt index 419551a54d..0e9db77189 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt @@ -5,6 +5,7 @@ import arrow.core.Either import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.pop import com.arkivanov.decompose.router.stack.pushNew +import com.domain.blockaid.models.transaction.SimulationResult import com.domain.blockaid.models.transaction.ValidationResult import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.TransactionFee @@ -27,6 +28,7 @@ import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.walletconnect.WcAnalyticEvents +import com.tangem.domain.walletconnect.WcAnalyticEvents.SignatureRequestReceived.EmulationStatus import com.tangem.domain.walletconnect.WcRequestUseCaseFactory import com.tangem.domain.walletconnect.model.WcRequestError import com.tangem.domain.walletconnect.model.WcRequestError.Companion.message @@ -88,6 +90,7 @@ internal class WcSendTransactionModel @Inject constructor( private var wcApproval: WcApproval? = null private var sign: () -> Unit = {} private val feeReloadState = MutableStateFlow(false) + private val signatureReceivedAnalyticsSendState = MutableStateFlow(false) init { @Suppress("UnusedPrivateMember") @@ -121,6 +124,8 @@ internal class WcSendTransactionModel @Inject constructor( .collectLatest { (signState, securityCheck) -> if (signingIsDone(signState, useCase)) return@collectLatest + sendSignatureReceivedAnalytics(useCase, securityCheck) + this@WcSendTransactionModel.signState = signState val isSecurityCheckContent = securityCheck is Lce.Content val isApprovalMethod = isSecurityCheckContent && @@ -257,6 +262,12 @@ internal class WcSendTransactionModel @Inject constructor( } fun showTransactionRequest() { + analytics.send( + WcAnalyticEvents.TransactionDetailsOpened( + rawRequest = useCase.rawSdkRequest, + network = useCase.network, + ), + ) stackNavigation.pushNew(WcTransactionRoutes.TransactionRequestInfo) } @@ -358,4 +369,30 @@ internal class WcSendTransactionModel @Inject constructor( private fun copyData(text: String) { clipboardManager.setText(text = text, isSensitive = true) } + + private fun sendSignatureReceivedAnalytics( + useCase: WcSignUseCase<*>, + securityCheck: Lce, + ) { + if (signatureReceivedAnalyticsSendState.value) return + + val emulationStatus = when (securityCheck) { + is Lce.Content -> when (securityCheck.content.result.simulation) { + SimulationResult.FailedToSimulate -> EmulationStatus.CanNotEmulate + is SimulationResult.Success -> EmulationStatus.Emulated + } + is Lce.Error -> EmulationStatus.Error + is Lce.Loading -> return + } + + analytics.send( + WcAnalyticEvents.SignatureRequestReceived( + rawRequest = useCase.rawSdkRequest, + network = useCase.network, + emulationStatus = emulationStatus, + ), + ) + + signatureReceivedAnalyticsSendState.value = true + } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt index cdcb80f9f4..1283f3e84f 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt @@ -4,12 +4,14 @@ import androidx.compose.runtime.Stable import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.pop import com.arkivanov.decompose.router.stack.pushNew +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.clipboard.ClipboardManager +import com.tangem.domain.walletconnect.WcAnalyticEvents import com.tangem.domain.walletconnect.WcRequestUseCaseFactory import com.tangem.domain.walletconnect.model.WcEthMethod import com.tangem.domain.walletconnect.model.WcSolanaMethod @@ -32,6 +34,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject +import kotlin.properties.Delegates @Suppress("LongParameterList") @Stable @@ -42,6 +45,7 @@ internal class WcSignTransactionModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val router: Router, private val clipboardManager: ClipboardManager, + private val analytics: AnalyticsEventHandler, private val useCaseFactory: WcRequestUseCaseFactory, private val signTypedDataUMConverter: WcSignTypedDataUMConverter, private val signTransactionUMConverter: WcSignTransactionUMConverter, @@ -54,9 +58,11 @@ internal class WcSignTransactionModel @Inject constructor( val stackNavigation = StackNavigation() + private var useCase by Delegates.notNull() + init { modelScope.launch { - val useCase = useCaseFactory.createUseCase(params.rawRequest) + useCase = useCaseFactory.createUseCase(params.rawRequest) .onLeft { router.push(WcHandleMethodErrorConverter.convert(it)) } .getOrNull() ?: return@launch useCase.invoke() @@ -109,6 +115,12 @@ internal class WcSignTransactionModel @Inject constructor( } fun showTransactionRequest() { + analytics.send( + WcAnalyticEvents.TransactionDetailsOpened( + rawRequest = useCase.rawSdkRequest, + network = useCase.network, + ), + ) stackNavigation.pushNew(WcTransactionRoutes.TransactionRequestInfo) } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt index 0a0a1f1eac..cf5f3fee9f 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt @@ -185,9 +185,9 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide onDismiss = {}, onSend = {}, appInfo = WcTransactionAppInfoContentUM( - appName = "React App", + appName = "React App React App ReactApp ReactApp ReactApp", appIcon = "", - verifiedState = VerifiedDAppState.Verified {}, + verifiedState = VerifiedDAppState.Unknown, appSubtitle = "react-app.walletconnect.com", ), estimatedWalletChanges = WcSendReceiveTransactionCheckResultsUM( @@ -225,7 +225,7 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide onDismiss = {}, onSend = {}, appInfo = WcTransactionAppInfoContentUM( - appName = "React App", + appName = "React App React App React App React App React App", appIcon = "", verifiedState = VerifiedDAppState.Verified {}, appSubtitle = "react-app.walletconnect.com",