Updated on 2026-08-14

This commit is contained in:
Tangem 2025-06-10 18:50:05 +08:00
parent 69fec5dc58
commit af243495bd
6 changed files with 47 additions and 25 deletions

View file

@ -28,7 +28,7 @@ internal class AssociateNetworksDelegate(
}
}
@Throws(WcPairError.UnsupportedNetworks::class)
@Throws(WcPairError.UnsupportedBlockchains::class)
suspend fun associate(sessionProposal: Wallet.Model.SessionProposal): Map<UserWallet, ProposalNetwork> {
val userWallets = getWallets.invokeSync().filter { it.isMultiCurrency }
val requiredNamespaces: Set<String> = sessionProposal.requiredNamespaces.setOfChainId()
@ -74,7 +74,7 @@ internal class AssociateNetworksDelegate(
notAdded.add(wcNetwork)
}
}
if (unknownRequired.isNotEmpty()) throw WcPairError.UnsupportedNetworks(unknownRequired)
if (unknownRequired.isNotEmpty()) throw WcPairError.UnsupportedBlockchains(unknownRequired)
return ProposalNetwork(
wallet = wallet,
missingRequired = missingRequired,

View file

@ -56,7 +56,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
// check unsupported dApps, just local constant for now, finish if unsupported
if (sdkSessionProposal.name in unsupportedDApps) {
Timber.tag(WC_TAG).i("Unsupported DApp ${sdkSessionProposal.name}")
val error = WcPairState.Error(WcPairError.UnsupportedDApp)
val error = WcPairState.Error(WcPairError.UnsupportedDomain)
emit(error)
return@flow
}
@ -104,7 +104,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
)
newSession
}.onLeft {
analytics.send(WcAnalyticEvents.DAppConnectionFailed(it.message))
analytics.send(WcAnalyticEvents.DAppConnectionFailed(it.code))
Timber.tag(WC_TAG).e(it, "Failed to approve session ${sdkSessionProposal.name}")
}
emit(WcPairState.Approving.Result(sessionForApprove, either))

View file

@ -27,7 +27,11 @@ internal class WcPairSdkDelegate : WcSdkObserver {
val pairCall = async { sdkPair(url) }
val proposal = async { proposalCallback() }
pairCall.await().map { proposal.await() }
pairCall.await().onLeft {
proposal.cancel()
return@coroutineScope it.left()
}
proposal.await().right()
}
suspend fun approve(
@ -40,11 +44,14 @@ internal class WcPairSdkDelegate : WcSdkObserver {
val approveCall = async { sdkApprove(sessionApprove) }
val approveCallback = async { approveCallback() }
approveCall.await()
.onLeft { return@coroutineScope it.left() }
.onLeft {
approveCallback.cancel()
return@coroutineScope it.left()
}
when (val result = approveCallback.await()) {
is Wallet.Model.SettledSessionResponse.Result -> result.right()
is Wallet.Model.SettledSessionResponse.Error ->
WcPairError.ExternalApprovalError(result.errorMessage).left()
WcPairError.ApprovalFailed(result.errorMessage).left()
}
}
@ -77,7 +84,7 @@ internal class WcPairSdkDelegate : WcSdkObserver {
WalletKit.approveSession(
params = sessionApprove,
onSuccess = { continuation.resume(Unit.right()) },
onError = { continuation.resume(it.throwable.toPairError()) },
onError = { continuation.resume(it.throwable.toApproveError()) },
)
}
}
@ -87,10 +94,20 @@ internal class WcPairSdkDelegate : WcSdkObserver {
WalletKit.pair(
params = Wallet.Params.Pair(uri),
onSuccess = { continuation.resume(Unit.right()) },
onError = { continuation.resume(it.throwable.toPairError()) },
onError = { continuation.resume(it.throwable.toPairError().left()) },
)
}
}
private fun Throwable.toPairError() = WcPairError.ExternalApprovalError(this.localizedMessage.orEmpty()).left()
private fun Throwable.toPairError() = when {
pairingExpiredMessages.any { message.orEmpty().contains(it) } -> WcPairError.UriAlreadyUsed(message.orEmpty())
else -> WcPairError.PairingFailed(this.localizedMessage.orEmpty())
}
private fun Throwable.toApproveError() = WcPairError.ApprovalFailed(this.localizedMessage.orEmpty()).left()
companion object {
// com.reown.android.pairing.engine.domain.PairingEngine.pair
private val pairingExpiredMessages = listOf("Pairing URI expired", "Pairing expired")
}
}

View file

@ -196,7 +196,7 @@ internal class DefaultWcPairUseCaseTest {
@Test
fun `success pair and reject unsupported dApp`() = runTest {
coEvery { sdkDelegate.pair(url) } returns unsupportedSdkProposal.right()
val unsupportedDAppError = WcPairState.Error(WcPairError.UnsupportedDApp)
val unsupportedDAppError = WcPairState.Error(WcPairError.UnsupportedDomain)
val useCase = useCaseFactory()
useCase.invoke().test {
@ -211,7 +211,7 @@ internal class DefaultWcPairUseCaseTest {
@Test
fun `complete on pair error`() = runTest {
val error = WcPairError.ExternalApprovalError("error")
val error = WcPairError.PairingFailed("error")
coEvery { sdkDelegate.pair(url) } returns error.left()
val errorState = WcPairState.Error(error)
@ -229,7 +229,7 @@ internal class DefaultWcPairUseCaseTest {
@Test
fun `complete on approve error`() = runTest {
val approveLoading = WcPairState.Approving.Loading(sessionForApprove)
val error = WcPairError.ExternalApprovalError("error").left()
val error = WcPairError.ApprovalFailed("error").left()
coEvery { sdkDelegate.pair(url) } returns sdkProposal.right()
coEvery { sdkDelegate.approve(sdkApprove) } returns error
coEvery { blockAidVerifier.verifyDApp(any()) } returns Either.catch { CheckDAppResult.SAFE }

View file

@ -1,15 +1,19 @@
package com.tangem.domain.walletconnect.model
sealed class WcPairError(override val message: String) : Exception(message) {
sealed class WcPairError(
val code: String,
override val message: String = "",
) : Exception() {
data object UnsupportedDApp : WcPairError("UnsupportedDApp")
data class UnsupportedNetworks(
val chains: Set<String>,
) : WcPairError("ApprovalErrorMissingNetworks")
data class ExternalApprovalError(
override val message: String,
) : WcPairError("ExternalApprovalError")
data class UriAlreadyUsed(override val message: String) : WcPairError("107 001 001")
data class PairingFailed(override val message: String) : WcPairError("107 001 002")
data object InvalidDomainURL : WcPairError("107 001 003")
data object UnsupportedDomain : WcPairError("107 001 004")
data class UnsupportedBlockchains(val chains: Set<String>) : WcPairError("107 001 005")
data object InvalidConnectionRequest : WcPairError("107 002 001")
data object ProposalExpired : WcPairError("107 002 002")
data class ApprovalFailed(override val message: String) : WcPairError("107 002 003")
data object RejectionFailed : WcPairError("107 002 004")
data class Unknown(override val message: String) : WcPairError(message)
}

View file

@ -13,6 +13,7 @@ import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.ToastMessage
import com.tangem.domain.models.network.Network
import com.tangem.domain.walletconnect.model.WcPairError
import com.tangem.domain.walletconnect.model.WcPairRequest
import com.tangem.domain.walletconnect.model.WcSessionApprove
import com.tangem.domain.walletconnect.model.WcSessionProposal
@ -32,7 +33,6 @@ import com.tangem.features.walletconnect.connections.model.transformers.WcDAppVe
import com.tangem.features.walletconnect.connections.routes.WcAppInfoRoutes
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
import timber.log.Timber
import javax.inject.Inject
import kotlin.properties.Delegates
import com.tangem.utils.transformer.update as transformerUpdate
@ -78,6 +78,7 @@ internal class WcPairModel @Inject constructor(
}
private fun loadDAppInfo() {
fun errorToast(error: WcPairError) = messageSender.send(ToastMessage(message = stringReference(error.message)))
wcPairUseCase()
.onEach { pairState ->
when (pairState) {
@ -88,14 +89,14 @@ internal class WcPairModel @Inject constructor(
appInfoUiState.transformerUpdate(
WcConnectButtonProgressTransformer(showProgress = false),
)
pairState.result.onLeft(::errorToast)
router.pop()
}
is WcPairState.Error -> {
appInfoUiState.transformerUpdate(
WcConnectButtonProgressTransformer(showProgress = false),
)
messageSender.send(ToastMessage(message = stringReference(pairState.error.message)))
Timber.e(pairState.error)
errorToast(pairState.error)
}
is WcPairState.Loading -> appInfoUiState.update { createLoadingState() }
is WcPairState.Proposal -> {