Updated on 2026-08-14

This commit is contained in:
Tangem 2025-06-09 17:59:58 +08:00
parent b352bb6235
commit b699b723a8
13 changed files with 99 additions and 69 deletions

View file

@ -12,12 +12,14 @@ import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate
import com.tangem.domain.transaction.usecase.PrepareForSendUseCase
import com.tangem.domain.walletconnect.model.WcSolanaMethod
import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck
import com.tangem.domain.walletconnect.usecase.method.WcListTransactionUseCase
import com.tangem.domain.walletconnect.usecase.method.WcSignState
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import okio.ByteString.Companion.decodeBase64
import org.json.JSONArray
import org.json.JSONObject
@ -38,7 +40,7 @@ internal class WcSolanaSignAllTransactionUseCase @AssistedInject constructor(
rawSdkRequest = rawSdkRequest,
session = session,
accountAddress = context.accountAddress,
)
).map { lce -> lce.map { result -> BlockAidTransactionCheck.Result.Plain(result) } }
override suspend fun SignCollector<List<TransactionData>>.onSign(state: WcSignState<List<TransactionData>>) {
val hash = prepareForSend.invoke(transactionData = state.signModel, userWallet = wallet, network = network)

View file

@ -1,6 +1,10 @@
package com.tangem.data.walletconnect.request
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
import com.tangem.domain.walletconnect.model.WcMethod
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase
import javax.inject.Inject
@ -10,8 +14,10 @@ internal class DefaultWcRequestUseCaseFactory @Inject constructor(
) : WcRequestUseCaseFactory {
@Suppress("UNCHECKED_CAST")
override suspend fun <T : WcMethodUseCase> createUseCase(request: WcSdkSessionRequest): T {
return requestConverters.firstNotNullOf { it.toUseCase(request) } as? T
?: error("Should check request before in WcRouting")
override suspend fun <T : WcMethodUseCase> createUseCase(
request: WcSdkSessionRequest,
): Either<WcMethod.Unsupported, T> {
val useCase = requestConverters.firstNotNullOfOrNull { converter -> converter.toUseCase(request) }
return (useCase as? T)?.let { useCase.right() } ?: WcMethod.Unsupported(request).left()
}
}

View file

@ -1,5 +1,7 @@
package com.tangem.domain.walletconnect.model
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
sealed interface WcMethod {
data object Unsupported : WcMethod
data class Unsupported(val request: WcSdkSessionRequest) : WcMethod
}

View file

@ -1,9 +1,11 @@
package com.tangem.domain.walletconnect
import arrow.core.Either
import com.tangem.domain.walletconnect.model.WcMethod
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase
interface WcRequestUseCaseFactory {
suspend fun <T : WcMethodUseCase> createUseCase(request: WcSdkSessionRequest): T
suspend fun <T : WcMethodUseCase> createUseCase(request: WcSdkSessionRequest): Either<WcMethod.Unsupported, T>
}

View file

@ -23,7 +23,7 @@ interface WcMethodContext {
* Wc Methods that must be signed by Tangem card
* [WcSignState] represents signing state
*/
interface WcSignUseCase<SignModel> : WcMethodContext {
interface WcSignUseCase<SignModel> : WcMethodContext, WcMethodUseCase {
operator fun invoke(): Flow<WcSignState<SignModel>>
fun cancel()

View file

@ -3,7 +3,6 @@ package com.tangem.domain.walletconnect.usecase.method
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.walletconnect.usecase.blockaid.WcBlockAidEligibleTransactionUseCase
/**
* Base UseCase for wc methods with single TransactionData
@ -27,7 +26,7 @@ interface WcTransactionUseCase :
*/
interface WcListTransactionUseCase :
WcSignUseCase<List<TransactionData>>,
WcBlockAidEligibleTransactionUseCase
BlockAidTransactionCheck
/**
* Wc Methods that support an updatable fee

View file

@ -64,4 +64,5 @@ dependencies {
implementation(deps.kotlin.immutable.collections)
implementation(deps.timber)
implementation(tangemDeps.card.core)
implementation(tangemDeps.blockchain)
}

View file

@ -15,8 +15,8 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.features.walletconnect.components.WcRoutingComponent
import com.tangem.features.walletconnect.connections.components.WcPairComponent
import com.tangem.features.walletconnect.connections.components.AlertsComponent
import com.tangem.features.walletconnect.connections.components.WcPairComponent
import com.tangem.features.walletconnect.transaction.components.chain.WcAddNetworkContainerComponent
import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams
import com.tangem.features.walletconnect.transaction.components.send.WcSendTransactionContainerComponent
@ -79,10 +79,10 @@ internal class DefaultWcRoutingComponent @AssistedInject constructor(
source = config.request.source,
),
)
is WcInnerRoute.Alert -> AlertsComponent(
is WcInnerRoute.UnsupportedMethodAlert -> AlertsComponent(
childContext,
AlertsComponent.Params(
alertType = config.alertType,
alertType = AlertsComponent.AlertType.UnsupportedMethod { model.innerRouter.pop() },
),
)
}

View file

@ -3,7 +3,6 @@ package com.tangem.features.walletconnect.connections.routing
import com.tangem.core.decompose.navigation.Route
import com.tangem.domain.walletconnect.model.WcPairRequest
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.features.walletconnect.connections.components.AlertsComponent
import kotlinx.serialization.Serializable
@Serializable
@ -27,5 +26,5 @@ internal sealed interface WcInnerRoute : Route {
data class Pair(val request: WcPairRequest) : WcInnerRoute
@Serializable
data class Alert(val alertType: AlertsComponent.AlertType) : WcInnerRoute
data class UnsupportedMethodAlert(override val rawRequest: WcSdkSessionRequest) : Method
}

View file

@ -11,7 +11,6 @@ import com.tangem.domain.walletconnect.model.WcEthMethodName
import com.tangem.domain.walletconnect.model.WcMethodName
import com.tangem.domain.walletconnect.model.WcSolanaMethodName
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
import com.tangem.features.walletconnect.connections.components.AlertsComponent
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
import javax.inject.Inject
@ -58,9 +57,7 @@ internal class WcRoutingModel @Inject constructor(
WcSolanaMethodName.SendAllTransaction,
-> WcInnerRoute.Send(rawRequest)
is WcMethodName.Unsupported,
-> WcInnerRoute.Alert(
alertType = AlertsComponent.AlertType.UnsupportedMethod { innerRouter.pop() },
)
-> WcInnerRoute.UnsupportedMethodAlert(rawRequest)
}
}

View file

@ -10,13 +10,13 @@ import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
import com.tangem.domain.walletconnect.usecase.method.WcAddNetworkUseCase
import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase
import com.tangem.features.walletconnect.connections.routing.WcInnerRoute
import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams
import com.tangem.features.walletconnect.transaction.converter.WcAddEthereumChainUMConverter
import com.tangem.features.walletconnect.transaction.entity.chain.WcAddEthereumChainUM
import com.tangem.features.walletconnect.transaction.entity.common.WcCommonTransactionModel
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionActionsUM
import com.tangem.features.walletconnect.transaction.routes.WcTransactionRoutes
import com.tangem.features.walletconnect.transaction.converter.WcAddEthereumChainUMConverter
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -44,8 +44,10 @@ internal class WcAddNetworkModel @Inject constructor(
init {
modelScope.launch {
val useCase: WcMethodUseCase = useCaseFactory.createUseCase(params.rawRequest)
val transactionUM = (useCase as? WcAddNetworkUseCase)?.let {
val useCase: WcAddNetworkUseCase = useCaseFactory.createUseCase<WcAddNetworkUseCase>(params.rawRequest)
.onLeft { router.push(WcInnerRoute.UnsupportedMethodAlert(params.rawRequest)) }
.getOrNull() ?: return@launch
_uiState.emit(
wcAddEthereumChainUMConverter.convert(
WcAddEthereumChainUMConverter.Input(
useCase = useCase,
@ -56,9 +58,8 @@ internal class WcAddNetworkModel @Inject constructor(
onCopy = { copyData(useCase.rawSdkRequest.request.params) },
),
),
)
}
_uiState.emit(transactionUM)
),
)
}
}

View file

@ -3,7 +3,7 @@ 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.domain.blockaid.models.transaction.TransactionData
import com.tangem.blockchain.common.TransactionData
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -12,16 +12,16 @@ import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
import com.tangem.domain.walletconnect.usecase.method.*
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
import com.tangem.features.walletconnect.transaction.entity.blockaid.WcSendReceiveTransactionCheckResultsUM
import com.tangem.features.walletconnect.transaction.entity.common.WcCommonTransactionModel
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionActionsUM
import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransactionUM
import com.tangem.features.walletconnect.transaction.routes.WcTransactionRoutes
import com.tangem.features.walletconnect.transaction.converter.WcCommonTransactionUMConverter
import com.tangem.features.walletconnect.transaction.entity.blockaid.WcSendReceiveTransactionCheckResultsUM
import com.tangem.features.walletconnect.transaction.ui.blockaid.WcSendAndReceiveBlockAidUiConverter
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -47,52 +47,69 @@ internal class WcSendTransactionModel @Inject constructor(
val stackNavigation = StackNavigation<WcTransactionRoutes>()
init {
@Suppress("UnusedPrivateMember")
modelScope.launch {
when (val useCase: WcSignUseCase<TransactionData> = useCaseFactory.createUseCase(params.rawRequest)) {
is WcTransactionUseCase,
val unknownMethodRunnable = { router.push(WcInnerRoute.UnsupportedMethodAlert(params.rawRequest)) }
val useCase = useCaseFactory.createUseCase<WcSignUseCase<*>>(params.rawRequest)
.onLeft { unknownMethodRunnable() }
.getOrNull() ?: return@launch
when (useCase) {
is WcListTransactionUseCase,
-> {
observeTransactionData(useCase)
}
is WcTransactionUseCase,
-> combine(
useCase.invoke(),
useCase.securityStatus,
) { signState, securityCheck -> signState to securityCheck }
.distinctUntilChanged()
.collectLatest {
val (signState, securityCheck) = it
if (signingIsDone(signState)) 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
}
val isDAppFeeSelected = dAppFee != null && dAppFee == selectedFee
val isSecurityCheckContent = securityCheck is Lce.Content
var isApprovalMethod = isSecurityCheckContent &&
securityCheck.content is BlockAidTransactionCheck.Result.Approval
buildUiState(securityCheck, useCase, signState)
}
else -> unknownMethodRunnable()
}
}
}
private suspend fun observeSecurityData(useCase: WcSignUseCase<TransactionData>) {
(useCase as? BlockAidTransactionCheck)?.let {
useCase.securityStatus.collectLatest {
val blockAidState = when (it) {
is Lce.Content -> blockAidUiConverter.convert(it.content.result)
is Lce.Error -> WcSendReceiveTransactionCheckResultsUM(isLoading = false)
is Lce.Loading -> WcSendReceiveTransactionCheckResultsUM(isLoading = true)
}
val current = _uiState.value
_uiState.emit(
current?.copy(transaction = current.transaction.copy(estimatedWalletChanges = blockAidState)),
)
}
private suspend fun buildUiState(
securityCheck: Lce<Throwable, BlockAidTransactionCheck.Result>,
useCase: WcSignUseCase<*>,
signState: WcSignState<*>,
) {
val blockAidState = when (securityCheck) {
is Lce.Content -> blockAidUiConverter.convert(securityCheck.content.result)
is Lce.Error -> WcSendReceiveTransactionCheckResultsUM(isLoading = false)
is Lce.Loading -> WcSendReceiveTransactionCheckResultsUM(isLoading = true)
}
}
private fun CoroutineScope.observeTransactionData(useCase: WcSignUseCase<TransactionData>) {
useCase.invoke().onEach { signState ->
if (signingIsDone(signState)) return@onEach
val transactionUM = converter.convert(
WcCommonTransactionUMConverter.Input(
useCase = useCase,
signState = signState,
actions = WcTransactionActionsUM(
onShowVerifiedAlert = ::showVerifiedAlert,
onDismiss = { cancel(useCase) },
onSign = useCase::sign,
onCopy = { copyData(useCase.rawSdkRequest.request.params) },
),
var transactionUM = converter.convert(
WcCommonTransactionUMConverter.Input(
useCase = useCase,
signState = signState,
actions = WcTransactionActionsUM(
onShowVerifiedAlert = ::showVerifiedAlert,
onDismiss = { cancel(useCase) },
onSign = useCase::sign,
onCopy = { copyData(useCase.rawSdkRequest.request.params) },
),
) as? WcSendTransactionUM
_uiState.emit(transactionUM)
observeSecurityData(useCase)
}
.launchIn(this)
),
) as? WcSendTransactionUM
transactionUM = transactionUM?.copy(
transaction = transactionUM.transaction.copy(estimatedWalletChanges = blockAidState),
)
_uiState.emit(transactionUM)
}
override fun dismiss() {

View file

@ -9,15 +9,17 @@ import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase
import com.tangem.domain.walletconnect.usecase.method.WcSignState
import com.tangem.domain.walletconnect.usecase.method.WcSignStep
import com.tangem.domain.walletconnect.usecase.method.WcSignUseCase
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
import com.tangem.features.walletconnect.transaction.entity.common.WcCommonTransactionModel
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionActionsUM
import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionUM
import com.tangem.features.walletconnect.transaction.routes.WcTransactionRoutes
import com.tangem.features.walletconnect.transaction.converter.WcCommonTransactionUMConverter
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -46,7 +48,9 @@ internal class WcSignTransactionModel @Inject constructor(
init {
modelScope.launch {
val useCase: WcSignUseCase<*> = useCaseFactory.createUseCase(params.rawRequest)
val useCase = useCaseFactory.createUseCase<WcMessageSignUseCase>(params.rawRequest)
.onLeft { router.push(WcInnerRoute.UnsupportedMethodAlert(params.rawRequest)) }
.getOrNull() ?: return@launch
useCase.invoke()
.onEach { signState ->
if (signingIsDone(signState)) return@onEach