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

@ -36,7 +36,7 @@ import java.math.BigInteger
internal class DefaultTransactionRepository(
private val walletManagersFacade: WalletManagersFacade,
private val walletManagersStore: WalletManagersStore,
private val coroutineDispatcherProvider: CoroutineDispatcherProvider,
private val dispatchers: CoroutineDispatcherProvider,
) : TransactionRepository {
override suspend fun createTransaction(
@ -47,7 +47,7 @@ internal class DefaultTransactionRepository(
userWalletId: UserWalletId,
network: Network,
txExtras: TransactionExtras?,
): TransactionData.Uncompiled = withContext(coroutineDispatcherProvider.io) {
): TransactionData.Uncompiled = withContext(dispatchers.io) {
val blockchain = network.toBlockchain()
val walletManager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
@ -84,7 +84,7 @@ internal class DefaultTransactionRepository(
userWalletId: UserWalletId,
network: Network,
nonce: BigInteger?,
): TransactionData.Uncompiled = withContext(coroutineDispatcherProvider.io) {
): TransactionData.Uncompiled = withContext(dispatchers.io) {
val blockchain = network.toBlockchain()
val walletManager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
@ -138,7 +138,7 @@ internal class DefaultTransactionRepository(
spenderAddress: String,
userWalletId: UserWalletId,
network: Network,
): TransactionData.Uncompiled = withContext(coroutineDispatcherProvider.io) {
): TransactionData.Uncompiled = withContext(dispatchers.io) {
val blockchain = network.toBlockchain()
val extras = createTransactionDataExtras(
@ -171,7 +171,7 @@ internal class DefaultTransactionRepository(
destinationAddress: String,
userWalletId: UserWalletId,
network: Network,
): TransactionData.Uncompiled = withContext(coroutineDispatcherProvider.io) {
): TransactionData.Uncompiled = withContext(dispatchers.io) {
val blockchain = network.toBlockchain()
// For now transfer one nft asset at a time
@ -229,7 +229,7 @@ internal class DefaultTransactionRepository(
destination: String,
userWalletId: UserWalletId,
network: Network,
): Result<Unit> = withContext(coroutineDispatcherProvider.io) {
): Result<Unit> = withContext(dispatchers.io) {
val blockchain = network.toBlockchain()
val walletManager = walletManagersStore.getSyncOrNull(
userWalletId = userWalletId,
@ -260,7 +260,7 @@ internal class DefaultTransactionRepository(
signer: TransactionSigner,
userWalletId: UserWalletId,
network: Network,
) = withContext(coroutineDispatcherProvider.io) {
) = withContext(dispatchers.io) {
val blockchain = network.toBlockchain()
val walletManager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
@ -276,7 +276,7 @@ internal class DefaultTransactionRepository(
userWalletId: UserWalletId,
network: Network,
sendMode: TransactionSender.MultipleTransactionSendMode,
) = withContext(coroutineDispatcherProvider.io) {
) = withContext(dispatchers.io) {
val blockchain = network.toBlockchain()
val walletManager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
@ -366,13 +366,9 @@ internal class DefaultTransactionRepository(
signer: TransactionSigner,
userWalletId: UserWalletId,
network: Network,
): Result<ByteArray> = withContext(coroutineDispatcherProvider.io) {
) = withContext(dispatchers.io) {
val preparer = getPreparer(network, userWalletId)
when (val prepareForSend = preparer.prepareForSend(transactionData, signer)) {
is com.tangem.blockchain.extensions.Result.Failure -> Result.failure(prepareForSend.error)
is com.tangem.blockchain.extensions.Result.Success -> Result.success(prepareForSend.data)
}
preparer.prepareForSend(transactionData, signer)
}
override suspend fun prepareForSendMultiple(
@ -380,13 +376,9 @@ internal class DefaultTransactionRepository(
signer: TransactionSigner,
userWalletId: UserWalletId,
network: Network,
): Result<List<ByteArray>> = withContext(coroutineDispatcherProvider.io) {
) = withContext(dispatchers.io) {
val preparer = getPreparer(network, userWalletId)
when (val prepareForSend = preparer.prepareForSendMultiple(transactionData, signer)) {
is com.tangem.blockchain.extensions.Result.Failure -> Result.failure(prepareForSend.error)
is com.tangem.blockchain.extensions.Result.Success -> Result.success(prepareForSend.data)
}
preparer.prepareForSendMultiple(transactionData, signer)
}
private suspend fun getPreparer(network: Network, userWalletId: UserWalletId): TransactionPreparer {

View file

@ -24,12 +24,12 @@ internal object TransactionDataModule {
fun providesTransactionRepository(
walletManagersFacade: WalletManagersFacade,
walletManagersStore: WalletManagersStore,
coroutineDispatcherProvider: CoroutineDispatcherProvider,
dispatchers: CoroutineDispatcherProvider,
): TransactionRepository {
return DefaultTransactionRepository(
walletManagersFacade = walletManagersFacade,
walletManagersStore = walletManagersStore,
coroutineDispatcherProvider = coroutineDispatcherProvider,
dispatchers = dispatchers,
)
}

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)