Updated on 2026-08-14

This commit is contained in:
Tangem 2023-07-27 17:29:54 +04:00
parent 171732a41a
commit 4420365cd1
11 changed files with 146 additions and 46 deletions

View file

@ -157,6 +157,9 @@ sealed class AnalyticsParam {
const val ERROR_KEY = "Error Key"
const val CREATION_TYPE = "Creation type"
const val DAPP_NAME = "DApp Name"
const val DAPP_URL = "DApp Url"
const val METHOD_NAME = "Method Name"
const val VALIDATION = "Validation"
const val BLOCKCHAIN_EXCEPTION_HOST = "exception_host"
const val BLOCKCHAIN_SELECTED_HOST = "selected_host"
}

View file

@ -1,6 +1,7 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.tap.common.extensions.filterNotNull
/**
[REDACTED_AUTHOR]
@ -12,16 +13,51 @@ sealed class WalletConnect(
) : AnalyticsEvent("Wallet Connect", event, params, error) {
class ScreenOpened : WalletConnect(event = "WC Screen Opened")
class NewSessionEstablished(dAppName: String) : WalletConnect(
class NewSessionEstablished(dAppName: String, dAppUrl: String) : WalletConnect(
event = "New Session Established",
params = mapOf(
AnalyticsParam.DAPP_NAME to dAppName,
AnalyticsParam.DAPP_URL to dAppUrl,
),
)
class SessionDisconnected : WalletConnect("Session Disconnected")
class RequestSigned : WalletConnect("Request Signed")
class SessionDisconnected(dAppName: String, dAppUrl: String) : WalletConnect(
event = "Session Disconnected",
params = mapOf(
AnalyticsParam.DAPP_NAME to dAppName,
AnalyticsParam.DAPP_URL to dAppUrl,
),
)
class SignError(error: Throwable) : WalletConnect("Sign", error = error)
class TransactionError(error: Throwable) : WalletConnect("Transaction", error = error)
class RequestHandled(
params: RequestHandledParams,
) : WalletConnect(
event = "Request Handled",
params = params.toParamsMap(),
)
data class RequestHandledParams(
val dAppName: String,
val dAppUrl: String,
val methodName: String,
val blockchain: String,
val errorCode: String? = null,
) {
fun toParamsMap(): Map<String, String> {
val validation = if (errorCode == null) Validation.SUCCESS.param else Validation.FAIL.param
return mapOf(
AnalyticsParam.DAPP_NAME to dAppName,
AnalyticsParam.DAPP_URL to dAppUrl,
AnalyticsParam.METHOD_NAME to methodName,
AnalyticsParam.BLOCKCHAIN to blockchain,
AnalyticsParam.VALIDATION to validation,
if (errorCode != null) AnalyticsParam.ERROR_CODE to errorCode else null to null,
).filterNotNull()
}
}
enum class Validation(val param: String) {
SUCCESS("Success"),
FAIL("Fail"),
}
}

View file

@ -3,11 +3,9 @@ package com.tangem.tap.domain.walletconnect
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.guard
import com.tangem.core.analytics.Analytics
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.analytics.events.WalletConnect
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError
@ -227,7 +225,6 @@ class WalletConnectManager {
}
private fun onSessionClosed(session: WCSession) {
Analytics.send(WalletConnect.SessionDisconnected())
sessions.remove(session.topic)
walletConnectRepository.removeSession(session)
store.dispatchOnMain(WalletConnectAction.RemoveSession(session))
@ -395,7 +392,6 @@ class WalletConnectManager {
),
)
}
Analytics.send(WalletConnect.NewSessionEstablished(""))
}
}
}

View file

@ -12,7 +12,6 @@ import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.blockchain.extensions.hexToBigDecimal
import com.tangem.blockchain.extensions.isAscii
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toDecompressedPublicKey
import com.tangem.common.extensions.toHexString
@ -23,7 +22,6 @@ import com.tangem.operations.sign.SignHashCommand
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.common.analytics.events.Basic.TransactionSent.MemoType
import com.tangem.tap.common.analytics.events.WalletConnect
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.domain.walletconnect.BnbHelper.toWCBinanceTradeOrder
@ -175,7 +173,6 @@ class WalletConnectSdkHelper {
HEX_PREFIX + data.walletManager.wallet.recentTransactions.last().hash
}
is SimpleResult.Failure -> {
(result.error as? TangemSdkError)?.let { Analytics.send(WalletConnect.TransactionError(it)) }
Timber.e(result.error as BlockchainSdkError)
null
}
@ -205,7 +202,6 @@ class WalletConnectSdkHelper {
).toHexString()
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { Analytics.send(WalletConnect.SignError(it)) }
Timber.e(result.error.customMessage)
null
}
@ -244,7 +240,6 @@ class WalletConnectSdkHelper {
)
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { Analytics.send(WalletConnect.TransactionError(it)) }
Timber.e(result.error.customMessage)
null
}
@ -338,7 +333,6 @@ class WalletConnectSdkHelper {
)
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { Analytics.send(WalletConnect.SignError(it)) }
Timber.e(result.error.customMessage)
null
}

View file

@ -2,9 +2,10 @@ package com.tangem.tap.domain.walletconnect2.data
import android.app.Application
import arrow.core.flatten
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.events.WalletConnect
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository
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.models.*
@ -25,6 +26,7 @@ import javax.inject.Inject
class WalletConnectRepositoryImpl @Inject constructor(
private val application: Application,
private val wcRequestDeserializer: WcJrpcRequestsDeserializer,
private val analyticsHandler: AnalyticsEventHandler,
) : WalletConnectRepository {
private var sessionProposal: Wallet.Model.SessionProposal? = null
@ -37,6 +39,8 @@ class WalletConnectRepositoryImpl @Inject constructor(
private val _activeSessions: MutableSharedFlow<List<WalletConnectSession>> = MutableSharedFlow()
override val activeSessions: Flow<List<WalletConnectSession>> = _activeSessions
private var currentSessions: List<WalletConnectSession> = emptyList()
/**
* @param projectId Project ID at https://cloud.walletconnect.com/
*/
@ -127,8 +131,12 @@ class WalletConnectRepositoryImpl @Inject constructor(
// we can send approval automatically, because in WC 2.0 the list of chains is approved when
// initial connection is established
sendRequest(
topic = sessionRequest.topic,
id = sessionRequest.request.id,
RequestData(
topic = sessionRequest.topic,
requestId = sessionRequest.request.id,
blockchain = sessionRequest.chainId.toString(),
method = WcJrpcMethods.WALLET_ADD_ETHEREUM_CHAIN.code,
),
result = "",
)
}
@ -142,6 +150,7 @@ class WalletConnectRepositoryImpl @Inject constructor(
id = sessionRequest.request.id,
metaUrl = sessionRequest.peerMetaData?.url ?: "",
metaName = sessionRequest.peerMetaData?.name ?: "",
method = sessionRequest.request.method,
),
)
}
@ -263,7 +272,12 @@ class WalletConnectRepositoryImpl @Inject constructor(
params = sessionApproval,
onSuccess = {
Timber.d("Approved successfully: $it")
Analytics.send(WalletConnect.NewSessionEstablished(sessionProposal.name))
analyticsHandler.send(
WalletConnect.NewSessionEstablished(
dAppName = sessionProposal.name,
dAppUrl = sessionProposal.url,
),
)
},
onError = {
Timber.d("Error while approving: $it")
@ -278,23 +292,56 @@ class WalletConnectRepositoryImpl @Inject constructor(
)
}
override fun sendRequest(topic: String, id: Long, result: String) {
override fun sendRequest(requestData: RequestData, result: String) {
val session = currentSessions.find { it.topic == requestData.topic }
analyticsHandler.send(
WalletConnect.RequestHandled(
WalletConnect.RequestHandledParams(
dAppName = session?.name ?: "",
dAppUrl = session?.url ?: "",
methodName = requestData.method,
blockchain = requestData.blockchain,
),
),
)
Web3Wallet.respondSessionRequest(
params = Wallet.Params.SessionRequestResponse(
sessionTopic = topic,
sessionTopic = requestData.topic,
jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcResult(
id = id,
id = requestData.requestId,
result = result,
),
),
onSuccess = {},
onError = {
Analytics.send(WalletConnect.TransactionError(it.throwable))
WalletConnect.RequestHandledParams(
dAppName = session?.name ?: "",
dAppUrl = session?.url ?: "",
methodName = requestData.method,
blockchain = requestData.blockchain,
errorCode = WalletConnectError.ValidationError.toString(),
)
},
)
}
override fun rejectRequest(topic: String, id: Long) {
override fun rejectRequest(requestData: RequestData, error: WalletConnectError) {
val session = currentSessions.find { it.topic == requestData.topic }
analyticsHandler.send(
WalletConnect.RequestHandled(
WalletConnect.RequestHandledParams(
dAppName = session?.name ?: "",
dAppUrl = session?.url ?: "",
methodName = requestData.method,
blockchain = requestData.blockchain,
errorCode = error.toString(),
),
),
)
cancelRequest(requestData.topic, requestData.requestId)
}
override fun cancelRequest(topic: String, id: Long) {
Web3Wallet.respondSessionRequest(
params = Wallet.Params.SessionRequestResponse(
sessionTopic = topic,
@ -325,10 +372,16 @@ class WalletConnectRepositoryImpl @Inject constructor(
}
override fun disconnect(topic: String) {
val session = currentSessions.find { it.topic == topic }
Web3Wallet.disconnectSession(
params = Wallet.Params.SessionDisconnect(topic),
onSuccess = {
Analytics.send(WalletConnect.SessionDisconnected())
analyticsHandler.send(
WalletConnect.SessionDisconnected(
dAppName = session?.name ?: "",
dAppUrl = session?.url ?: "",
),
)
updateSessions()
Timber.d("Disconnected successfully: $it")
},
@ -364,6 +417,7 @@ class WalletConnectRepositoryImpl @Inject constructor(
)
}
Timber.d("Available sessions: $availableSessions")
currentSessions = availableSessions
_activeSessions.emit(availableSessions)
}
}

View file

@ -1,7 +1,5 @@
package com.tangem.tap.domain.walletconnect2.domain
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.WalletConnect
import com.tangem.tap.common.extensions.filterNotNull
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
import com.tangem.tap.domain.walletconnect2.domain.models.*
@ -148,8 +146,8 @@ class WalletConnectInteractor(
walletConnectRepository.disconnect(topic)
}
fun rejectRequest(topic: String, id: Long) {
walletConnectRepository.rejectRequest(topic, id)
fun cancelRequest(topic: String, id: Long) {
walletConnectRepository.cancelRequest(topic, id)
}
private suspend fun handleRequest(sessionRequest: WalletConnectEvents.SessionRequest) {
@ -164,16 +162,23 @@ class WalletConnectInteractor(
null
}
}
val networkId = sessionRequest.chainId?.let { blockchainHelper.chainIdToNetworkIdOrNull(it) } ?: ""
val requestData = RequestData(
topic = sessionRequest.topic,
requestId = sessionRequest.id,
blockchain = networkId,
method = sessionRequest.method,
)
if (error != null) {
walletConnectRepository.rejectRequest(sessionRequest.topic, sessionRequest.id)
walletConnectRepository.rejectRequest(requestData, error)
return
}
when (sessionRequest.request) {
is WcRequest.BnbCancel -> Unit
is WcRequest.BnbTxConfirm -> walletConnectRepository.sendRequest(
topic = sessionRequest.topic,
id = sessionRequest.id,
requestData = requestData,
result = "",
)
else -> {
@ -212,16 +217,18 @@ class WalletConnectInteractor(
Timber.d("Signed hash: $signedHash")
val requestData = RequestData(
topic = request.topic,
requestId = request.requestId,
blockchain = networkId,
method = currentRequest.method,
)
if (signedHash == null) {
walletConnectRepository.rejectRequest(
topic = request.topic,
id = request.requestId,
)
walletConnectRepository.rejectRequest(requestData, WalletConnectError.SigningError)
} else {
Analytics.send(WalletConnect.RequestSigned())
walletConnectRepository.sendRequest(
topic = request.topic,
id = request.requestId,
requestData = requestData,
result = signedHash,
)
}

View file

@ -1,9 +1,6 @@
package com.tangem.tap.domain.walletconnect2.domain
import com.tangem.tap.domain.walletconnect2.domain.models.Account
import com.tangem.tap.domain.walletconnect2.domain.models.NetworkNamespace
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectSession
import com.tangem.tap.domain.walletconnect2.domain.models.*
import kotlinx.coroutines.flow.Flow
interface WalletConnectRepository {
@ -24,7 +21,9 @@ interface WalletConnectRepository {
fun reject()
fun sendRequest(topic: String, id: Long, result: String)
fun sendRequest(requestData: RequestData, result: String)
fun rejectRequest(topic: String, id: Long)
fun rejectRequest(requestData: RequestData, error: WalletConnectError)
fun cancelRequest(topic: String, id: Long)
}

View file

@ -0,0 +1,8 @@
package com.tangem.tap.domain.walletconnect2.domain.models
data class RequestData(
val topic: String,
val requestId: Long,
val method: String,
val blockchain: String,
)

View file

@ -7,4 +7,6 @@ sealed class WalletConnectError : Exception() {
data class ExternalApprovalError(override val message: String?) : WalletConnectError()
object WrongUserWallet : WalletConnectError()
object UnsupportedMethod : WalletConnectError()
object SigningError : WalletConnectError()
object ValidationError : WalletConnectError()
}

View file

@ -24,5 +24,6 @@ sealed interface WalletConnectEvents {
val id: Long,
val metaName: String,
val metaUrl: String,
val method: String,
) : WalletConnectEvents
}

View file

@ -182,7 +182,7 @@ class WalletConnectMiddleware {
}
is WalletConnectAction.RejectRequest -> {
walletConnectManager.rejectRequest(action.topic, action.id)
walletConnectInteractor.rejectRequest(action.topic, action.id)
walletConnectInteractor.cancelRequest(action.topic, action.id)
}
is WalletConnectAction.SendTransaction -> {
walletConnectManager.completeTransaction(action.topic)