Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-15 18:46:49 +05:00
parent 16156c559a
commit bbd0c7927d
9 changed files with 133 additions and 32 deletions

View file

@ -16,13 +16,16 @@ dependencies {
implementation(projects.features.walletconnect.api)
implementation(projects.features.sendV2.api)
/** Core */
implementation(projects.core.configToggles)
implementation(projects.core.decompose)
implementation(projects.core.ui)
/** Common */
implementation(projects.common.routing)
implementation(projects.common.ui)
/** Core */
implementation(projects.core.analytics)
implementation(projects.core.configToggles)
implementation(projects.core.decompose)
implementation(projects.core.navigation)
implementation(projects.core.ui)
/** Domain models */
implementation(projects.domain.appCurrency.models)

View file

@ -11,4 +11,5 @@ internal data class WcSpendAllowanceUM(
val tokenSymbol: String,
val tokenImageUrl: String?,
val networkIconRes: Int?,
val onLearnMoreClicked: () -> Unit,
) : TangemBottomSheetConfigContent

View file

@ -16,6 +16,7 @@ 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.navigation.url.UrlOpener
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2.Icon.Type
@ -55,6 +56,7 @@ import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransacti
import com.tangem.features.walletconnect.transaction.routes.WcTransactionRoutes
import com.tangem.features.walletconnect.transaction.ui.blockaid.WcSendAndReceiveBlockAidUiConverter
import com.tangem.features.walletconnect.utils.WcNotificationsFactory
import com.tangem.utils.SupportedLanguages
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
@ -74,11 +76,11 @@ internal class WcSendTransactionModel @Inject constructor(
private val clipboardManager: ClipboardManager,
private val useCaseFactory: WcRequestUseCaseFactory,
private val converter: WcSendTransactionUMConverter,
private val blockAidUiConverter: WcSendAndReceiveBlockAidUiConverter,
private val getFeeUseCase: GetFeeUseCase,
private val getNetworkCoinUseCase: GetNetworkCoinStatusUseCase,
private val notificationsFactory: WcNotificationsFactory,
private val analytics: AnalyticsEventHandler,
private val urlOpener: UrlOpener,
) : Model(), WcCommonTransactionModel, FeeSelectorModelCallback {
private val params = paramsContainer.require<WcTransactionModelParams>()
@ -94,6 +96,7 @@ internal class WcSendTransactionModel @Inject constructor(
private var signState: WcSignState<*> by Delegates.notNull()
private var wcApproval: WcApproval? = null
private var sign: () -> Unit = {}
private val blockAidUiConverter = WcSendAndReceiveBlockAidUiConverter()
private val feeReloadState = MutableStateFlow(false)
private val signatureReceivedAnalyticsSendState = MutableStateFlow(false)
private val securityStatusState =
@ -243,8 +246,9 @@ internal class WcSendTransactionModel @Inject constructor(
val blockAidState = when (securityCheck) {
is Lce.Content -> blockAidUiConverter.convert(
WcSendAndReceiveBlockAidUiConverter.Input(
securityCheck.content.result,
if (isApproval) wcApproval?.getAmount() else null,
result = securityCheck.content.result,
approvedAmount = if (isApproval) wcApproval?.getAmount() else null,
onApproveLearnMoreClick = ::onApproveLearnMoreClick,
),
)
is Lce.Error -> WcSendReceiveTransactionCheckResultsUM(isLoading = false)
@ -291,6 +295,15 @@ internal class WcSendTransactionModel @Inject constructor(
stackNavigation.pop()
}
private fun onApproveLearnMoreClick() {
val code = SupportedLanguages.getCurrentSupportedLanguageCode()
.takeIf { it == SupportedLanguages.RUSSIAN }
?: SupportedLanguages.ENGLISH
val url = "https://tangem.com/$code/blog/post/give-revoke-permission/"
urlOpener.openUrl(url)
}
private fun isMultipleSignRequired(useCase: WcSignUseCase<*>): Boolean {
return if (useCase is SignRequirements) {
useCase.isMultipleSignRequired()

View file

@ -255,6 +255,7 @@ private class WcCustomAllowanceStateProvider : CollectionPreviewParameterProvide
amountValue = BigDecimal("100"),
tokenSymbol = "ETH",
isUnlimited = false,
onLearnMoreClicked = {},
),
),
)

View file

@ -3,25 +3,29 @@ package com.tangem.features.walletconnect.transaction.ui.blockaid
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withLink
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.isNullOrEmpty
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.walletconnect.impl.R
import com.tangem.features.walletconnect.transaction.entity.approve.WcSpendAllowanceUM
import com.tangem.features.walletconnect.transaction.entity.blockaid.BlockAidNotificationUM
import com.tangem.features.walletconnect.transaction.entity.blockaid.WcEstimatedWalletChangeUM
import com.tangem.features.walletconnect.transaction.entity.blockaid.WcEstimatedWalletChangesUM
import com.tangem.features.walletconnect.transaction.entity.blockaid.WcSendReceiveTransactionCheckResultsUM
import com.tangem.features.walletconnect.transaction.ui.approve.WcSpendAllowanceItem
import kotlinx.collections.immutable.persistentListOf
import java.math.BigDecimal
@Composable
internal fun TransactionCheckResultsItem(
@ -29,11 +33,7 @@ internal fun TransactionCheckResultsItem(
onClickAllowToSpend: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
Column(modifier = modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
if (item.isLoading) {
WcEstimatedWalletChangesLoadingItem()
} else {
@ -44,6 +44,10 @@ internal fun TransactionCheckResultsItem(
WcEstimatedWalletChangesItem(item.estimatedWalletChanges)
} else if (item.spendAllowance != null) {
WcSpendAllowanceItem(item.spendAllowance, onClickAllowToSpend)
ApproveDescription(
modifier = Modifier.padding(bottom = 6.dp, start = 12.dp, end = 12.dp),
onLearnMoreClick = item.spendAllowance.onLearnMoreClicked,
)
} else if (!item.additionalNotification.isNullOrEmpty()) {
WcEstimatedWalletChangesNotificationItem(description = item.additionalNotification)
} else {
@ -53,6 +57,29 @@ internal fun TransactionCheckResultsItem(
}
}
@Composable
private fun ApproveDescription(modifier: Modifier = Modifier, onLearnMoreClick: () -> Unit) {
val linkText = stringResourceSafe(R.string.common_learn_more)
val fullString = stringResourceSafe(R.string.wc_approve_description)
val defaultColor = TangemTheme.colors.text.tertiary
val linkColor = TangemTheme.colors.text.accent
Text(
modifier = modifier,
style = TangemTheme.typography.caption2,
text = buildAnnotatedString {
appendColored(fullString, defaultColor)
appendSpace()
withLink(
link = LinkAnnotation.Clickable(
tag = "WC_APPROVE_LEARN_MORE_TAG",
linkInteractionListener = { onLearnMoreClick() },
),
block = { appendColored(text = linkText, color = linkColor) },
)
},
)
}
@Composable
@Preview(showBackground = true, device = Devices.PIXEL_7_PRO)
@Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES)
@ -95,5 +122,22 @@ private class TransactionCheckResultsItemProvider : PreviewParameterProvider<WcS
),
),
),
WcSendReceiveTransactionCheckResultsUM(
isLoading = false,
notification = BlockAidNotificationUM(
type = BlockAidNotificationUM.Type.ERROR,
title = TextReference.Res(R.string.wc_malicious_transaction),
text = TextReference.Str("The transaction approves erc20 tokens to a known malicious address"),
),
spendAllowance = WcSpendAllowanceUM(
amountValue = BigDecimal.ZERO,
isUnlimited = false,
amountText = stringReference("0.00 WPOL"),
tokenSymbol = "",
tokenImageUrl = "",
networkIconRes = 0,
onLearnMoreClicked = {},
),
),
)
}

View file

@ -6,9 +6,8 @@ import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.features.walletconnect.transaction.entity.blockaid.WcEstimatedWalletChangeUM
import com.tangem.utils.converter.Converter
import javax.inject.Inject
internal class WcEstimatedWalletChangeUMConverter @Inject constructor() :
internal class WcEstimatedWalletChangeUMConverter :
Converter<WcEstimatedWalletChangeUMConverter.Input, WcEstimatedWalletChangeUM> {
override fun convert(value: Input): WcEstimatedWalletChangeUM {

View file

@ -20,15 +20,16 @@ import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import java.math.BigDecimal
import javax.inject.Inject
private const val DECIMALS_AMOUNT = 2
@Suppress("CyclomaticComplexMethod", "LongMethod")
internal class WcSendAndReceiveBlockAidUiConverter @Inject constructor(
private val estimatedWalletChangeUMConverter: WcEstimatedWalletChangeUMConverter,
private val spendAllowanceUMConverter: WcSpendAllowanceUMConverter,
) : Converter<WcSendAndReceiveBlockAidUiConverter.Input, WcSendReceiveTransactionCheckResultsUM> {
internal class WcSendAndReceiveBlockAidUiConverter :
Converter<WcSendAndReceiveBlockAidUiConverter.Input, WcSendReceiveTransactionCheckResultsUM> {
private val estimatedWalletChangeUMConverter = WcEstimatedWalletChangeUMConverter()
private val spendAllowanceUMConverter = WcSpendAllowanceUMConverter()
override fun convert(value: Input): WcSendReceiveTransactionCheckResultsUM {
val description = value.result.description?.let { if (it.isNotEmpty()) TextReference.Str(it) else null }
val simulation = value.result.simulation
@ -118,7 +119,12 @@ internal class WcSendAndReceiveBlockAidUiConverter @Inject constructor(
when (data) {
is SimulationData.SendAndReceive, SimulationData.NoWalletChangesDetected -> null
is SimulationData.Approve -> value.approvedAmount?.let {
spendAllowanceUMConverter.convert(it)
spendAllowanceUMConverter.convert(
WcSpendAllowanceUMConverter.Input(
approvedAmount = it,
onLearnMoreClick = value.onApproveLearnMoreClick,
),
)
}
}
},
@ -128,6 +134,7 @@ internal class WcSendAndReceiveBlockAidUiConverter @Inject constructor(
data class Input(
val result: CheckTransactionResult,
val approvedAmount: WcApprovedAmount?,
val onApproveLearnMoreClick: () -> Unit,
)
}

View file

@ -6,24 +6,26 @@ import com.tangem.domain.walletconnect.model.WcApprovedAmount
import com.tangem.features.walletconnect.impl.R
import com.tangem.features.walletconnect.transaction.entity.approve.WcSpendAllowanceUM
import com.tangem.utils.converter.Converter
import javax.inject.Inject
internal class WcSpendAllowanceUMConverter @Inject constructor() : Converter<WcApprovedAmount, WcSpendAllowanceUM> {
internal class WcSpendAllowanceUMConverter : Converter<WcSpendAllowanceUMConverter.Input, WcSpendAllowanceUM> {
override fun convert(value: WcApprovedAmount): WcSpendAllowanceUM {
val amount = value.amount?.value ?: 0.0.toBigDecimal()
val isUnlimited = value.amount?.value == null
override fun convert(value: Input): WcSpendAllowanceUM {
val amount = value.approvedAmount.amount?.value ?: 0.0.toBigDecimal()
val isUnlimited = value.approvedAmount.amount?.value == null
return WcSpendAllowanceUM(
amountValue = amount,
amountText = if (value.amount?.value == null) {
amountText = if (value.approvedAmount.amount?.value == null) {
TextReference.Res(R.string.wc_common_unlimited)
} else {
TextReference.Str(amount.amountText())
},
isUnlimited = isUnlimited,
tokenSymbol = value.amount?.currencySymbol ?: "",
tokenImageUrl = value.logoUrl,
networkIconRes = value.chainId?.toString()?.let { getActiveIconRes(it) },
tokenSymbol = value.approvedAmount.amount?.currencySymbol ?: "",
tokenImageUrl = value.approvedAmount.logoUrl,
networkIconRes = value.approvedAmount.chainId?.toString()?.let { getActiveIconRes(it) },
onLearnMoreClicked = value.onLearnMoreClick,
)
}
data class Input(val approvedAmount: WcApprovedAmount, val onLearnMoreClick: () -> Unit)
}

View file

@ -37,6 +37,7 @@ import com.tangem.features.walletconnect.connections.entity.VerifiedDAppState
import com.tangem.features.walletconnect.connections.ui.WcAppInfoItem
import com.tangem.features.walletconnect.impl.R
import com.tangem.features.walletconnect.transaction.components.PreviewFeeSelectorBlockComponent
import com.tangem.features.walletconnect.transaction.entity.approve.WcSpendAllowanceUM
import com.tangem.features.walletconnect.transaction.entity.blockaid.BlockAidNotificationUM
import com.tangem.features.walletconnect.transaction.entity.blockaid.WcEstimatedWalletChangeUM
import com.tangem.features.walletconnect.transaction.entity.blockaid.WcEstimatedWalletChangesUM
@ -51,6 +52,7 @@ import com.tangem.features.walletconnect.transaction.ui.common.WcSmallTitleItem
import com.tangem.features.walletconnect.transaction.ui.common.WcTransactionRequestButtons
import com.tangem.features.walletconnect.transaction.ui.common.WcTransactionRequestItem
import kotlinx.collections.immutable.persistentListOf
import java.math.BigDecimal
@Suppress("LongParameterList", "LongMethod")
@Composable
@ -316,5 +318,34 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide
),
transactionValidationResult = ValidationResult.SAFE,
),
WcSendTransactionItemUM(
onDismiss = {},
onSend = {},
appInfo = WcTransactionAppInfoContentUM(
appName = "React App",
appIcon = "",
verifiedState = VerifiedDAppState.Verified {},
appSubtitle = "react-app.walletconnect.com",
),
estimatedWalletChanges = WcSendReceiveTransactionCheckResultsUM(
isLoading = false,
spendAllowance = WcSpendAllowanceUM(
amountValue = BigDecimal.ZERO,
isUnlimited = false,
amountText = stringReference("0.00 WPOL"),
tokenSymbol = "",
tokenImageUrl = "",
networkIconRes = 0,
onLearnMoreClicked = {},
),
),
walletName = "Tangem 2.0",
networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22),
feeState = WcTransactionFeeState.None,
address = "0xdac17f958d2ee523a2206206994597c13d831ec7",
sendEnabled = true,
feeErrorNotification = null,
transactionValidationResult = ValidationResult.SAFE,
),
),
)