Updated on 2026-08-14

This commit is contained in:
Tangem 2025-06-25 17:19:42 +05:00
parent 058a6ab4d5
commit 451fdc5e0b
37 changed files with 748 additions and 226 deletions

View file

@ -64,12 +64,10 @@ internal class DefaultWcRoutingComponent @AssistedInject constructor(
is WcInnerRoute.SignMessage -> WcSignTransactionContainerComponent(
appComponentContext = childContext,
params = WcTransactionModelParams(config.rawRequest),
feeSelectorBlockComponentFactory = feeSelectorBlockComponentFactory,
)
is WcInnerRoute.AddNetwork -> WcAddNetworkContainerComponent(
appComponentContext = childContext,
params = WcTransactionModelParams(config.rawRequest),
feeSelectorBlockComponentFactory = feeSelectorBlockComponentFactory,
)
is WcInnerRoute.Send -> WcSendTransactionContainerComponent(
appComponentContext = childContext,

View file

@ -5,7 +5,6 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.walletconnect.transaction.components.common.WcCommonTransactionComponentDelegate
import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams
import com.tangem.features.walletconnect.transaction.components.common.getWcCommonScreen
@ -15,8 +14,7 @@ import com.tangem.features.walletconnect.transaction.routes.WcTransactionRoutes
internal class WcAddNetworkContainerComponent(
appComponentContext: AppComponentContext,
params: WcTransactionModelParams,
feeSelectorBlockComponentFactory: FeeSelectorBlockComponent.Factory,
) : WcCommonTransactionComponentDelegate(appComponentContext, feeSelectorBlockComponentFactory) {
) : WcCommonTransactionComponentDelegate(appComponentContext) {
private val model: WcAddNetworkModel = getOrCreateModel(params = params)

View file

@ -15,12 +15,10 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.navigation.inner.InnerRouter
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.walletconnect.transaction.routes.WcTransactionRoutes
internal abstract class WcCommonTransactionComponentDelegate(
private val appComponentContext: AppComponentContext,
private val feeSelectorBlockComponentFactory: FeeSelectorBlockComponent.Factory,
) : AppComponentContext by appComponentContext, ComposableContentComponent {
private var stackNavigation: StackNavigation<WcTransactionRoutes>? = null
@ -31,10 +29,6 @@ internal abstract class WcCommonTransactionComponentDelegate(
InnerRouter<WcTransactionRoutes>(stackNavigation = it, popCallback = { onChildBack() })
}
}
protected val feeSelectorBlockComponent = feeSelectorBlockComponentFactory.create(
context = appComponentContext,
params = Unit,
)
protected fun init(
navigation: StackNavigation<WcTransactionRoutes>,

View file

@ -6,15 +6,28 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.send.v2.api.params.FeeSelectorParams
import com.tangem.features.walletconnect.transaction.model.WcSendTransactionModel
import com.tangem.features.walletconnect.transaction.ui.send.WcSendTransactionModalBottomSheet
internal class WcSendTransactionComponent(
private val appComponentContext: AppComponentContext,
private val model: WcSendTransactionModel,
private val feeSelectorBlockComponent: FeeSelectorBlockComponent,
private val feeSelectorBlockComponentFactory: FeeSelectorBlockComponent.Factory,
) : AppComponentContext by appComponentContext, ComposableBottomSheetComponent {
private val feeSelectorBlockComponent by lazy {
feeSelectorBlockComponentFactory.create(
context = appComponentContext,
params = FeeSelectorParams.FeeSelectorBlockParams(
onLoadFee = model::loadFee,
network = model.useCase.network,
cryptoCurrencyStatus = model.cryptoCurrencyStatus,
suggestedFeeState = model.suggestedFeeState,
),
)
}
override fun dismiss() {
model.dismiss()
}

View file

@ -15,8 +15,8 @@ import com.tangem.features.walletconnect.transaction.routes.WcTransactionRoutes
internal class WcSendTransactionContainerComponent(
appComponentContext: AppComponentContext,
params: WcTransactionModelParams,
feeSelectorBlockComponentFactory: FeeSelectorBlockComponent.Factory,
) : WcCommonTransactionComponentDelegate(appComponentContext, feeSelectorBlockComponentFactory) {
private val feeSelectorBlockComponentFactory: FeeSelectorBlockComponent.Factory,
) : WcCommonTransactionComponentDelegate(appComponentContext) {
private val model: WcSendTransactionModel = getOrCreateModel(params = params)
@ -45,7 +45,7 @@ internal class WcSendTransactionContainerComponent(
return WcSendTransactionComponent(
appComponentContext = appComponentContext,
model = model,
feeSelectorBlockComponent = feeSelectorBlockComponent,
feeSelectorBlockComponentFactory = feeSelectorBlockComponentFactory,
)
}
}

View file

@ -5,14 +5,12 @@ import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.walletconnect.transaction.model.WcSignTransactionModel
import com.tangem.features.walletconnect.transaction.ui.sign.WcSignTransactionModalBottomSheetContent
internal class WcSignTransactionComponent(
private val appComponentContext: AppComponentContext,
private val model: WcSignTransactionModel,
private val feeSelectorBlockComponent: FeeSelectorBlockComponent,
) : AppComponentContext by appComponentContext, ComposableBottomSheetComponent {
override fun dismiss() {
@ -26,7 +24,6 @@ internal class WcSignTransactionComponent(
if (content != null) {
WcSignTransactionModalBottomSheetContent(
state = content!!.transaction,
feeSelectorBlockComponent = feeSelectorBlockComponent,
onClickTransactionRequest = model::showTransactionRequest,
onBack = router::pop,
onDismiss = ::dismiss,

View file

@ -5,7 +5,6 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.walletconnect.transaction.components.common.WcCommonTransactionComponentDelegate
import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams
import com.tangem.features.walletconnect.transaction.components.common.getWcCommonScreen
@ -15,8 +14,7 @@ import com.tangem.features.walletconnect.transaction.routes.WcTransactionRoutes
internal class WcSignTransactionContainerComponent(
appComponentContext: AppComponentContext,
params: WcTransactionModelParams,
feeSelectorBlockComponentFactory: FeeSelectorBlockComponent.Factory,
) : WcCommonTransactionComponentDelegate(appComponentContext, feeSelectorBlockComponentFactory) {
) : WcCommonTransactionComponentDelegate(appComponentContext) {
private val model: WcSignTransactionModel = getOrCreateModel(params = params)
@ -45,7 +43,6 @@ internal class WcSignTransactionContainerComponent(
return WcSignTransactionComponent(
appComponentContext = appComponentContext,
model = model,
feeSelectorBlockComponent = feeSelectorBlockComponent,
)
}
}

View file

@ -3,15 +3,13 @@ package com.tangem.features.walletconnect.transaction.converter
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.walletconnect.model.WcEthMethod
import com.tangem.domain.walletconnect.model.WcSolanaMethod
import com.tangem.domain.walletconnect.usecase.method.WcMutableFee
import com.tangem.domain.walletconnect.usecase.method.WcSignState
import com.tangem.domain.walletconnect.usecase.method.WcSignStep
import com.tangem.domain.walletconnect.usecase.method.WcTransactionUseCase
import com.tangem.features.walletconnect.impl.R
import com.tangem.features.walletconnect.transaction.entity.blockaid.WcSendReceiveTransactionCheckResultsUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionActionsUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestBlockUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoItemUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoUM
import com.tangem.features.walletconnect.transaction.entity.common.*
import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransactionItemUM
import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransactionUM
import com.tangem.utils.converter.Converter
@ -39,6 +37,7 @@ internal class WcSendTransactionUMConverter @Inject constructor(
onShowVerifiedAlert = value.actions.onShowVerifiedAlert,
),
),
feeState = constructFeeState(useCase = value.useCase),
walletName = value.useCase.session.wallet.name,
networkInfo = networkInfoUMConverter.convert(value.useCase.network),
address = value.useCase.walletAddress.toShortAddressText(),
@ -81,6 +80,12 @@ internal class WcSendTransactionUMConverter @Inject constructor(
else -> null
}
private fun constructFeeState(useCase: WcTransactionUseCase): WcTransactionFeeState {
val mutableFee = useCase as? WcMutableFee ?: return WcTransactionFeeState.None
val dAppFee = mutableFee.dAppFee()
return if (dAppFee != null) WcTransactionFeeState.Success(dAppFee) else WcTransactionFeeState.Loading
}
data class Input(
val useCase: WcTransactionUseCase,
val signState: WcSignState<*>,

View file

@ -0,0 +1,9 @@
package com.tangem.features.walletconnect.transaction.entity.common
import com.tangem.blockchain.common.transaction.Fee
internal sealed class WcTransactionFeeState {
object Loading : WcTransactionFeeState()
data class Success(val fee: Fee) : WcTransactionFeeState()
data object None : WcTransactionFeeState()
}

View file

@ -6,6 +6,7 @@ import com.tangem.features.walletconnect.transaction.entity.blockaid.WcSendRecei
import com.tangem.features.walletconnect.transaction.entity.common.WcCommonTransactionUM
import com.tangem.features.walletconnect.transaction.entity.common.WcNetworkInfoUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionAppInfoContentUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionFeeState
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoUM
internal data class WcSendTransactionUM(
@ -15,13 +16,13 @@ internal data class WcSendTransactionUM(
) : WcCommonTransactionUM
internal data class WcSendTransactionItemUM(
val feeState: WcTransactionFeeState,
val onDismiss: () -> Unit,
val onSend: () -> Unit,
val appInfo: WcTransactionAppInfoContentUM,
val estimatedWalletChanges: WcSendReceiveTransactionCheckResultsUM?,
val walletName: String,
val networkInfo: WcNetworkInfoUM,
val networkFee: String? = null,
val address: String? = null,
val isLoading: Boolean = false,
) : TangemBottomSheetConfigContent

View file

@ -7,14 +7,25 @@ import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.decompose.router.stack.pushNew
import com.domain.blockaid.models.transaction.ValidationResult
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.TransactionFee
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.ui.clipboard.ClipboardManager
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.models.network.Network
import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.usecase.GetFeeUseCase
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
import com.tangem.domain.walletconnect.usecase.method.*
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.send.v2.api.params.FeeSelectorParams
import com.tangem.features.walletconnect.connections.routing.WcInnerRoute
import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams
import com.tangem.features.walletconnect.transaction.converter.WcCommonTransactionUMConverter
@ -29,6 +40,7 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import java.math.BigDecimal
import javax.inject.Inject
import kotlin.properties.Delegates
@Suppress("LongParameterList")
@Stable
@ -41,6 +53,8 @@ internal class WcSendTransactionModel @Inject constructor(
private val useCaseFactory: WcRequestUseCaseFactory,
private val converter: WcCommonTransactionUMConverter,
private val blockAidUiConverter: WcSendAndReceiveBlockAidUiConverter,
private val getFeeUseCase: GetFeeUseCase,
private val getNetworkCoinUseCase: GetNetworkCoinStatusUseCase,
) : Model(), WcCommonTransactionModel {
private val params = paramsContainer.require<WcTransactionModelParams>()
@ -50,6 +64,10 @@ internal class WcSendTransactionModel @Inject constructor(
val stackNavigation = StackNavigation<WcTransactionRoutes>()
internal var useCase: WcSignUseCase<*> by Delegates.notNull()
internal var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
internal var suggestedFeeState: FeeSelectorParams.SuggestedFeeState = FeeSelectorParams.SuggestedFeeState.None
private var signState: WcSignState<*> by Delegates.notNull()
private var wcApproval: WcApproval? = null
private var sign: () -> Unit = {}
@ -63,36 +81,65 @@ internal class WcSendTransactionModel @Inject constructor(
when (useCase) {
is WcListTransactionUseCase,
is WcTransactionUseCase,
-> combine(
useCase.invoke(),
useCase.securityStatus,
) { signState, securityCheck -> signState to securityCheck }
.distinctUntilChanged()
.collectLatest {
val (signState, securityCheck) = it
if (signingIsDone(signState, useCase)) return@collectLatest
val signModel: Any = signState.signModel
val isMutableFee = useCase is WcMutableFee
val dAppFee = if (isMutableFee) useCase.dAppFee() else null
val selectedFee = when (signModel) {
is TransactionData.Compiled -> signModel.fee
is TransactionData.Uncompiled -> signModel.fee
else -> null
-> {
this@WcSendTransactionModel.cryptoCurrencyStatus =
getCryptoCurrencyStatus(userWallet = useCase.wallet, network = useCase.network)
.onLeft { unknownMethodRunnable() }
.getOrNull() ?: return@launch
this@WcSendTransactionModel.useCase = useCase
(useCase as? WcMutableFee)
?.dAppFee()
?.let { dAppFee ->
suggestedFeeState = FeeSelectorParams.SuggestedFeeState.Suggestion(
title = stringReference(useCase.session.sdkModel.appMetaData.name),
fee = dAppFee,
)
}
val isDAppFeeSelected = dAppFee != null && dAppFee == selectedFee
combine(
useCase.invoke(),
useCase.securityStatus,
) { signState, securityCheck -> signState to securityCheck }
.distinctUntilChanged()
.collectLatest { (signState, securityCheck) ->
if (signingIsDone(signState, useCase)) return@collectLatest
val isSecurityCheckContent = securityCheck is Lce.Content
var isApprovalMethod = isSecurityCheckContent &&
securityCheck.content is BlockAidTransactionCheck.Result.Approval
wcApproval = useCase as? WcApproval
sign = { useCase.sign() }
buildUiState(securityCheck, useCase, signState)
}
this@WcSendTransactionModel.signState = signState
val isSecurityCheckContent = securityCheck is Lce.Content
var isApprovalMethod = isSecurityCheckContent &&
securityCheck.content is BlockAidTransactionCheck.Result.Approval
wcApproval = useCase as? WcApproval
sign = { useCase.sign() }
buildUiState(securityCheck, useCase, signState)
}
}
else -> unknownMethodRunnable()
}
}
}
suspend fun loadFee(): Either<GetFeeError, TransactionFee> {
val signModel = signState.signModel
val transactionData = signModel as? TransactionData.Uncompiled ?: error("TransactionData must be Uncompiled")
return getFeeUseCase.invoke(
userWallet = useCase.wallet,
network = useCase.network,
transactionData = transactionData,
)
}
private suspend fun getCryptoCurrencyStatus(
userWallet: UserWallet,
network: Network,
): Either<CurrencyStatusError, CryptoCurrencyStatus> {
return getNetworkCoinUseCase.invokeSync(
userWalletId = userWallet.walletId,
networkId = network.id,
derivationPath = network.derivationPath,
isSingleWalletWithTokens = userWallet is UserWallet.Cold &&
userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(),
)
}
private suspend fun buildUiState(
securityCheck: Lce<Throwable, BlockAidTransactionCheck.Result>,
useCase: WcSignUseCase<*>,

View file

@ -14,11 +14,13 @@ import com.tangem.core.ui.components.divider.DividerWithPadding
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.walletconnect.transaction.entity.common.WcNetworkInfoUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionFeeState
@Composable
internal fun WcSendTransactionItems(
walletName: String,
networkInfo: WcNetworkInfoUM,
feeState: WcTransactionFeeState,
feeSelectorBlockComponent: FeeSelectorBlockComponent,
modifier: Modifier = Modifier,
address: String? = null,
@ -52,6 +54,8 @@ internal fun WcSendTransactionItems(
)
}
DividerWithPadding(start = 40.dp, end = 12.dp)
feeSelectorBlockComponent.Content(Modifier)
if (feeState != WcTransactionFeeState.None) {
feeSelectorBlockComponent.Content(Modifier)
}
}
}

View file

@ -34,6 +34,7 @@ import com.tangem.features.walletconnect.transaction.entity.blockaid.WcEstimated
import com.tangem.features.walletconnect.transaction.entity.blockaid.WcSendReceiveTransactionCheckResultsUM
import com.tangem.features.walletconnect.transaction.entity.common.WcNetworkInfoUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionAppInfoContentUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionFeeState
import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransactionItemUM
import com.tangem.features.walletconnect.transaction.ui.blockaid.TransactionCheckResultsItem
import com.tangem.features.walletconnect.transaction.ui.common.WcSendTransactionItems
@ -42,7 +43,7 @@ import com.tangem.features.walletconnect.transaction.ui.common.WcTransactionRequ
import com.tangem.features.walletconnect.transaction.ui.common.WcTransactionRequestItem
import kotlinx.collections.immutable.persistentListOf
@Suppress("LongParameterList")
@Suppress("LongParameterList", "LongMethod")
@Composable
internal fun WcSendTransactionModalBottomSheet(
state: WcSendTransactionItemUM,
@ -103,6 +104,7 @@ internal fun WcSendTransactionModalBottomSheet(
WcSendTransactionItems(
walletName = state.walletName,
networkInfo = state.networkInfo,
feeState = state.feeState,
feeSelectorBlockComponent = feeSelectorBlockComponent,
address = state.address,
)
@ -192,7 +194,7 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide
),
walletName = "Tangem 2.0",
networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22),
networkFee = "~ 0.22 $",
feeState = WcTransactionFeeState.Loading,
address = "0x345FF...34FA",
),
),

View file

@ -25,11 +25,9 @@ import com.tangem.core.ui.components.divider.DividerWithPadding
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
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.common.WcNetworkInfoUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionAppInfoContentUM
import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionItemUM
@ -38,7 +36,6 @@ import com.tangem.features.walletconnect.transaction.ui.common.*
@Composable
internal fun WcSignTransactionModalBottomSheetContent(
state: WcSignTransactionItemUM,
feeSelectorBlockComponent: FeeSelectorBlockComponent,
onClickTransactionRequest: () -> Unit,
onBack: () -> Unit,
onDismiss: () -> Unit,
@ -87,7 +84,7 @@ internal fun WcSignTransactionModalBottomSheetContent(
)
}
Column(modifier = Modifier.padding(top = 16.dp)) {
WcSignTransactionItems(state, feeSelectorBlockComponent)
WcSignTransactionItems(state)
}
}
},
@ -104,11 +101,7 @@ internal fun WcSignTransactionModalBottomSheetContent(
}
@Composable
private fun WcSignTransactionItems(
state: WcSignTransactionItemUM,
feeSelectorBlockComponent: FeeSelectorBlockComponent,
modifier: Modifier = Modifier,
) {
private fun WcSignTransactionItems(state: WcSignTransactionItemUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.clip(RoundedCornerShape(14.dp))
@ -138,7 +131,6 @@ private fun WcSignTransactionItems(
)
}
DividerWithPadding(start = 40.dp, end = 12.dp)
feeSelectorBlockComponent.Content(modifier = Modifier)
}
}
@ -168,7 +160,6 @@ private fun WcSignTransactionBottomSheetPreview(
content = {
WcSignTransactionModalBottomSheetContent(
state = state,
feeSelectorBlockComponent = PreviewFeeSelectorBlockComponent(),
onClickTransactionRequest = {},
onBack = {},
onDismiss = {},