Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-20 23:37:58 +07:00
parent fbace5ed35
commit da066d2ba4
14 changed files with 130 additions and 155 deletions

View file

@ -1,25 +0,0 @@
package com.tangem.data.walletconnect.network.ethereum
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
import com.tangem.domain.walletconnect.model.WcEthTransactionParams
import com.tangem.domain.walletconnect.usecase.method.WcEthTransaction
import com.tangem.utils.converter.Converter
internal class EthTransactionParamsConverter(
private val context: WcMethodUseCaseContext,
) : Converter<WcEthTransactionParams, WcEthTransaction?> {
override fun convert(value: WcEthTransactionParams): WcEthTransaction? {
val dAppFee = WcEthTxHelper.getDAppFee(context.network, value)
val transactionData = WcEthTxHelper.createTransactionData(
dAppFee = dAppFee,
network = context.network,
txParams = value,
)
transactionData ?: return null
return WcEthTransaction(
dAppFee = dAppFee,
transactionData = transactionData,
)
}
}

View file

@ -1,24 +1,26 @@
package com.tangem.data.walletconnect.network.ethereum
import arrow.core.left
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.formatHex
import com.tangem.core.analytics.api.AnalyticsEventHandler
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
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.SendTransactionUseCase
import com.tangem.domain.walletconnect.model.WcEthMethod
import com.tangem.domain.walletconnect.usecase.method.WcEthTransaction
import com.tangem.domain.walletconnect.usecase.method.WcEthTransactionUseCase
import com.tangem.domain.walletconnect.usecase.method.WcMutableFee
import com.tangem.domain.walletconnect.usecase.method.WcSignState
import com.tangem.domain.walletconnect.usecase.method.WcTransactionUseCase
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
@ -29,8 +31,9 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor(
override val analytics: AnalyticsEventHandler,
private val sendTransaction: SendTransactionUseCase,
blockAidDelegate: BlockAidVerificationDelegate,
) : BaseWcSignUseCase<Fee, WcEthTransaction>(),
WcEthTransactionUseCase {
) : BaseWcSignUseCase<Fee, TransactionData>(),
WcTransactionUseCase,
WcMutableFee {
override val securityStatus = blockAidDelegate.getSecurityStatus(
network = network,
@ -40,10 +43,8 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor(
accountAddress = context.accountAddress,
)
private val converter = EthTransactionParamsConverter(context)
override suspend fun SignCollector<WcEthTransaction>.onSign(state: WcSignState<WcEthTransaction>) {
val hash = sendTransaction(state.signModel.transactionData, wallet, network)
override suspend fun SignCollector<TransactionData>.onSign(state: WcSignState<TransactionData>) {
val hash = sendTransaction(state.signModel, wallet, network)
.onLeft { error ->
val sendError = IllegalArgumentException(error.toString()) // todo(wc) use domain error
emit(state.toResult(sendError.left()))
@ -53,13 +54,31 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor(
emit(state.toResult(respondResult))
}
override suspend fun FlowCollector<TransactionData>.onMiddleAction(signModel: TransactionData, fee: Fee) {
val uncompiled = signModel.requireUncompiled()
val newState = uncompiled.copy(fee = fee)
emit(newState)
}
override suspend fun dAppFee(): Fee.Ethereum.Legacy? {
val dAppFee = WcEthTxHelper.getDAppFee(
network = context.network,
txParams = method.transaction,
)
return dAppFee
}
override fun updateFee(fee: Fee) {
middleAction(fee)
}
override fun invoke(): Flow<WcSignState<WcEthTransaction>> = flow {
val ethTransaction = converter.convert(method.transaction) ?: return@flow
emitAll(delegate.invoke(ethTransaction))
override fun invoke(): Flow<WcSignState<TransactionData>> = flow {
val transactionData = WcEthTxHelper.createTransactionData(
dAppFee = dAppFee(),
network = context.network,
txParams = method.transaction,
) ?: return@flow
emitAll(delegate.invoke(transactionData))
}
@AssistedFactory

View file

@ -1,21 +1,22 @@
package com.tangem.data.walletconnect.network.ethereum
import arrow.core.left
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.formatHex
import com.tangem.common.extensions.toHexString
import com.tangem.core.analytics.api.AnalyticsEventHandler
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
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.WcEthMethod
import com.tangem.domain.walletconnect.usecase.method.WcEthTransaction
import com.tangem.domain.walletconnect.usecase.method.WcEthTransactionUseCase
import com.tangem.domain.walletconnect.usecase.method.WcMutableFee
import com.tangem.domain.walletconnect.usecase.method.WcSignState
import com.tangem.domain.walletconnect.usecase.method.WcTransactionUseCase
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -31,8 +32,9 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor(
@Assisted override val context: WcMethodUseCaseContext,
@Assisted override val method: WcEthMethod.SignTransaction,
blockAidDelegate: BlockAidVerificationDelegate,
) : BaseWcSignUseCase<Fee, WcEthTransaction>(),
WcEthTransactionUseCase {
) : BaseWcSignUseCase<Fee, TransactionData>(),
WcTransactionUseCase,
WcMutableFee {
override val securityStatus = blockAidDelegate.getSecurityStatus(
network = network,
@ -42,10 +44,8 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor(
accountAddress = context.accountAddress,
)
private val converter = EthTransactionParamsConverter(context)
override suspend fun SignCollector<WcEthTransaction>.onSign(state: WcSignState<WcEthTransaction>) {
val hash = prepareForSend(state.signModel.transactionData, wallet, network)
override suspend fun SignCollector<TransactionData>.onSign(state: WcSignState<TransactionData>) {
val hash = prepareForSend(state.signModel, wallet, network)
.map { it.toHexString().formatHex() }
.onLeft { error ->
emit(state.toResult(error.left()))
@ -56,9 +56,9 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor(
emit(state.toResult(respondResult))
}
override suspend fun FlowCollector<WcEthTransaction>.onMiddleAction(signModel: WcEthTransaction, fee: Fee) {
val newState = signModel
.copy(transactionData = signModel.transactionData.copy(fee = fee))
override suspend fun FlowCollector<TransactionData>.onMiddleAction(signModel: TransactionData, fee: Fee) {
val uncompiled = signModel.requireUncompiled()
val newState = uncompiled.copy(fee = fee)
emit(newState)
}
@ -66,9 +66,21 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor(
middleAction(fee)
}
override fun invoke(): Flow<WcSignState<WcEthTransaction>> = flow {
val ethTransaction = converter.convert(method.transaction) ?: return@flow
emitAll(delegate.invoke(ethTransaction))
override fun invoke(): Flow<WcSignState<TransactionData>> = flow {
val transactionData = WcEthTxHelper.createTransactionData(
dAppFee = dAppFee(),
network = context.network,
txParams = method.transaction,
) ?: return@flow
emitAll(delegate.invoke(transactionData))
}
override suspend fun dAppFee(): Fee.Ethereum.Legacy? {
val dAppFee = WcEthTxHelper.getDAppFee(
network = context.network,
txParams = method.transaction,
)
return dAppFee
}
@AssistedFactory

View file

@ -119,8 +119,8 @@ internal class WcSolanaNetwork(
internal class Factories @Inject constructor(
val messageSign: WcSolanaMessageSignUseCase.Factory,
val signTransaction: DefaultWcSolanaSignTransactionUseCase.Factory,
val signAllTransaction: DefaultWcSolanaSignAllTransactionUseCase.Factory,
val signTransaction: WcSolanaSignTransactionUseCase.Factory,
val signAllTransaction: WcSolanaSignAllTransactionUseCase.Factory,
)
companion object {

View file

@ -12,8 +12,8 @@ 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.WcListTransactionUseCase
import com.tangem.domain.walletconnect.usecase.method.WcSignState
import com.tangem.domain.walletconnect.usecase.solana.WcSolanaSignAllTransactionUseCase
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -22,15 +22,15 @@ import okio.ByteString.Companion.decodeBase64
import org.json.JSONArray
import org.json.JSONObject
internal class DefaultWcSolanaSignAllTransactionUseCase @AssistedInject constructor(
internal class WcSolanaSignAllTransactionUseCase @AssistedInject constructor(
override val respondService: WcRespondService,
override val analytics: AnalyticsEventHandler,
private val prepareForSend: PrepareForSendUseCase,
@Assisted override val context: WcMethodUseCaseContext,
@Assisted override val method: WcSolanaMethod.SignAllTransaction,
blockAidDelegate: BlockAidVerificationDelegate,
) : BaseWcSignUseCase<Nothing, List<TransactionData.Compiled>>(),
WcSolanaSignAllTransactionUseCase {
) : BaseWcSignUseCase<Nothing, List<TransactionData>>(),
WcListTransactionUseCase {
override val securityStatus = blockAidDelegate.getSecurityStatus(
network = network,
@ -40,9 +40,7 @@ internal class DefaultWcSolanaSignAllTransactionUseCase @AssistedInject construc
accountAddress = context.accountAddress,
)
override suspend fun SignCollector<List<TransactionData.Compiled>>.onSign(
state: WcSignState<List<TransactionData.Compiled>>,
) {
override suspend fun SignCollector<List<TransactionData>>.onSign(state: WcSignState<List<TransactionData>>) {
val hash = prepareForSend.invoke(transactionData = state.signModel, userWallet = wallet, network = network)
.onLeft { error ->
emit(state.toResult(error.left()))
@ -54,7 +52,7 @@ internal class DefaultWcSolanaSignAllTransactionUseCase @AssistedInject construc
emit(state.toResult(respondResult))
}
override fun invoke(): Flow<WcSignState<List<TransactionData.Compiled>>> {
override fun invoke(): Flow<WcSignState<List<TransactionData>>> {
val transactionData = method.transaction
.map { it.decodeBase64()?.toByteArray() ?: ByteArray(0) }
.map { TransactionData.Compiled(value = TransactionData.Compiled.Data.Bytes(it)) }
@ -85,6 +83,6 @@ internal class DefaultWcSolanaSignAllTransactionUseCase @AssistedInject construc
fun create(
context: WcMethodUseCaseContext,
method: WcSolanaMethod.SignAllTransaction,
): DefaultWcSolanaSignAllTransactionUseCase
): WcSolanaSignAllTransactionUseCase
}
}

View file

@ -13,22 +13,22 @@ 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.WcSignState
import com.tangem.domain.walletconnect.usecase.solana.WcSolanaSignTransactionUseCase
import com.tangem.domain.walletconnect.usecase.method.WcTransactionUseCase
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
import okio.ByteString.Companion.decodeBase64
internal class DefaultWcSolanaSignTransactionUseCase @AssistedInject constructor(
internal class WcSolanaSignTransactionUseCase @AssistedInject constructor(
override val respondService: WcRespondService,
override val analytics: AnalyticsEventHandler,
private val prepareForSend: PrepareForSendUseCase,
@Assisted override val context: WcMethodUseCaseContext,
@Assisted override val method: WcSolanaMethod.SignTransaction,
blockAidDelegate: BlockAidVerificationDelegate,
) : BaseWcSignUseCase<Nothing, TransactionData.Compiled>(),
WcSolanaSignTransactionUseCase {
) : BaseWcSignUseCase<Nothing, TransactionData>(),
WcTransactionUseCase {
override val securityStatus = blockAidDelegate.getSecurityStatus(
network = network,
@ -38,7 +38,7 @@ internal class DefaultWcSolanaSignTransactionUseCase @AssistedInject constructor
accountAddress = context.accountAddress,
)
override suspend fun SignCollector<TransactionData.Compiled>.onSign(state: WcSignState<TransactionData.Compiled>) {
override suspend fun SignCollector<TransactionData>.onSign(state: WcSignState<TransactionData>) {
val hash = prepareForSend.invoke(transactionData = state.signModel, userWallet = wallet, network = network)
.onLeft { error ->
emit(state.toResult(error.left()))
@ -50,7 +50,7 @@ internal class DefaultWcSolanaSignTransactionUseCase @AssistedInject constructor
emit(state.toResult(respondResult))
}
override fun invoke(): Flow<WcSignState<TransactionData.Compiled>> {
override fun invoke(): Flow<WcSignState<TransactionData>> {
val data = method.transaction.decodeBase64()?.toByteArray() ?: ByteArray(0)
val transactionData = TransactionData.Compiled(
@ -64,6 +64,6 @@ internal class DefaultWcSolanaSignTransactionUseCase @AssistedInject constructor
fun create(
context: WcMethodUseCaseContext,
method: WcSolanaMethod.SignTransaction,
): DefaultWcSolanaSignTransactionUseCase
): WcSolanaSignTransactionUseCase
}
}

View file

@ -13,7 +13,7 @@ import kotlinx.coroutines.flow.FlowCollector
internal abstract class BaseWcSignUseCase<MiddleAction, SignModel> :
WcMethodUseCase,
WcSignUseCase,
WcSignUseCase<SignModel>,
FinalActionCollector<SignModel>,
MiddleActionCollector<MiddleAction, SignModel> {

View file

@ -1,22 +0,0 @@
package com.tangem.domain.walletconnect.usecase.method
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.walletconnect.usecase.blockaid.WcBlockAidEligibleTransactionUseCase
/**
* UseCase for
* ## Ethereum
* eth_sendTransaction https://docs.reown.com/advanced/multichain/rpc-reference/ethereum-rpc#eth-sendtransaction
* eth_signTransaction https://docs.reown.com/advanced/multichain/rpc-reference/ethereum-rpc#eth-signtransaction
*/
interface WcEthTransactionUseCase :
WcSignUseCase,
WcSignUseCase.SimpleRun<WcEthTransaction>,
WcBlockAidEligibleTransactionUseCase,
WcMutableFee
data class WcEthTransaction(
val dAppFee: Fee.Ethereum.Legacy?,
val transactionData: TransactionData.Uncompiled,
)

View file

@ -14,8 +14,7 @@ import com.tangem.domain.walletconnect.usecase.blockaid.WcBlockAidEligibleTransa
* solana_signMessage https://docs.reown.com/advanced/multichain/rpc-reference/solana-rpc#solana-signmessage
*/
interface WcMessageSignUseCase :
WcSignUseCase,
WcSignUseCase.SimpleRun<WcMessageSignUseCase.SignModel>,
WcSignUseCase<WcMessageSignUseCase.SignModel>,
WcBlockAidEligibleTransactionUseCase {
data class SignModel(

View file

@ -1,7 +1,6 @@
package com.tangem.domain.walletconnect.usecase.method
import arrow.core.Either
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.models.network.Network
import com.tangem.domain.walletconnect.model.WcMethod
import com.tangem.domain.walletconnect.model.WcSession
@ -9,7 +8,7 @@ import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.domain.wallets.models.UserWallet
import kotlinx.coroutines.flow.Flow
interface WcMethodUseCase : WcMethodContext
interface WcMethodUseCase
interface WcMethodContext {
val session: WcSession
@ -20,29 +19,15 @@ interface WcMethodContext {
val wallet: UserWallet get() = session.wallet
}
/**
* Wc Methods that support an updatable fee
*/
interface WcMutableFee {
fun updateFee(fee: Fee)
}
/**
* Wc Methods that must be signed by Tangem card
* [WcSignState] represents signing state
*/
interface WcSignUseCase : WcMethodContext {
interface WcSignUseCase<SignModel> : WcMethodContext {
operator fun invoke(): Flow<WcSignState<SignModel>>
fun cancel()
fun sign()
interface SimpleRun<SignModel> {
operator fun invoke(): Flow<WcSignState<SignModel>>
}
interface ArgsRun<SignModel, Args> {
operator fun invoke(args: Args): Flow<WcSignState<SignModel>>
}
}
data class WcSignState<SignModel>(

View file

@ -0,0 +1,47 @@
package com.tangem.domain.walletconnect.usecase.method
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
*
* ## Ethereum
* eth_sendTransaction https://docs.reown.com/advanced/multichain/rpc-reference/ethereum-rpc#eth-sendtransaction
* eth_signTransaction https://docs.reown.com/advanced/multichain/rpc-reference/ethereum-rpc#eth-signtransaction
*
* ## Solana
* solana_signTransaction https://docs.reown.com/advanced/multichain/rpc-reference/solana-rpc#solana-signtransaction
*/
interface WcTransactionUseCase :
WcSignUseCase<TransactionData>,
WcBlockAidEligibleTransactionUseCase
/**
* Base UseCase for wc methods with list of TransactionData
*
* ## Solana
* solana_signAllTransactions https://docs.reown.com/advanced/multichain/rpc-reference/solana-rpc#solana-signalltransactions
*/
interface WcListTransactionUseCase :
WcSignUseCase<List<TransactionData>>,
WcBlockAidEligibleTransactionUseCase
/**
* Wc Methods that support an updatable fee
* [dAppFee] call to gee fee from dApp
* [updateFee] call when user update the fee
*/
interface WcMutableFee {
suspend fun dAppFee(): Fee?
fun updateFee(fee: Fee)
}
/**
* Wc Methods that support an updatable allowance
* [updateFee] call when user update the fee
*/
interface WcMutableAllowance {
fun updateAllowance(allowance: Any) // todo wc
}

View file

@ -1,38 +0,0 @@
package com.tangem.domain.walletconnect.usecase.solana
import com.tangem.blockchain.common.TransactionData
import com.tangem.domain.models.network.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.method.WcSignState
import com.tangem.domain.walletconnect.usecase.method.WcSignUseCase
import kotlinx.coroutines.flow.Flow
interface WcSolanaSignTransactionUseCase :
WcSignUseCase,
WcSignUseCase.SimpleRun<TransactionData.Compiled>,
WcBlockAidEligibleTransactionUseCase {
override val network: Network
override val rawSdkRequest: WcSdkSessionRequest
override val session: WcSession
override fun sign()
override fun cancel()
override fun invoke(): Flow<WcSignState<TransactionData.Compiled>>
}
interface WcSolanaSignAllTransactionUseCase :
WcSignUseCase,
WcSignUseCase.SimpleRun<List<TransactionData.Compiled>>,
WcBlockAidEligibleTransactionUseCase {
override val network: Network
override val rawSdkRequest: WcSdkSessionRequest
override val session: WcSession
override fun sign()
override fun cancel()
override fun invoke(): Flow<WcSignState<List<TransactionData.Compiled>>>
}

View file

@ -39,11 +39,11 @@ internal class WcSignTransactionModel @Inject constructor(
init {
modelScope.launch {
val useCase: WcSignUseCase.SimpleRun<*> = useCaseFactory.createUseCase(params.rawRequest)
val useCase: WcSignUseCase<*> = useCaseFactory.createUseCase(params.rawRequest)
useCase.invoke()
.onEach { signState ->
if (signingIsDone(signState)) return@onEach
val signTransactionUM = (useCase as? WcSignUseCase)?.toUM(
val signTransactionUM = useCase.toUM(
signState = signState,
actions = WcTransactionActionsUM(
onDismiss = { cancel(useCase) },
@ -69,7 +69,7 @@ internal class WcSignTransactionModel @Inject constructor(
return false
}
private fun cancel(useCase: WcSignUseCase) {
private fun cancel(useCase: WcSignUseCase<*>) {
useCase.cancel()
router.pop()
}

View file

@ -17,7 +17,7 @@ import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransacti
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
internal fun WcSignUseCase.toUM(signState: WcSignState<*>, actions: WcTransactionActionsUM): WcSignTransactionUM? {
internal fun WcSignUseCase<*>.toUM(signState: WcSignState<*>, actions: WcTransactionActionsUM): WcSignTransactionUM? {
return when (this) {
is WcMessageSignUseCase -> {
when (method) {