Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-02 16:22:47 +07:00
parent c039057700
commit 47ba4043a3
10 changed files with 30 additions and 16 deletions

View file

@ -43,6 +43,7 @@ internal object WalletConnectInteractorModule {
currenciesRepository: CurrenciesRepository,
walletManagersFacade: WalletManagersFacade,
userWalletsListManager: UserWalletsListManager,
walletConnectFeatureToggles: WalletConnectFeatureToggles,
coroutineDispatcherProvider: CoroutineDispatcherProvider,
): WalletConnectInteractor {
return WalletConnectInteractor(
@ -55,6 +56,7 @@ internal object WalletConnectInteractorModule {
walletManagersFacade = walletManagersFacade,
userWalletsListManager = userWalletsListManager,
dispatchers = coroutineDispatcherProvider,
walletConnectFeatureToggles = walletConnectFeatureToggles,
)
}
}

View file

@ -16,6 +16,7 @@ import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.isMultiCurrency
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.filterNotNull
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
@ -39,8 +40,10 @@ class WalletConnectInteractor(
private val walletManagersFacade: WalletManagersFacade,
private val currenciesRepository: CurrenciesRepository,
private val userWalletsListManager: UserWalletsListManager,
private val walletConnectFeatureToggles: WalletConnectFeatureToggles,
val blockchainHelper: WcBlockchainHelper,
) {
private val isNewWc by lazy { walletConnectFeatureToggles.isRedesignedWalletConnectEnabled }
private var isWalletConnectReadyForDeepLinks = false
@ -88,6 +91,7 @@ class WalletConnectInteractor(
}
private fun initWithWallet(userWallet: UserWallet) {
if (isNewWc) return
if (userWallet.isMultiCurrency) {
Timber.i("WalletConnect: initialize and setup networks for ${userWallet.walletId}")
startListeningWc(userWallet.walletId.stringValue, getCardId(userWallet))
@ -243,6 +247,7 @@ class WalletConnectInteractor(
}
fun approveSessionProposal(accounts: List<Account>) {
if (isNewWc) return
Timber.i("Approve session proposal: $accounts")
val userNamespaces: Map<NetworkNamespace, List<Account>> = accounts
.groupBy { account ->
@ -259,16 +264,19 @@ class WalletConnectInteractor(
}
fun rejectSessionProposal() {
if (isNewWc) return
Timber.i("Reject session proposal")
walletConnectRepository.reject()
}
fun disconnectSession(topic: String) {
if (isNewWc) return
Timber.i("Disconnect session: $topic")
walletConnectRepository.disconnect(topic)
}
fun cancelRequest(topic: String, id: Long) {
if (isNewWc) return
Timber.i("Cancel request: $topic, $id")
walletConnectRepository.cancelRequest(topic, id)
}
@ -323,6 +331,7 @@ class WalletConnectInteractor(
}
suspend fun continueWithRequest(request: WcPreparedRequest) {
if (isNewWc) return
val currentRequest = this.currentRequest
if (currentRequest == null || request.topic != currentRequest.topic) return
@ -386,6 +395,7 @@ class WalletConnectInteractor(
* @param deeplink deeplink to handle
*/
fun addDeeplink(deeplink: String) {
if (isNewWc) return
val deeplinkRegex = Regex(WC_PARAM_REGEX)
val matched = deeplinkRegex.findAll(deeplink)
val sessionTopic = matched.firstOrNull { it.value.contains(WC_TOPIC_QUERY_NAME) }?.groupValues?.lastOrNull()

View file

@ -108,6 +108,7 @@ internal class DeepLinkFactory @Inject constructor(
}
}
@Suppress("CyclomaticComplexMethod")
private fun handleTangemDeepLinks(deeplinkUri: Uri, coroutineScope: CoroutineScope, isFromOnNewIntent: Boolean) {
val queryParams = getQueryParams(deeplinkUri)
when (deeplinkUri.host) {
@ -127,6 +128,7 @@ internal class DeepLinkFactory @Inject constructor(
DeepLinkRoute.Buy.host -> buyDeepLink.create()
DeepLinkRoute.Sell.host -> sellDeepLink.create()
DeepLinkRoute.Swap.host -> swapDeepLink.create()
DeepLinkRoute.WalletConnect.host -> walletConnectDeepLink.create(deeplinkUri)
else -> {
Timber.i(
"""

View file

@ -51,6 +51,10 @@ sealed class DeepLinkRoute {
data object Swap : DeepLinkRoute() {
override val host: String = "swap"
}
data object WalletConnect : DeepLinkRoute() {
override val host: String = "wc"
}
}
enum class DeepLinkScheme(val scheme: String) {

View file

@ -110,15 +110,11 @@ internal class WcEthNetwork(
}
internal class NamespaceConverter(
private val excludedBlockchains: ExcludedBlockchains,
override val excludedBlockchains: ExcludedBlockchains,
) : WcNamespaceConverter {
override val namespaceKey: NamespaceKey = NamespaceKey("eip155")
override fun toNetwork(chainId: String, wallet: UserWallet): Network? {
return toNetwork(chainId, wallet, excludedBlockchains)
}
override fun toBlockchain(chainId: CAIP2): Blockchain? {
if (chainId.namespace != namespaceKey.key) return null
val ethChainId = chainId.reference.toIntOrNull() ?: return null

View file

@ -18,7 +18,6 @@ import com.tangem.domain.walletconnect.model.WcSolanaMethodName
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.domain.walletconnect.repository.WcSessionsManager
import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.lib.crypto.UserWalletManager
import jakarta.inject.Inject
import timber.log.Timber
@ -66,7 +65,7 @@ internal class WcSolanaNetwork(
}
internal class NamespaceConverter @Inject constructor(
private val excludedBlockchains: ExcludedBlockchains,
override val excludedBlockchains: ExcludedBlockchains,
) : WcNamespaceConverter {
override val namespaceKey: NamespaceKey = NamespaceKey("solana")
@ -80,10 +79,6 @@ internal class WcSolanaNetwork(
}
}
override fun toNetwork(chainId: String, wallet: UserWallet): Network? {
return toNetwork(chainId, wallet, excludedBlockchains)
}
override fun toCAIP2(network: Network): CAIP2? {
val blockchain = network.toBlockchain()
val chainId = when (blockchain) {

View file

@ -105,6 +105,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
newSession
}.onLeft {
analytics.send(WcAnalyticEvents.DAppConnectionFailed(it.code))
sdkDelegate.rejectSession(sdkSessionProposal.proposerPublicKey)
Timber.tag(WC_TAG).e(it, "Failed to approve session ${sdkSessionProposal.name}")
}
emit(WcPairState.Approving.Result(sessionForApprove, either))

View file

@ -13,7 +13,9 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeout
import kotlin.coroutines.resume
import kotlin.time.Duration.Companion.seconds
internal class WcPairSdkDelegate : WcSdkObserver {
@ -26,7 +28,7 @@ internal class WcPairSdkDelegate : WcSdkObserver {
.first()
val pairCall = async { sdkPair(url) }
val proposal = async { proposalCallback() }
val proposal = async { withTimeout(20.seconds) { proposalCallback() } }
pairCall.await().onLeft {
proposal.cancel()
return@coroutineScope it.left()
@ -42,7 +44,7 @@ internal class WcPairSdkDelegate : WcSdkObserver {
.first()
val approveCall = async { sdkApprove(sessionApprove) }
val approveCallback = async { approveCallback() }
val approveCallback = async { withTimeout(20.seconds) { approveCallback() } }
approveCall.await()
.onLeft {
approveCallback.cancel()

View file

@ -12,6 +12,7 @@ import com.tangem.domain.wallets.models.requireColdWallet
internal interface WcNamespaceConverter {
val namespaceKey: NamespaceKey
val excludedBlockchains: ExcludedBlockchains
fun toBlockchain(chainId: CAIP2): Blockchain?
fun toBlockchain(chainId: String): Blockchain? = toCAIP2(chainId)?.let { caip2 -> toBlockchain(caip2) }
@ -19,9 +20,9 @@ internal interface WcNamespaceConverter {
fun toCAIP2(network: Network): CAIP2?
fun toCAIP2(chainId: String): CAIP2? = CAIP2.fromRaw(chainId)
fun toNetwork(chainId: String, wallet: UserWallet): Network?
fun toNetwork(chainId: String, wallet: UserWallet, excludedBlockchains: ExcludedBlockchains): Network? {
fun toNetwork(chainId: String, wallet: UserWallet): Network? {
val blockchain = toBlockchain(chainId) ?: return null
if (blockchain.isTestnet()) return null
return NetworkFactory(excludedBlockchains).create(
blockchain = blockchain,

View file

@ -232,6 +232,7 @@ internal class DefaultWcPairUseCaseTest {
val error = WcPairError.ApprovalFailed("error").left()
coEvery { sdkDelegate.pair(url) } returns sdkProposal.right()
coEvery { sdkDelegate.approve(sdkApprove) } returns error
coEvery { sdkDelegate.rejectSession(sdkApprove.proposerPublicKey) } returns Unit
coEvery { blockAidVerifier.verifyDApp(any()) } returns Either.catch { CheckDAppResult.SAFE }
val errorResult = WcPairState.Approving.Result(sessionForApprove, error)
@ -250,8 +251,8 @@ internal class DefaultWcPairUseCaseTest {
assertEquals(approveLoading, awaitItem())
coVerifyOrder {
sdkDelegate.approve(sdkApprove)
sdkDelegate.rejectSession(sdkApprove.proposerPublicKey)
}
assertEquals(errorResult, awaitItem())
awaitComplete()
}