Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-10 22:47:25 +07:00
parent 67457f0746
commit 63a718d771
25 changed files with 186 additions and 85 deletions

View file

@ -5,6 +5,7 @@ import com.tangem.data.walletconnect.respond.WcRespondService
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
import com.tangem.domain.models.network.Network
import com.tangem.domain.walletconnect.model.WcEthMethod
import com.tangem.domain.walletconnect.model.WcRequestError
import com.tangem.domain.walletconnect.model.WcSession
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.domain.walletconnect.usecase.method.WcAddNetworkUseCase
@ -27,7 +28,7 @@ internal class WcEthAddNetworkUseCase @AssistedInject constructor(
override val walletAddress: String
get() = context.accountAddress
override suspend fun approve(): Either<Throwable, Unit> {
override suspend fun approve(): Either<WcRequestError, String> {
return respondService.respond(rawSdkRequest, "")
}

View file

@ -19,6 +19,7 @@ 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.SignUseCase
import com.tangem.domain.walletconnect.error.parseTangemSdkError
import com.tangem.domain.walletconnect.model.WcEthMethod
import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase
import com.tangem.domain.walletconnect.usecase.method.WcSignState
@ -59,7 +60,7 @@ internal class WcEthMessageSignUseCase @AssistedInject constructor(
?: return
val signedHash = signUseCase(hashToSign, userWallet, network)
.onLeft { emit(state.toResult(it.left())) }
.onLeft { emit(state.toResult(parseTangemSdkError(it).left())) }
.getOrNull() ?: return
val respond = prepareToSendMessageData(signedHash, hashToSign, walletManager)

View file

@ -17,6 +17,7 @@ import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.tokens.model.Amount
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.walletconnect.error.parseSendError
import com.tangem.domain.walletconnect.model.WcApprovedAmount
import com.tangem.domain.walletconnect.model.WcEthMethod
import com.tangem.domain.walletconnect.usecase.method.*
@ -80,8 +81,7 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor(
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()))
emit(state.toResult(parseSendError(error).left()))
}
.getOrNull() ?: return
val respondResult = respondService.respond(rawSdkRequest, hash.formatHex())

View file

@ -17,6 +17,7 @@ import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate
import com.tangem.domain.tokens.model.Amount
import com.tangem.domain.transaction.usecase.PrepareForSendUseCase
import com.tangem.domain.walletconnect.error.parseSendError
import com.tangem.domain.walletconnect.model.WcApprovedAmount
import com.tangem.domain.walletconnect.model.WcEthMethod
import com.tangem.domain.walletconnect.usecase.method.*
@ -82,7 +83,7 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor(
val hash = prepareForSend(state.signModel, wallet, network)
.map { it.toHexString().formatHex() }
.onLeft { error ->
emit(state.toResult(error.left()))
emit(state.toResult(parseSendError(error).left()))
}
.getOrNull()
?: return

View file

@ -11,6 +11,7 @@ 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.SignUseCase
import com.tangem.domain.walletconnect.error.parseTangemSdkError
import com.tangem.domain.walletconnect.model.WcEthMethod
import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase
import com.tangem.domain.walletconnect.usecase.method.WcSignState
@ -51,7 +52,7 @@ internal class WcEthSignTypedDataUseCase @AssistedInject constructor(
?: return
val signedHash = signUseCase(hashToSign, userWallet, network)
.onLeft { emit(state.toResult(it.left())) }
.onLeft { emit(state.toResult(parseTangemSdkError(it).left())) }
.getOrNull() ?: return
val respond = prepareToSendMessageData(signedHash, hashToSign, walletManager)

View file

@ -12,6 +12,7 @@ import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.transaction.usecase.SignUseCase
import com.tangem.domain.walletconnect.error.parseTangemSdkError
import com.tangem.domain.walletconnect.model.WcSolanaMethod
import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase
import com.tangem.domain.walletconnect.usecase.method.WcSignState
@ -40,7 +41,7 @@ internal class WcSolanaMessageSignUseCase @AssistedInject constructor(
val userWallet = session.wallet
val signedHash = signUseCase(hashToSign, userWallet, network)
.onLeft { emit(state.toResult(it.left())) }
.onLeft { emit(state.toResult(parseTangemSdkError(it).left())) }
.getOrNull() ?: return
val respond = "{ signature: \"${signedHash.encodeBase58()}\" }"

View file

@ -11,6 +11,7 @@ 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.error.parseSendError
import com.tangem.domain.walletconnect.model.WcSolanaMethod
import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck
import com.tangem.domain.walletconnect.usecase.method.WcListTransactionUseCase
@ -47,7 +48,7 @@ internal class WcSolanaSignAllTransactionUseCase @AssistedInject constructor(
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()))
emit(state.toResult(parseSendError(error).left()))
}
.getOrNull()
?: return

View file

@ -11,6 +11,7 @@ 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.error.parseSendError
import com.tangem.domain.walletconnect.model.WcSolanaMethod
import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck
import com.tangem.domain.walletconnect.usecase.method.WcSignState
@ -45,7 +46,7 @@ internal class WcSolanaSignTransactionUseCase @AssistedInject constructor(
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()))
emit(state.toResult(parseSendError(error).left()))
}
.getOrNull()
?: return

View file

@ -6,6 +6,7 @@ import arrow.core.right
import com.reown.walletkit.client.Wallet
import com.reown.walletkit.client.WalletKit
import com.tangem.data.walletconnect.utils.WC_TAG
import com.tangem.domain.walletconnect.model.WcRequestError
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import kotlinx.coroutines.suspendCancellableCoroutine
import timber.log.Timber
@ -13,7 +14,7 @@ import kotlin.coroutines.resume
internal class DefaultWcRespondService : WcRespondService {
override suspend fun respond(request: WcSdkSessionRequest, response: String): Either<Throwable, Unit> =
override suspend fun respond(request: WcSdkSessionRequest, response: String): Either<WcRequestError, String> =
suspendCancellableCoroutine { continuation ->
WalletKit.respondSessionRequest(
params = Wallet.Params.SessionRequestResponse(
@ -26,12 +27,19 @@ internal class DefaultWcRespondService : WcRespondService {
onSuccess = {
if (continuation.isCompleted) return@respondSessionRequest
Timber.tag(WC_TAG).i("Successful respond for request $request")
continuation.resume(Unit.right())
val result = when (val response = it.jsonRpcResponse) {
is Wallet.Model.JsonRpcResponse.JsonRpcError -> WcRequestError.WcRespondError(
code = response.code,
message = response.message,
).left()
is Wallet.Model.JsonRpcResponse.JsonRpcResult -> response.result.right()
}
continuation.resume(result)
},
onError = {
if (continuation.isCompleted) return@respondSessionRequest
Timber.tag(WC_TAG).e(it.throwable, "Failed respond for request $request")
continuation.resume(it.throwable.left())
continuation.resume(WcRequestError.UnknownError(it.throwable).left())
},
)
}

View file

@ -1,9 +1,10 @@
package com.tangem.data.walletconnect.respond
import arrow.core.Either
import com.tangem.domain.walletconnect.model.WcRequestError
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
interface WcRespondService {
suspend fun respond(request: WcSdkSessionRequest, response: String): Either<Throwable, Unit>
suspend fun respond(request: WcSdkSessionRequest, response: String): Either<WcRequestError, String>
fun rejectRequestNonBlock(request: WcSdkSessionRequest, message: String = "")
}

View file

@ -1,16 +1,12 @@
package com.tangem.data.walletconnect.sign
import arrow.core.Either
import com.tangem.domain.walletconnect.model.WcRequestError
import com.tangem.domain.walletconnect.usecase.method.WcSignState
import com.tangem.domain.walletconnect.usecase.method.WcSignStep
object SignStateConverter {
internal fun <M> preSign(signModel: M) = WcSignState(signModel, WcSignStep.PreSign)
internal fun <M> signing(signModel: M) = WcSignState(signModel, WcSignStep.Signing)
internal fun <M> result(result: Either<Throwable, Unit>, signModel: M) =
WcSignState(signModel, WcSignStep.Result(result))
internal fun <M> WcSignState<M>.toPreSign(signModel: M = this.signModel) = copy(
signModel = signModel,
domainStep = WcSignStep.PreSign,
@ -21,8 +17,9 @@ object SignStateConverter {
signModel = signModel,
)
internal fun <M> WcSignState<M>.toResult(result: Either<Throwable, Unit>, signModel: M = this.signModel) = copy(
domainStep = WcSignStep.Result(result),
signModel = signModel,
)
internal fun <M> WcSignState<M>.toResult(result: Either<WcRequestError, String>, signModel: M = this.signModel) =
copy(
domainStep = WcSignStep.Result(result),
signModel = signModel,
)
}

View file

@ -6,6 +6,8 @@ import com.tangem.data.walletconnect.sign.SignStateConverter.toPreSign
import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
import com.tangem.data.walletconnect.sign.SignStateConverter.toSigning
import com.tangem.domain.walletconnect.WcAnalyticEvents
import com.tangem.domain.walletconnect.model.WcRequestError
import com.tangem.domain.walletconnect.model.WcRequestError.Companion.code
import com.tangem.domain.walletconnect.usecase.method.WcSignState
import com.tangem.domain.walletconnect.usecase.method.WcSignStep
import kotlinx.coroutines.Job
@ -62,18 +64,19 @@ internal class WcSignUseCaseDelegate<MiddleAction, SignModel>(
fun signFlow() = flow { onSign(state.updateAndGet { it.toSigning() }) }
.onEach { newState -> state.update { newState } }
.catch { exception ->
val errorResult = state.value.toResult(exception.left())
val errorResult = state.value
.toResult(WcRequestError.UnknownError(exception).left())
state.update { errorResult }
}
.onEach { state ->
val step = state.domainStep as? WcSignStep.Result ?: return@onEach
val event = step.result.fold(
ifLeft = {
ifLeft = { error ->
WcAnalyticEvents.SignatureRequestFailed(
session = context.session,
rawRequest = context.rawSdkRequest,
network = context.network,
it.message.orEmpty(),
errorCode = error.code() ?: error::class.simpleName.orEmpty(),
)
},
ifRight = {

View file

@ -10,6 +10,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.data.walletconnect.sign.*
import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
import com.tangem.data.walletconnect.sign.SignStateConverter.toSigning
import com.tangem.domain.walletconnect.model.WcRequestError
import com.tangem.domain.walletconnect.model.WcSession
import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSession
@ -32,6 +33,7 @@ internal class WcSignUseCaseDelegateTest {
object : FinalActionCollector<TestSignModel> {}
private val initSignModel = TestSignModel()
private val analytics: AnalyticsEventHandler = mockk<AnalyticsEventHandler>(relaxed = true)
private val simpleResult = "hex".right()
private val rawRequestMock = WcSdkSessionRequest(
topic = "",
chainId = "",
@ -64,14 +66,14 @@ internal class WcSignUseCaseDelegateTest {
private val initState = WcSignState(initSignModel, WcSignStep.PreSign)
private val signing = initState.toSigning()
private val result = signing.toResult(Unit.right())
private val testException = RuntimeException("test")
private val result = signing.toResult(simpleResult)
private val testException = WcRequestError.UnknownError(RuntimeException("test"))
private val successSign: suspend FlowCollector<WcSignState<TestSignModel>>.(
currentState: WcSignState<TestSignModel>,
) -> Unit = { state ->
delay(2)
emit(state.toResult(Unit.right()))
emit(state.toResult(simpleResult))
}
private val failedSign: suspend FlowCollector<WcSignState<TestSignModel>>.(
@ -161,8 +163,9 @@ internal class WcSignUseCaseDelegateTest {
@Test
fun `failed sign and catch unknown exception`() = runTest {
val exception = RuntimeException("asd")
val expectedErrorState = signing.toResult(exception.left())
val exception = RuntimeException("test")
val testException = WcRequestError.UnknownError(exception)
val expectedErrorState = signing.toResult(testException.left())
finalActionCollector = object : FinalActionCollector<TestSignModel> {
override suspend fun SignCollector<TestSignModel>.onSign(state: WcSignState<TestSignModel>) {
delay(2)