Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-28 11:44:32 +04:00
parent f49f013aa2
commit 37dc463808
14 changed files with 157 additions and 64 deletions

View file

@ -52,6 +52,7 @@ dependencies {
implementation(deps.arrow.core)
implementation(projects.domain.blockaid)
implementation(projects.domain.blockaid.models)
implementation(projects.libs.crypto)
/* Tests */
testImplementation(projects.common.test)

View file

@ -35,6 +35,7 @@ import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.lib.crypto.UserWalletManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@ -146,11 +147,13 @@ internal object WalletConnectDataModule {
excludedBlockchains: ExcludedBlockchains,
sessionsManager: WcSessionsManager,
factories: WcSolanaNetwork.Factories,
walletManager: UserWalletManager,
): WcSolanaNetwork = WcSolanaNetwork(
moshi = moshi,
sessionsManager = sessionsManager,
factories = factories,
excludedBlockchains = excludedBlockchains,
walletManager = walletManager,
)
@Provides

View file

@ -1,50 +0,0 @@
package com.tangem.data.walletconnect.network.ethereum
import com.domain.blockaid.models.transaction.CheckTransactionResult
import com.domain.blockaid.models.transaction.TransactionData
import com.domain.blockaid.models.transaction.TransactionParams
import com.tangem.domain.blockaid.BlockAidVerifier
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.walletconnect.model.WcEthMethod
import com.tangem.domain.walletconnect.model.WcSession
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import timber.log.Timber
import javax.inject.Inject
internal class BlockAidEthereumVerificationDelegate @Inject constructor(
private val blockAidVerifier: BlockAidVerifier,
) {
fun getSecurityStatus(
network: Network,
method: WcEthMethod,
rawSdkRequest: WcSdkSessionRequest,
session: WcSession,
): Flow<Lce<Throwable, CheckTransactionResult>> = flow {
emit(Lce.Loading(partialContent = null))
blockAidVerifier.verifyTransaction(
TransactionData(
chain = network.name,
accountAddress = when (method) {
is WcEthMethod.MessageSign -> method.account
is WcEthMethod.SendTransaction -> method.transaction.from
is WcEthMethod.SignTransaction -> method.transaction.from
},
method = rawSdkRequest.request.method,
domainUrl = session.sdkModel.appMetaData.url,
params = TransactionParams.Evm(rawSdkRequest.request.params),
),
).fold(
ifLeft = {
Timber.e("Failed to verify transaction: ${it.localizedMessage}")
emit(Lce.Error(it))
},
ifRight = {
emit(Lce.Content(it))
},
)
}
}

View file

@ -7,6 +7,7 @@ import com.tangem.blockchain.common.HEX_PREFIX
import com.tangem.blockchain.extensions.isAscii
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toDecompressedPublicKey
import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate
import com.tangem.data.walletconnect.respond.WcRespondService
import com.tangem.data.walletconnect.sign.BaseWcSignUseCase
import com.tangem.data.walletconnect.sign.SignCollector
@ -30,11 +31,17 @@ internal class DefaultWcEthMessageSignUseCase @AssistedInject constructor(
@Assisted private val method: WcEthMethod.MessageSign,
private val walletManagersFacade: WalletManagersFacade,
private val signUseCase: SignUseCase,
blockAidDelegate: BlockAidEthereumVerificationDelegate,
blockAidDelegate: BlockAidVerificationDelegate,
) : BaseWcSignUseCase<Nothing, WcEthMessageSignUseCase.SignModel>(),
WcEthMessageSignUseCase {
override val securityStatus = blockAidDelegate.getSecurityStatus(network, method, rawSdkRequest, session)
override val securityStatus = blockAidDelegate.getSecurityStatus(
network = network,
method = method,
rawSdkRequest = rawSdkRequest,
session = session,
accountAddress = context.accountAddress,
)
override suspend fun SignCollector<WcEthMessageSignUseCase.SignModel>.onSign(
state: WcSignState<WcEthMessageSignUseCase.SignModel>,

View file

@ -3,6 +3,7 @@ package com.tangem.data.walletconnect.network.ethereum
import arrow.core.left
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.formatHex
import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate
import com.tangem.data.walletconnect.respond.WcRespondService
import com.tangem.data.walletconnect.sign.BaseWcSignUseCase
import com.tangem.data.walletconnect.sign.SignCollector
@ -25,11 +26,17 @@ internal class DefaultWcEthSendTransactionUseCase @AssistedInject constructor(
@Assisted private val method: WcEthMethod.SendTransaction,
override val respondService: WcRespondService,
private val sendTransaction: SendTransactionUseCase,
blockAidDelegate: BlockAidEthereumVerificationDelegate,
blockAidDelegate: BlockAidVerificationDelegate,
) : BaseWcSignUseCase<Fee, WcEthTransaction>(),
WcEthSendTransactionUseCase {
override val securityStatus = blockAidDelegate.getSecurityStatus(network, method, rawSdkRequest, session)
override val securityStatus = blockAidDelegate.getSecurityStatus(
network = network,
method = method,
rawSdkRequest = rawSdkRequest,
session = session,
accountAddress = context.accountAddress,
)
private val converter = EthTransactionParamsConverter(context)

View file

@ -4,6 +4,7 @@ import arrow.core.left
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.formatHex
import com.tangem.common.extensions.toHexString
import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate
import com.tangem.data.walletconnect.respond.WcRespondService
import com.tangem.data.walletconnect.sign.BaseWcSignUseCase
import com.tangem.data.walletconnect.sign.SignCollector
@ -27,11 +28,17 @@ internal class DefaultWcEthSignTransactionUseCase @AssistedInject constructor(
private val prepareForSend: PrepareForSendUseCase,
@Assisted override val context: WcMethodUseCaseContext,
@Assisted private val method: WcEthMethod.SignTransaction,
blockAidDelegate: BlockAidEthereumVerificationDelegate,
blockAidDelegate: BlockAidVerificationDelegate,
) : BaseWcSignUseCase<Fee, WcEthTransaction>(),
WcEthSignTransactionUseCase {
override val securityStatus = blockAidDelegate.getSecurityStatus(network, method, rawSdkRequest, session)
override val securityStatus = blockAidDelegate.getSecurityStatus(
network = network,
method = method,
rawSdkRequest = rawSdkRequest,
session = session,
accountAddress = context.accountAddress,
)
private val converter = EthTransactionParamsConverter(context)

View file

@ -39,7 +39,17 @@ internal class WcEthNetwork(
val method: WcEthMethod = name.toMethod(request) ?: return null
val session = sessionsManager.findSessionByTopic(request.topic) ?: return null
val network = toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null
val context = WcMethodUseCaseContext(session = session, rawSdkRequest = request, network = network)
val accountAddress = when (method) {
is WcEthMethod.MessageSign -> method.account
is WcEthMethod.SendTransaction -> method.transaction.from
is WcEthMethod.SignTransaction -> method.transaction.from
}
val context = WcMethodUseCaseContext(
session = session,
rawSdkRequest = request,
network = network,
accountAddress = accountAddress,
)
return when (method) {
is WcEthMethod.MessageSign -> factories.messageSign.create(context, method)
is WcEthMethod.SendTransaction -> factories.sendTransaction.create(context, method)

View file

@ -8,6 +8,7 @@ import com.tangem.data.walletconnect.sign.BaseWcSignUseCase
import com.tangem.data.walletconnect.sign.SignCollector
import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
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.sign.WcSignState
@ -25,9 +26,18 @@ internal class DefaultWcSolanaSignAllTransactionUseCase @AssistedInject construc
private val prepareForSend: PrepareForSendUseCase,
@Assisted override val context: WcMethodUseCaseContext,
@Assisted private val method: WcSolanaMethod.SignAllTransaction,
blockAidDelegate: BlockAidVerificationDelegate,
) : BaseWcSignUseCase<Nothing, List<TransactionData.Compiled>>(),
WcSolanaSignAllTransactionUseCase {
override val securityStatus = blockAidDelegate.getSecurityStatus(
network = network,
method = method,
rawSdkRequest = rawSdkRequest,
session = session,
accountAddress = context.accountAddress,
)
override suspend fun SignCollector<List<TransactionData.Compiled>>.onSign(
state: WcSignState<List<TransactionData.Compiled>>,
) {

View file

@ -8,6 +8,7 @@ import com.tangem.data.walletconnect.sign.BaseWcSignUseCase
import com.tangem.data.walletconnect.sign.SignCollector
import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
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.sign.WcSignState
@ -23,9 +24,18 @@ internal class DefaultWcSolanaSignTransactionUseCase @AssistedInject constructor
private val prepareForSend: PrepareForSendUseCase,
@Assisted override val context: WcMethodUseCaseContext,
@Assisted private val method: WcSolanaMethod.SignTransaction,
blockAidDelegate: BlockAidVerificationDelegate,
) : BaseWcSignUseCase<Nothing, TransactionData.Compiled>(),
WcSolanaSignTransactionUseCase {
override val securityStatus = blockAidDelegate.getSecurityStatus(
network = network,
method = method,
rawSdkRequest = rawSdkRequest,
session = session,
accountAddress = context.accountAddress,
)
override suspend fun SignCollector<TransactionData.Compiled>.onSign(state: WcSignState<TransactionData.Compiled>) {
val hash = prepareForSend.invoke(transactionData = state.signModel, userWallet = wallet, network = network)
.onLeft { error ->

View file

@ -16,13 +16,16 @@ import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.domain.walletconnect.repository.WcSessionsManager
import com.tangem.domain.walletconnect.usecase.WcMethodUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.lib.crypto.UserWalletManager
import jakarta.inject.Inject
import timber.log.Timber
internal class WcSolanaNetwork constructor(
internal class WcSolanaNetwork(
private val moshi: Moshi,
private val sessionsManager: WcSessionsManager,
private val factories: Factories,
private val excludedBlockchains: ExcludedBlockchains,
private val walletManager: UserWalletManager,
) : WcNamespaceConverter, WcRequestToUseCaseConverter {
override val namespaceKey: NamespaceKey = NamespaceKey("solana")
@ -38,7 +41,13 @@ internal class WcSolanaNetwork constructor(
val method: WcSolanaMethod = name.toMethod(request) ?: return null
val session = sessionsManager.findSessionByTopic(request.topic) ?: return null
val network = toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null
val context = WcMethodUseCaseContext(session = session, rawSdkRequest = request, network = network)
val accountAddress = getAccountAddress(network)
val context = WcMethodUseCaseContext(
session = session,
rawSdkRequest = request,
network = network,
accountAddress = accountAddress,
)
return when (method) {
is WcSolanaMethod.SignMessage -> TODO()
is WcSolanaMethod.SignTransaction -> factories.signTransaction.create(context, method)
@ -46,6 +55,15 @@ internal class WcSolanaNetwork constructor(
}
}
private suspend fun getAccountAddress(network: Network): String? {
return try {
walletManager.getWalletAddress(network.id.value, network.derivationPath.value)
} catch (exception: Exception) {
Timber.e(exception)
null
}
}
override fun toBlockchain(chainId: CAIP2): Blockchain? {
if (chainId.namespace != namespaceKey.key) return null
return when (chainId.reference) {

View file

@ -60,6 +60,7 @@ internal class WcMethodUseCaseContext(
val session: WcSession,
val rawSdkRequest: WcSdkSessionRequest,
val network: Network,
val accountAddress: String?,
)
internal typealias SignCollector<SignModel> = FlowCollector<WcSignState<SignModel>>

View file

@ -0,0 +1,67 @@
package com.tangem.data.walletconnect.utils
import com.domain.blockaid.models.transaction.*
import com.tangem.domain.blockaid.BlockAidVerifier
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.walletconnect.model.WcEthMethod
import com.tangem.domain.walletconnect.model.WcMethod
import com.tangem.domain.walletconnect.model.WcSession
import com.tangem.domain.walletconnect.model.WcSolanaMethod
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import kotlinx.coroutines.flow.flow
import timber.log.Timber
import javax.inject.Inject
internal class BlockAidVerificationDelegate @Inject constructor(
private val blockAidVerifier: BlockAidVerifier,
) {
fun getSecurityStatus(
network: Network,
method: WcMethod,
rawSdkRequest: WcSdkSessionRequest,
session: WcSession,
accountAddress: String?,
): LceFlow<Throwable, CheckTransactionResult> = flow {
emit(Lce.Loading(partialContent = null))
val failedResult = CheckTransactionResult(
validation = ValidationResult.FAILED_TO_VALIDATE,
simulation = SimulationResult.FailedToSimulate,
)
if (accountAddress.isNullOrEmpty()) {
emit(Lce.Content(failedResult))
return@flow
}
when (method) {
is WcEthMethod -> TransactionParams.Evm(rawSdkRequest.request.params)
is WcSolanaMethod.SignAllTransaction -> TransactionParams.Solana(method.transaction)
is WcSolanaMethod.SignTransaction -> TransactionParams.Solana(listOf(method.transaction))
is WcSolanaMethod.SignMessage -> {
// BlockAid doesn't support solana_signMessage
emit(Lce.Content(failedResult))
return@flow
}
else -> null
}?.let { params ->
blockAidVerifier.verifyTransaction(
TransactionData(
chain = network.name,
accountAddress = accountAddress,
method = rawSdkRequest.request.method,
domainUrl = session.sdkModel.appMetaData.url,
params = params,
),
).fold(
ifLeft = {
Timber.e("Failed to verify transaction: ${it.localizedMessage}")
emit(Lce.Error(it))
},
ifRight = {
emit(Lce.Content(it))
},
)
} ?: emit(Lce.Content(failedResult))
}
}

View file

@ -1,10 +1,9 @@
package com.tangem.domain.walletconnect.usecase.blockaid
import com.domain.blockaid.models.transaction.CheckTransactionResult
import com.tangem.domain.core.lce.Lce
import kotlinx.coroutines.flow.Flow
import com.tangem.domain.core.lce.LceFlow
interface WcBlockAidEligibleTransactionUseCase {
val securityStatus: Flow<Lce<Throwable, CheckTransactionResult>>
val securityStatus: LceFlow<Throwable, CheckTransactionResult>
}

View file

@ -4,13 +4,15 @@ import com.tangem.blockchain.common.TransactionData
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.walletconnect.model.WcSession
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.domain.walletconnect.usecase.blockaid.WcBlockAidEligibleTransactionUseCase
import com.tangem.domain.walletconnect.usecase.sign.WcSignState
import com.tangem.domain.walletconnect.usecase.sign.WcSignUseCase
import kotlinx.coroutines.flow.Flow
interface WcSolanaSignTransactionUseCase :
WcSignUseCase,
WcSignUseCase.SimpleRun<TransactionData.Compiled> {
WcSignUseCase.SimpleRun<TransactionData.Compiled>,
WcBlockAidEligibleTransactionUseCase {
override val network: Network
override val rawSdkRequest: WcSdkSessionRequest
@ -23,7 +25,8 @@ interface WcSolanaSignTransactionUseCase :
interface WcSolanaSignAllTransactionUseCase :
WcSignUseCase,
WcSignUseCase.SimpleRun<List<TransactionData.Compiled>> {
WcSignUseCase.SimpleRun<List<TransactionData.Compiled>>,
WcBlockAidEligibleTransactionUseCase {
override val network: Network
override val rawSdkRequest: WcSdkSessionRequest