Updated on 2026-08-14
This commit is contained in:
commit
fc71201d0f
11 changed files with 146 additions and 46 deletions
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
|
@ -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(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -24,5 +24,6 @@ sealed interface WalletConnectEvents {
|
|||
val id: Long,
|
||||
val metaName: String,
|
||||
val metaUrl: String,
|
||||
val method: String,
|
||||
) : WalletConnectEvents
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue