From 2fb0ef193672f7341edae7548df7de3d59589649 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Mar 2025 23:49:10 +0700 Subject: [PATCH] Updated on 2026-08-14 --- .../common/analytics/events/AnalyticsParam.kt | 1 + .../common/analytics/events/WalletConnect.kt | 58 +++++++++++++++++-- .../DefaultLegacyWalletConnectRepository.kt | 51 ++++++++++++---- .../di/WalletConnectInteractorModule.kt | 16 +++-- .../domain/LegacyWalletConnectRepository.kt | 3 +- .../domain/WalletConnectInteractor.kt | 7 ++- .../walletconnect/WalletConnectAction.kt | 5 +- .../walletconnect/WalletConnectMiddleware.kt | 2 +- .../ui/walletconnect/WalletConnectModel.kt | 4 +- .../dialogs/ClipboardOrScanQrDialog.kt | 2 +- 10 files changed, 120 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index 8cfba8dd64..67cd08e295 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -113,6 +113,7 @@ sealed class AnalyticsParam { const val SEED_PHRASE_LENGTH = "Seed Phrase Length" const val DAPP_NAME = "DApp Name" const val DAPP_URL = "DApp Url" + const val NETWORKS = "Networks" const val METHOD_NAME = "Method Name" const val VALIDATION = "Validation" const val BLOCKCHAIN_EXCEPTION_HOST = "exception_host" diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt index d2870bbb6a..1725eabaaa 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt @@ -1,6 +1,7 @@ package com.tangem.tap.common.analytics.events import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType /** [REDACTED_AUTHOR] @@ -12,8 +13,41 @@ internal sealed class WalletConnect( ) : AnalyticsEvent("Wallet Connect", event, params, error) { class ScreenOpened : WalletConnect(event = "WC Screen Opened") - class NewSessionEstablished(dAppName: String, dAppUrl: String, blockchainNames: List) : WalletConnect( - event = "New Session Established", + class NewSessionInitiated(source: SourceType) : WalletConnect( + event = "Session Initiated", + params = mapOf( + AnalyticsParam.SOURCE to when (source) { + SourceType.QR -> "QR" + SourceType.DEEPLINK -> "DeepLink" + SourceType.ETC -> "etc" + }, + ), + ) + + data object SessionFailed : WalletConnect( + event = "Session Failed", + ) + + class DAppConnectionRequested( + blockchainNames: List, + ) : WalletConnect( + event = "dApp Connection Requested", + params = mapOf( + AnalyticsParam.NETWORKS to blockchainNames.joinToString(","), + ), + ) + + class DAppConnected(dAppName: String, dAppUrl: String, blockchainNames: List) : WalletConnect( + event = "dApp Connected", + params = mapOf( + AnalyticsParam.DAPP_NAME to dAppName, + AnalyticsParam.DAPP_URL to dAppUrl, + AnalyticsParam.BLOCKCHAIN to blockchainNames.joinToString(","), + ), + ) + + class DAppConnectionFailed(dAppName: String, dAppUrl: String, blockchainNames: List) : WalletConnect( + event = "dApp Connection Failed", params = mapOf( AnalyticsParam.DAPP_NAME to dAppName, AnalyticsParam.DAPP_URL to dAppUrl, @@ -22,17 +56,31 @@ internal sealed class WalletConnect( ) class SessionDisconnected(dAppName: String, dAppUrl: String) : WalletConnect( - event = "Session Disconnected", + event = "dApp Disconnected", params = mapOf( AnalyticsParam.DAPP_NAME to dAppName, AnalyticsParam.DAPP_URL to dAppUrl, ), ) - class RequestHandled( + class SignatureRequestHandled( params: RequestHandledParams, ) : WalletConnect( - event = "Request Handled", + event = "Signature Request Handled", + params = params.toParamsMap(), + ) + + class SignatureRequestReceived( + params: RequestHandledParams, + ) : WalletConnect( + event = "Signature Request Received", + params = params.toParamsMap(), + ) + + class SignatureRequestFailed( + params: RequestHandledParams, + ) : WalletConnect( + event = "Signature Request Failed", params = params.toParamsMap(), ) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt index d49c3386d7..7517f4d57d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt @@ -9,11 +9,9 @@ import com.reown.walletkit.client.Wallet import com.reown.walletkit.client.WalletKit import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.tap.common.analytics.events.WalletConnect -import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository -import com.tangem.tap.domain.walletconnect2.domain.WcJrpcMethods -import com.tangem.tap.domain.walletconnect2.domain.WcJrpcRequestsDeserializer -import com.tangem.tap.domain.walletconnect2.domain.WcRequest +import com.tangem.tap.domain.walletconnect2.domain.* import com.tangem.tap.domain.walletconnect2.domain.models.* +import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType import kotlinx.coroutines.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -24,6 +22,7 @@ internal class DefaultLegacyWalletConnectRepository( private val application: Application, private val wcRequestDeserializer: WcJrpcRequestsDeserializer, private val analyticsHandler: AnalyticsEventHandler, + private val blockchainHelper: WcBlockchainHelper, ) : LegacyWalletConnectRepository { private var sessionProposal: Wallet.Model.SessionProposal? = null @@ -136,6 +135,13 @@ internal class DefaultLegacyWalletConnectRepository( userNamespaces = this@DefaultLegacyWalletConnectRepository.userNamespaces ?: emptyMap(), ) + val requiredChainIds = sessionProposal.requiredNamespaces.values.flatMap { it.chains ?: emptyList() } + val optionalChainIds = optionalWithoutMissingNetworks.toList() + val networks = (requiredChainIds + optionalChainIds) + .mapNotNull { blockchainHelper.chainIdToFullNameOrNull(it) } + .distinct() + analyticsHandler.send(WalletConnect.DAppConnectionRequested(networks)) + scope.launch { _events.emit( WalletConnectEvents.SessionProposal( @@ -143,8 +149,8 @@ internal class DefaultLegacyWalletConnectRepository( sessionProposal.description, sessionProposal.url, sessionProposal.icons, - sessionProposal.requiredNamespaces.values.flatMap { it.chains ?: emptyList() }, - optionalWithoutMissingNetworks.toList(), + requiredChainIds, + optionalChainIds, ), ) } @@ -176,7 +182,17 @@ internal class DefaultLegacyWalletConnectRepository( result = "", ) } - else -> + else -> { + val event = WalletConnect.SignatureRequestReceived( + WalletConnect.RequestHandledParams( + dAppName = sessionRequest.peerMetaData?.name ?: "", + dAppUrl = sessionRequest.peerMetaData?.url ?: "", + methodName = sessionRequest.request.method, + blockchain = sessionRequest.chainId + ?.let { blockchainHelper.chainIdToNetworkIdOrNull(it) } ?: "", + ), + ) + analyticsHandler.send(event) scope.launch { _events.emit( WalletConnectEvents.SessionRequest( @@ -190,6 +206,7 @@ internal class DefaultLegacyWalletConnectRepository( ), ) } + } } } @@ -247,7 +264,8 @@ internal class DefaultLegacyWalletConnectRepository( this.userNamespaces = userNamespaces } - override fun pair(uri: String) { + override fun pair(uri: String, source: SourceType) { + analyticsHandler.send(WalletConnect.NewSessionInitiated(source = source)) WalletKit.pair( params = Wallet.Params.Pair(uri), onSuccess = { @@ -255,6 +273,7 @@ internal class DefaultLegacyWalletConnectRepository( }, onError = { Timber.e("Error while pairing: $it") + analyticsHandler.send(WalletConnect.SessionFailed) scope.launch { _events.emit( WalletConnectEvents.PairConnectError(it.throwable), @@ -305,7 +324,7 @@ internal class DefaultLegacyWalletConnectRepository( onSuccess = { Timber.i("Approved successfully: $it") analyticsHandler.send( - WalletConnect.NewSessionEstablished( + WalletConnect.DAppConnected( dAppName = sessionProposal.name, dAppUrl = sessionProposal.url, blockchainNames = blockchainNames, @@ -314,6 +333,13 @@ internal class DefaultLegacyWalletConnectRepository( }, onError = { Timber.e("Error while approving: $it") + analyticsHandler.send( + WalletConnect.DAppConnectionFailed( + dAppName = sessionProposal.name, + dAppUrl = sessionProposal.url, + blockchainNames = blockchainNames, + ), + ) scope.launch { _events.emit( WalletConnectEvents.SessionApprovalError( @@ -352,7 +378,7 @@ internal class DefaultLegacyWalletConnectRepository( // Add Ethereum Chain method is processed without user input, skip logging it if (requestData.method != WcJrpcMethods.WALLET_ADD_ETHEREUM_CHAIN.code) { analyticsHandler.send( - WalletConnect.RequestHandled( + WalletConnect.SignatureRequestHandled( WalletConnect.RequestHandledParams( dAppName = session?.name ?: "", dAppUrl = session?.url ?: "", @@ -377,7 +403,7 @@ internal class DefaultLegacyWalletConnectRepository( onError = { error -> Timber.e(error.throwable, "Error while responging session request") - WalletConnect.RequestHandledParams( + val params = WalletConnect.RequestHandledParams( dAppName = session?.name ?: "", dAppUrl = session?.url ?: "", methodName = requestData.method, @@ -385,6 +411,7 @@ internal class DefaultLegacyWalletConnectRepository( errorCode = WalletConnectError.ValidationError.error, errorDescription = error.throwable.message, ) + analyticsHandler.send(WalletConnect.SignatureRequestFailed(params)) }, ) } @@ -393,7 +420,7 @@ internal class DefaultLegacyWalletConnectRepository( val session = currentSessions.find { it.topic == requestData.topic } analyticsHandler.send( - WalletConnect.RequestHandled( + WalletConnect.SignatureRequestHandled( WalletConnect.RequestHandledParams( dAppName = session?.name ?: "", dAppUrl = session?.url ?: "", diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt index 9b7a44b10d..754a727e54 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt @@ -14,9 +14,7 @@ import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper import com.tangem.tap.domain.walletconnect2.app.WalletConnectEventsHandlerImpl import com.tangem.tap.domain.walletconnect2.data.DefaultLegacyWalletConnectRepository import com.tangem.tap.domain.walletconnect2.data.DefaultWalletConnectSessionsRepository -import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository -import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor -import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository +import com.tangem.tap.domain.walletconnect2.domain.* import com.tangem.tap.domain.walletconnect2.domain.WcJrpcRequestsDeserializer import com.tangem.tap.domain.walletconnect2.toggles.WalletConnectFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -35,7 +33,7 @@ internal object WalletConnectInteractorModule { fun provideWalletConnectInteractor( wcRepository: LegacyWalletConnectRepository, wcSessionsRepository: WalletConnectSessionsRepository, - walletConnectFeatureToggles: WalletConnectFeatureToggles, + wcBlockchainHelper: WcBlockchainHelper, currenciesRepository: CurrenciesRepository, walletManagersFacade: WalletManagersFacade, userWalletsListManager: UserWalletsListManager, @@ -46,7 +44,7 @@ internal object WalletConnectInteractorModule { walletConnectRepository = wcRepository, sessionsRepository = wcSessionsRepository, sdkHelper = WalletConnectSdkHelper(), - blockchainHelper = TangemWcBlockchainHelper(walletConnectFeatureToggles), + blockchainHelper = wcBlockchainHelper, currenciesRepository = currenciesRepository, walletManagersFacade = walletManagersFacade, userWalletsListManager = userWalletsListManager, @@ -65,17 +63,25 @@ internal object WalletConnectModule { return WalletConnectFeatureToggles(featureTogglesManager) } + @Provides + @Singleton + fun provideWcBlockchainHelper(walletConnectFeatureToggles: WalletConnectFeatureToggles): WcBlockchainHelper { + return TangemWcBlockchainHelper(walletConnectFeatureToggles) + } + @Provides @Singleton fun provideWalletConnectRepository( application: Application, wcRequestDeserializer: WcJrpcRequestsDeserializer, analyticsHandler: AnalyticsEventHandler, + wcBlockchainHelper: WcBlockchainHelper, ): LegacyWalletConnectRepository { return DefaultLegacyWalletConnectRepository( application = application, wcRequestDeserializer = wcRequestDeserializer, analyticsHandler = analyticsHandler, + blockchainHelper = wcBlockchainHelper, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt index d755da5aea..289a2664b5 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt @@ -1,6 +1,7 @@ package com.tangem.tap.domain.walletconnect2.domain import com.tangem.tap.domain.walletconnect2.domain.models.* +import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType import kotlinx.coroutines.flow.Flow interface LegacyWalletConnectRepository { @@ -17,7 +18,7 @@ interface LegacyWalletConnectRepository { fun updateSessions() - fun pair(uri: String) + fun pair(uri: String, source: SourceType) fun disconnect(topic: String) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index 48f90f3554..8c3b553cfe 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -135,7 +135,9 @@ class WalletConnectInteractor( isWalletConnectReadyForDeepLinks = true if (deeplinkStack.empty()) return val lastDeeplink = deeplinkStack.pop() - store.dispatchOnMain(WalletConnectAction.OpenSession(lastDeeplink)) + val action = WalletConnectAction + .OpenSession(lastDeeplink, WalletConnectAction.OpenSession.SourceType.DEEPLINK) + store.dispatchOnMain(action) }.onFailure { Timber.e("WC deeplink handling failed. $it") } @@ -392,7 +394,8 @@ class WalletConnectInteractor( } if (isWalletConnectReadyForDeepLinks) { - store.dispatchOnMain(WalletConnectAction.OpenSession(deeplink)) + val action = WalletConnectAction.OpenSession(deeplink, WalletConnectAction.OpenSession.SourceType.DEEPLINK) + store.dispatchOnMain(action) } else { deeplinkStack.push(deeplink) } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt index 476bcd288d..3383203b41 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt @@ -15,7 +15,10 @@ sealed class WalletConnectAction : Action { data class OpenSession( val wcUri: String, - ) : WalletConnectAction() + val source: SourceType, + ) : WalletConnectAction() { + enum class SourceType { QR, DEEPLINK, ETC } + } data class DisconnectSession(val topic: String) : WalletConnectAction() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 4e7c5c7be2..8535bc008f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -76,7 +76,7 @@ class WalletConnectMiddleware { is WalletConnectAction.OpenSession -> { val index = action.wcUri.indexOf("@") when (action.wcUri[index + 1]) { - '2' -> walletConnectRepository.pair(uri = action.wcUri) + '2' -> walletConnectRepository.pair(uri = action.wcUri, source = action.source) '1' -> { store.dispatchOnMain(WalletConnectAction.UnsupportedDappRequest) store.dispatchOnMain( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt index 39f27fb752..b940ddcc29 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt @@ -13,6 +13,7 @@ import com.tangem.tap.store import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -29,7 +30,8 @@ internal class WalletConnectModel @Inject constructor( modelScope.launch { listenToQrScanningUseCase(SourceType.WALLET_CONNECT) .getOrElse { emptyFlow() } - .collect { store.dispatch(WalletConnectAction.OpenSession(it)) } + .map { WalletConnectAction.OpenSession(it, WalletConnectAction.OpenSession.SourceType.QR) } + .collect { store.dispatch(it) } } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt index 5411faec75..a6ac03c6c7 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt @@ -17,7 +17,7 @@ object ClipboardOrScanQrDialog { setTitle(context.getString(R.string.common_select_action)) setMessage(context.getText(R.string.wallet_connect_clipboard_alert)) setPositiveButton(context.getText(R.string.wallet_connect_paste_from_clipboard)) { _, _ -> - store.dispatch(WalletConnectAction.OpenSession(wcUri)) + store.dispatch(WalletConnectAction.OpenSession(wcUri, WalletConnectAction.OpenSession.SourceType.ETC)) } setNegativeButton(context.getText(R.string.wallet_connect_scan_new_code)) { _, _ -> store.dispatchNavigationAction {