From bbd1fc9ace4cb3238cf555956eda69143b306117 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 16 Jul 2025 20:11:00 +0700 Subject: [PATCH] Updated on 2026-08-14 --- .../di/WalletConnectDataModule.kt | 31 ++++-- .../network/ethereum/WcEthNetwork.kt | 33 ++++-- .../walletconnect/network/solana/Model.kt | 3 + .../network/solana/WcSolanaNetwork.kt | 42 ++++---- .../pair/AssociateNetworksDelegate.kt | 21 ++-- .../pair/CaipNamespaceDelegate.kt | 60 ++++++----- .../pair/DefaultWcPairUseCase.kt | 3 +- .../sessions/DefaultWcSessionsManager.kt | 32 ++---- .../utils/WcNamespaceConverter.kt | 2 +- .../utils/WcNetworksConverter.kt | 102 ++++++++++++++++++ .../walletconnect/DefaultWcPairUseCaseTest.kt | 3 +- .../walletconnect/model/WcSolanaMethod.kt | 1 + .../model/WcConnectedAppInfoModel.kt | 1 + 13 files changed, 231 insertions(+), 103 deletions(-) create mode 100644 data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt index 0143e8b307..40593c27e5 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt @@ -16,6 +16,7 @@ import com.tangem.data.walletconnect.respond.DefaultWcRespondService import com.tangem.data.walletconnect.respond.WcRespondService import com.tangem.data.walletconnect.sessions.DefaultWcSessionsManager import com.tangem.data.walletconnect.utils.WcNamespaceConverter +import com.tangem.data.walletconnect.utils.WcNetworksConverter import com.tangem.datasource.di.SdkMoshi import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.walletconnect.WalletConnectStore @@ -91,7 +92,7 @@ internal object WalletConnectDataModule { dispatchers: CoroutineDispatcherProvider, legacyStore: WalletConnectSessionsRepository, getWallets: GetWalletsUseCase, - associateNetworks: AssociateNetworksDelegate, + wcNetworksConverter: WcNetworksConverter, analytics: AnalyticsEventHandler, ): DefaultWcSessionsManager { val scope = CoroutineScope(SupervisorJob() + dispatchers.io) @@ -100,7 +101,7 @@ internal object WalletConnectDataModule { dispatchers = dispatchers, legacyStore = legacyStore, getWallets = getWallets, - associateNetworks = associateNetworks, + wcNetworksConverter = wcNetworksConverter, analytics = analytics, scope = scope, ) @@ -133,11 +134,11 @@ internal object WalletConnectDataModule { @SdkMoshi moshi: Moshi, sessionsManager: WcSessionsManager, factories: WcEthNetwork.Factories, - namespaceConverter: WcEthNetwork.NamespaceConverter, walletManagersFacade: WalletManagersFacade, + wcNetworksConverter: WcNetworksConverter, ): WcEthNetwork = WcEthNetwork( moshi = moshi, - namespaceConverter = namespaceConverter, + networksConverter = wcNetworksConverter, sessionsManager = sessionsManager, factories = factories, walletManagersFacade = walletManagersFacade, @@ -147,7 +148,7 @@ internal object WalletConnectDataModule { @Singleton fun wcSolanaNetwork( @SdkMoshi moshi: Moshi, - namespaceConverter: WcSolanaNetwork.NamespaceConverter, + wcNetworksConverter: WcNetworksConverter, sessionsManager: WcSessionsManager, factories: WcSolanaNetwork.Factories, walletManagersFacade: WalletManagersFacade, @@ -155,7 +156,7 @@ internal object WalletConnectDataModule { moshi = moshi, sessionsManager = sessionsManager, factories = factories, - namespaceConverter = namespaceConverter, + networksConverter = wcNetworksConverter, walletManagersFacade = walletManagersFacade, ) @@ -164,9 +165,27 @@ internal object WalletConnectDataModule { fun caipNamespaceDelegate( namespaceConverters: Set<@JvmSuppressWildcards WcNamespaceConverter>, walletManagersFacade: WalletManagersFacade, + wcNetworksConverter: WcNetworksConverter, ): CaipNamespaceDelegate = CaipNamespaceDelegate( namespaceConverters = namespaceConverters, walletManagersFacade = walletManagersFacade, + wcNetworksConverter = wcNetworksConverter, + ) + + @Provides + @Singleton + fun wcNetworksConverter( + namespaceConverters: Set<@JvmSuppressWildcards WcNamespaceConverter>, + walletManagersFacade: WalletManagersFacade, + currenciesRepository: CurrenciesRepository, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles: TokensFeatureToggles, + ): WcNetworksConverter = WcNetworksConverter( + namespaceConverters = namespaceConverters, + walletManagersFacade = walletManagersFacade, + currenciesRepository = currenciesRepository, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles = tokensFeatureToggles, ) @Provides diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt index 6bcb815d18..66cbe92413 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt @@ -10,6 +10,7 @@ import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter.Companion.fromJson import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext import com.tangem.data.walletconnect.utils.WcNamespaceConverter +import com.tangem.data.walletconnect.utils.WcNetworksConverter import com.tangem.domain.models.network.Network import com.tangem.domain.walletconnect.model.* import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest @@ -23,7 +24,7 @@ internal class WcEthNetwork( private val moshi: Moshi, private val sessionsManager: WcSessionsManager, private val factories: Factories, - private val namespaceConverter: NamespaceConverter, + private val networksConverter: WcNetworksConverter, private val walletManagersFacade: WalletManagersFacade, ) : WcRequestToUseCaseConverter { @@ -37,20 +38,34 @@ internal class WcEthNetwork( override suspend fun toUseCase(request: WcSdkSessionRequest): WcMethodUseCase? { val name = toWcMethodName(request) ?: return null val session = sessionsManager.findSessionByTopic(request.topic) ?: return null - val method: WcEthMethod = name.toMethod(request, session.wallet) ?: return null - val network = namespaceConverter.toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null - val walletManagerAddress = walletManagersFacade.getDefaultAddress(session.wallet.walletId, network).orEmpty() + val wallet = session.wallet + val chainId = request.chainId.orEmpty() + val method: WcEthMethod = name.toMethod(request, wallet) ?: return null + suspend fun anyExistNetwork() = networksConverter.mainOrAnyWalletNetworkForRequest(chainId, wallet) + val accountAddress = when (method) { is WcEthMethod.MessageSign -> method.account is WcEthMethod.SendTransaction -> method.transaction.from is WcEthMethod.SignTransaction -> method.transaction.from is WcEthMethod.SignTypedData -> method.account - is WcEthMethod.AddEthereumChain -> walletManagerAddress + is WcEthMethod.AddEthereumChain -> + anyExistNetwork() + ?.let { network -> walletManagersFacade.getDefaultAddress(wallet.walletId, network).orEmpty() } + .orEmpty() } + val walletNetwork = when (method) { + is WcEthMethod.SignTypedData, + is WcEthMethod.MessageSign, + is WcEthMethod.SendTransaction, + is WcEthMethod.SignTransaction, + -> networksConverter.findWalletNetworkForRequest(request, session, accountAddress) + is WcEthMethod.AddEthereumChain -> anyExistNetwork() + } ?: return null + val context = WcMethodUseCaseContext( session = session, rawSdkRequest = request, - network = network, + network = walletNetwork, accountAddress = accountAddress, ) return when (method) { @@ -62,7 +77,7 @@ internal class WcEthNetwork( } } - private fun WcEthMethodName.toMethod(request: WcSdkSessionRequest, wallet: UserWallet): WcEthMethod? { + private suspend fun WcEthMethodName.toMethod(request: WcSdkSessionRequest, wallet: UserWallet): WcEthMethod? { val rawParams = request.request.params return when (this) { WcEthMethodName.EthSign, @@ -85,8 +100,8 @@ internal class WcEthNetwork( WcEthMethodName.AddEthereumChain -> moshi.fromJson>(rawParams) ?.firstOrNull() ?.let { - val newNetwork = namespaceConverter - .toNetwork(it.chainId, wallet) ?: return null + val newNetwork = networksConverter.mainOrAnyWalletNetworkForRequest(it.chainId, wallet) + ?: return null WcEthMethod.AddEthereumChain(rawChain = it, network = newNetwork) } } diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/Model.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/Model.kt index fe252ca79c..3f739b3f64 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/Model.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/Model.kt @@ -16,4 +16,7 @@ internal data class WcSolanaSignMessageRequest( internal data class WcSolanaSignTransactionRequest( @Json(name = "transaction") val transaction: String, + + @Json(name = "feePayer") + val feePayer: String?, ) \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt index 38dc6265be..f994aba0f3 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt @@ -6,14 +6,14 @@ import com.tangem.blockchain.extensions.decodeBase58 import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.common.extensions.toHexString -import com.tangem.data.walletconnect.model.CAIP2 import com.tangem.data.walletconnect.model.NamespaceKey import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter.Companion.fromJson import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext import com.tangem.data.walletconnect.utils.WcNamespaceConverter +import com.tangem.data.walletconnect.utils.WcNetworksConverter import com.tangem.domain.models.network.Network -import com.tangem.domain.walletconnect.model.WcSession +import com.tangem.data.walletconnect.model.CAIP2 import com.tangem.domain.walletconnect.model.WcSolanaMethod import com.tangem.domain.walletconnect.model.WcSolanaMethodName import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest @@ -21,13 +21,12 @@ import com.tangem.domain.walletconnect.repository.WcSessionsManager import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import jakarta.inject.Inject -import timber.log.Timber internal class WcSolanaNetwork( private val moshi: Moshi, private val sessionsManager: WcSessionsManager, private val factories: Factories, - private val namespaceConverter: NamespaceConverter, + private val networksConverter: WcNetworksConverter, private val walletManagersFacade: WalletManagersFacade, ) : WcRequestToUseCaseConverter { @@ -37,17 +36,33 @@ internal class WcSolanaNetwork( return name } + @Suppress("CyclomaticComplexMethod") override suspend fun toUseCase(request: WcSdkSessionRequest): WcMethodUseCase? { val name = toWcMethodName(request) ?: return null val method: WcSolanaMethod = name.toMethod(request) ?: return null val session = sessionsManager.findSessionByTopic(request.topic) ?: return null - val network = namespaceConverter.toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null - val accountAddress = getAccountAddress(session, network) + val wallet = session.wallet + val chainId = request.chainId.orEmpty() + suspend fun anyExistNetwork() = networksConverter.mainOrAnyWalletNetworkForRequest(chainId, wallet) + suspend fun anyAddress() = anyExistNetwork() + ?.let { network -> walletManagersFacade.getDefaultAddress(wallet.walletId, network).orEmpty() } + .orEmpty() + + val accountAddress = when (method) { + is WcSolanaMethod.SignAllTransaction -> anyAddress() + is WcSolanaMethod.SignMessage -> anyAddress() + is WcSolanaMethod.SignTransaction -> method.address ?: anyAddress() + } + val walletNetwork = networksConverter + .findWalletNetworkForRequest(request, session, accountAddress) + ?: anyExistNetwork() + ?: return null + val context = WcMethodUseCaseContext( session = session, rawSdkRequest = request, - network = network, - accountAddress = accountAddress.orEmpty(), + network = walletNetwork, + accountAddress = accountAddress, ) return when (method) { is WcSolanaMethod.SignMessage -> factories.messageSign.create(context, method) @@ -56,15 +71,6 @@ internal class WcSolanaNetwork( } } - private suspend fun getAccountAddress(session: WcSession, network: Network): String? { - return try { - walletManagersFacade.getDefaultAddress(session.wallet.walletId, network) - } catch (exception: Exception) { - Timber.e(exception) - null - } - } - internal class NamespaceConverter @Inject constructor( override val excludedBlockchains: ExcludedBlockchains, ) : WcNamespaceConverter { @@ -107,7 +113,7 @@ internal class WcSolanaNetwork( ) } WcSolanaMethodName.SignTransaction -> moshi.fromJson(rawParams) - ?.let { request -> WcSolanaMethod.SignTransaction(request.transaction) } + ?.let { request -> WcSolanaMethod.SignTransaction(request.transaction, request.feePayer) } WcSolanaMethodName.SendAllTransaction -> moshi.fromJson>(rawParams)?.let { list -> WcSolanaMethod.SignAllTransaction(list) } diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/AssociateNetworksDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/AssociateNetworksDelegate.kt index a203ce26cb..cdf7ca5bc0 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/AssociateNetworksDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/AssociateNetworksDelegate.kt @@ -2,7 +2,6 @@ package com.tangem.data.walletconnect.pair import com.reown.walletkit.client.Wallet import com.reown.walletkit.client.Wallet.Model.Namespace -import com.tangem.data.common.currency.isCustomCoin import com.tangem.data.walletconnect.utils.WcNamespaceConverter import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network @@ -25,16 +24,6 @@ internal class AssociateNetworksDelegate( private val tokensFeatureToggles: TokensFeatureToggles, ) { - suspend fun associate(wallet: UserWallet, namespaces: Map): Set { - val walletNetworks = getWalletNetworks(userWalletId = wallet.walletId) - val namespacesSet = namespaces.values.flatMap { proposal -> proposal.chains ?: listOf() }.toSet() - return namespacesSet.mapNotNullTo(mutableSetOf()) { chainId -> - val wcNetwork = namespaceConverters - .firstNotNullOfOrNull { it.toNetwork(chainId, wallet) } ?: return@mapNotNullTo null - walletNetworks.find { network -> wcNetwork.id == network.id } - } - } - @Throws(WcPairError.UnsupportedBlockchains::class) suspend fun associate(sessionProposal: Wallet.Model.SessionProposal): Map { val userWallets = getWallets.invokeSync().filter { it.isMultiCurrency } @@ -70,9 +59,9 @@ internal class AssociateNetworksDelegate( unknownRequired.add(missingNetworkName(chainId)) return@forEach } - val walletNetwork = walletNetworks.find { network -> wcNetwork.id == network.id } + val walletNetwork = walletNetworks.find { network -> wcNetwork.rawId == network.rawId } - if (walletNetwork == null || isCustomCoin(walletNetwork)) { + if (walletNetwork == null) { missingRequired.add(wcNetwork) } else { required.add(walletNetwork) @@ -84,8 +73,8 @@ internal class AssociateNetworksDelegate( unknownOptional.add(missingNetworkName(chainId)) return@forEach } - val walletNetwork = walletNetworks.find { network -> wcNetwork.id == network.id } - if (walletNetwork != null && !isCustomCoin(walletNetwork)) { + val walletNetwork = walletNetworks.find { network -> wcNetwork.rawId == network.rawId } + if (walletNetwork != null) { available.add(walletNetwork) } else { notAdded.add(wcNetwork) @@ -117,6 +106,8 @@ internal class AssociateNetworksDelegate( } .filterIsInstance() .map(CryptoCurrency.Coin::network) + // flatten all derivation + .distinctBy { it.rawId } } private fun Map.setOfChainId(): Set = diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt index 2cf3fb99c1..2aba95bd34 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt @@ -1,48 +1,47 @@ package com.tangem.data.walletconnect.pair import com.reown.walletkit.client.Wallet -import com.tangem.data.walletconnect.model.CAIP10 import com.tangem.data.walletconnect.utils.WcNamespaceConverter +import com.tangem.data.walletconnect.utils.WcNetworksConverter import com.tangem.domain.models.network.Network +import com.tangem.data.walletconnect.model.CAIP10 +import com.tangem.domain.walletconnect.model.WcSessionApprove import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId internal class CaipNamespaceDelegate( private val namespaceConverters: Set, private val walletManagersFacade: WalletManagersFacade, + private val wcNetworksConverter: WcNetworksConverter, ) { suspend fun associate( sessionProposal: Wallet.Model.SessionProposal, - userWallet: UserWallet, - networks: List, + sessionForApprove: WcSessionApprove, ): Map { + val userWallet = sessionForApprove.wallet val result = mutableMapOf() - networks.map { network -> - val address = walletManagersFacade.getDefaultAddress(userWallet.walletId, network) - val chainId = namespaceConverters.firstNotNullOfOrNull { it.toCAIP2(network) } - requireNotNull(chainId) - requireNotNull(address) - CAIP10(chainId = chainId, accountAddress = address) - }.forEach { account -> - val namespaceKey = account.chainId.namespace - val session = result.getOrPut(namespaceKey) { Session() } - val requiredNamespaces = sessionProposal.requiredNamespaces - val optionalNamespaces = sessionProposal.optionalNamespaces - val methods = buildSet { - requiredNamespaces[namespaceKey]?.methods?.let { addAll(it) } - optionalNamespaces[namespaceKey]?.methods?.let { addAll(it) } + wcNetworksConverter.convertNetworksForApprove(sessionForApprove) + .mapNotNull { createCAIP10(userWallet.walletId, it) } + .forEach { account -> + val namespaceKey = account.chainId.namespace + val session = result.getOrPut(namespaceKey) { Session() } + val requiredNamespaces = sessionProposal.requiredNamespaces + val optionalNamespaces = sessionProposal.optionalNamespaces + val methods = buildSet { + requiredNamespaces[namespaceKey]?.methods?.let { addAll(it) } + optionalNamespaces[namespaceKey]?.methods?.let { addAll(it) } + } + val events = buildSet { + requiredNamespaces[namespaceKey]?.events?.let { addAll(it) } + optionalNamespaces[namespaceKey]?.events?.let { addAll(it) } + } + session.chains.add(account.chainId.raw) + session.accounts.add(account.raw) + session.methods.addAll(methods) + session.events.addAll(events) } - val events = buildSet { - requiredNamespaces[namespaceKey]?.events?.let { addAll(it) } - optionalNamespaces[namespaceKey]?.events?.let { addAll(it) } - } - session.chains.add(account.chainId.raw) - session.accounts.add(account.raw) - session.methods.addAll(methods) - session.events.addAll(events) - } return result.mapValues { (_, session) -> Wallet.Model.Namespace.Session( chains = session.chains.toList(), @@ -53,6 +52,13 @@ internal class CaipNamespaceDelegate( } } + private suspend fun createCAIP10(userWalletId: UserWalletId, network: Network): CAIP10? { + val address = walletManagersFacade.getDefaultAddress(userWalletId, network) + val chainId = namespaceConverters.firstNotNullOfOrNull { it.toCAIP2(network) } + if (chainId == null || address == null) return null + return CAIP10(chainId = chainId, accountAddress = address) + } + private data class Session( val chains: MutableSet = mutableSetOf(), val accounts: MutableSet = mutableSetOf(), diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt index dd81c7820d..e02b9f623c 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt @@ -136,8 +136,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor( ): Either { val namespaces = caipNamespaceDelegate.associate( sdkSessionProposal, - sessionForApprove.wallet, - sessionForApprove.network, + sessionForApprove, ) val sessionApprove = Wallet.Params.SessionApprove( proposerPublicKey = sdkSessionProposal.proposerPublicKey, diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt index e9b611c63b..28afaab46a 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt @@ -7,10 +7,7 @@ import com.domain.blockaid.models.dapp.CheckDAppResult import com.reown.walletkit.client.Wallet import com.reown.walletkit.client.WalletKit import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.data.walletconnect.pair.AssociateNetworksDelegate -import com.tangem.data.walletconnect.utils.WC_TAG -import com.tangem.data.walletconnect.utils.WcSdkObserver -import com.tangem.data.walletconnect.utils.WcSdkSessionConverter +import com.tangem.data.walletconnect.utils.* import com.tangem.datasource.local.walletconnect.WalletConnectStore import com.tangem.domain.walletconnect.WcAnalyticEvents import com.tangem.domain.walletconnect.model.WcSession @@ -33,7 +30,7 @@ internal class DefaultWcSessionsManager( private val legacyStore: WalletConnectSessionsRepository, private val getWallets: GetWalletsUseCase, private val dispatchers: CoroutineDispatcherProvider, - private val associateNetworks: AssociateNetworksDelegate, + private val wcNetworksConverter: WcNetworksConverter, private val analytics: AnalyticsEventHandler, private val scope: CoroutineScope, ) : WcSessionsManager, WcSdkObserver { @@ -85,20 +82,9 @@ internal class DefaultWcSessionsManager( } override suspend fun findSessionByTopic(topic: String): WcSession? = withContext(dispatchers.io) { - val storedSession = sessions.firstOrNull() + sessions.firstOrNull() ?.values?.flatten() ?.firstOrNull { it.sdkModel.topic == topic } - ?: return@withContext null - val sdkSession = WalletKit.getActiveSessionByTopic(topic) ?: return@withContext null - val wallet = storedSession.wallet - val networks = associateNetworks.associate(wallet, sdkSession.namespaces) - WcSession( - wallet = wallet, - sdkModel = WcSdkSessionConverter.convert(sdkSession), - securityStatus = storedSession.securityStatus, - networks = networks, - connectingTime = storedSession.connectingTime, - ) } override fun onSessionDelete(sessionDelete: Wallet.Model.SessionDelete) { @@ -137,16 +123,16 @@ internal class DefaultWcSessionsManager( inStore: Set, wallets: List, ): List { - val wcSessions = inStore.mapNotNull { session -> - val wallet = wallets.find { it.walletId == session.walletId } ?: return@mapNotNull null - val sdkSession = inSdk.find { it.topic == session.topic } ?: return@mapNotNull null - val networks = associateNetworks.associate(wallet, sdkSession.namespaces) + val wcSessions = inStore.mapNotNull { storeSession -> + val wallet = wallets.find { it.walletId == storeSession.walletId } ?: return@mapNotNull null + val sdkSession = inSdk.find { it.topic == storeSession.topic } ?: return@mapNotNull null + val networks = wcNetworksConverter.findWalletNetworks(wallet, sdkSession) WcSession( wallet = wallet, sdkModel = WcSdkSessionConverter.convert(sdkSession), - securityStatus = session.securityStatus, + securityStatus = storeSession.securityStatus, networks = networks, - connectingTime = session.connectingTime, + connectingTime = storeSession.connectingTime, ) } return wcSessions diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNamespaceConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNamespaceConverter.kt index 77e4239b0d..86d099131c 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNamespaceConverter.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNamespaceConverter.kt @@ -3,9 +3,9 @@ package com.tangem.data.walletconnect.utils import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.data.common.network.NetworkFactory -import com.tangem.data.walletconnect.model.CAIP2 import com.tangem.data.walletconnect.model.NamespaceKey import com.tangem.domain.models.network.Network +import com.tangem.data.walletconnect.model.CAIP2 import com.tangem.domain.wallets.models.UserWallet internal interface WcNamespaceConverter { diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt new file mode 100644 index 0000000000..6455ccb1e6 --- /dev/null +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt @@ -0,0 +1,102 @@ +package com.tangem.data.walletconnect.utils + +import com.reown.walletkit.client.Wallet +import com.tangem.data.common.currency.isCustomCoin +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.network.Network +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier +import com.tangem.domain.tokens.TokensFeatureToggles +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.data.walletconnect.model.CAIP10 +import com.tangem.domain.walletconnect.model.WcSession +import com.tangem.domain.walletconnect.model.WcSessionApprove +import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import javax.inject.Inject + +internal class WcNetworksConverter @Inject constructor( + private val namespaceConverters: Set, + private val walletManagersFacade: WalletManagersFacade, + private val currenciesRepository: CurrenciesRepository, + private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + private val tokensFeatureToggles: TokensFeatureToggles, +) { + + suspend fun findWalletNetworkForRequest( + request: WcSdkSessionRequest, + session: WcSession, + requestAddress: String, + ): Network? { + val wallet = session.wallet + val allCoinNetwork = filterWalletNetworkForRequest(request.chainId.orEmpty(), session.wallet) + + val requestNetwork = allCoinNetwork.find { network -> + val address = walletManagersFacade.getDefaultAddress(wallet.walletId, network) + requestAddress == address + } + return requestNetwork + } + + /** + * return network with not custom derivationPath or first custom or any + */ + suspend fun mainOrAnyWalletNetworkForRequest(rawChainId: String, wallet: UserWallet): Network? { + val networks = filterWalletNetworkForRequest(rawChainId, wallet) + return networks.firstOrNull { !isCustomCoin(it) } ?: networks.firstOrNull() + } + + /** + * return all exist derivation networks + */ + suspend fun filterWalletNetworkForRequest(rawChainId: String, wallet: UserWallet): List { + val walletNetworks = getWalletNetworks(wallet.walletId) + + val blockchain = namespaceConverters + .firstNotNullOfOrNull { it.toBlockchain(rawChainId) } ?: return listOf() + + val allCoinNetwork = walletNetworks.filter { it.rawId == blockchain.id } + return allCoinNetwork + } + + suspend fun findWalletNetworks(wallet: UserWallet, sdkSession: Wallet.Model.Session): Set { + val walletNetworks = getWalletNetworks(wallet.walletId) + val existNetworks = sdkSession.namespaces.values + .map { it.accounts }.flatten().toSet() + .mapNotNull { CAIP10.fromRaw(it) } + .mapNotNullTo(mutableSetOf()) { caip10 -> + val blockchain = namespaceConverters + .firstNotNullOfOrNull { it.toBlockchain(caip10.chainId) } + ?: return@mapNotNullTo null + walletNetworks + // find all derivation + .filter { it.rawId == blockchain.id } + // find equal address + .firstOrNull { + val walletAddress = walletManagersFacade.getDefaultAddress(wallet.walletId, it) + walletAddress == caip10.accountAddress + } + } + + return existNetworks + } + + suspend fun convertNetworksForApprove(sessionForApprove: WcSessionApprove): List { + val walletNetworks = getWalletNetworks(sessionForApprove.wallet.walletId) + return sessionForApprove.network + .map { network -> walletNetworks.filter { walletNetwork -> walletNetwork.rawId == network.rawId } } + .flatten() + } + + private suspend fun getWalletNetworks(userWalletId: UserWalletId): List { + return if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) { + multiWalletCryptoCurrenciesSupplier.getSyncOrNull( + params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId = userWalletId), + ).orEmpty() + } else { + currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId) + }.filterIsInstance().map(CryptoCurrency.Coin::network) + } +} \ No newline at end of file diff --git a/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt b/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt index caf657f919..286a997186 100644 --- a/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt +++ b/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt @@ -114,8 +114,7 @@ internal class DefaultWcPairUseCaseTest { coEvery { caipNamespaceDelegate.associate( sessionProposal = sdkProposal, - userWallet = sessionForApprove.wallet, - networks = sessionForApprove.network, + sessionForApprove = sessionForApprove, ) } returns mapOf() } diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSolanaMethod.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSolanaMethod.kt index d599bea8d7..0042025dfb 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSolanaMethod.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSolanaMethod.kt @@ -10,6 +10,7 @@ sealed interface WcSolanaMethod : WcMethod { data class SignTransaction( val transaction: String, + val address: String?, ) : WcSolanaMethod data class SignAllTransaction( diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectedAppInfoModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectedAppInfoModel.kt index 9e5b0ee9ce..b35c69510f 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectedAppInfoModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectedAppInfoModel.kt @@ -71,6 +71,7 @@ internal class WcConnectedAppInfoModel @Inject constructor( walletName = session.wallet.name, connectingTime = session.connectingTime, networks = session.networks + .distinctBy { network -> network.rawId } .map { WcNetworkInfoItem.Required( id = it.rawId,