Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-25 16:36:36 +07:00
parent 546a9decb0
commit 150026e2bd
9 changed files with 115 additions and 54 deletions

View file

@ -12,7 +12,9 @@ import com.tangem.data.walletconnect.pair.CaipNamespaceDelegate
import com.tangem.data.walletconnect.pair.DefaultWcPairService
import com.tangem.data.walletconnect.pair.DefaultWcPairUseCase
import com.tangem.data.walletconnect.pair.WcPairSdkDelegate
import com.tangem.data.walletconnect.pair.*
import com.tangem.data.walletconnect.request.DefaultWcRequestService
import com.tangem.data.walletconnect.request.DefaultWcRequestUseCaseFactory
import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter
import com.tangem.data.walletconnect.respond.DefaultWcRespondService
import com.tangem.data.walletconnect.respond.WcRespondService
@ -24,6 +26,8 @@ import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.datasource.local.walletconnect.WalletConnectStore
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletconnect.WcPairService
import com.tangem.domain.walletconnect.WcRequestService
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
import com.tangem.domain.walletconnect.repository.WalletConnectRepository
import com.tangem.domain.walletconnect.repository.WcSessionsManager
@ -107,14 +111,13 @@ internal object WalletConnectDataModule {
@Provides
@Singleton
fun defaultWcRequestService(
dispatchers: CoroutineDispatcherProvider,
diHelperBox: DiHelperBox,
): DefaultWcRequestService {
val scope = CoroutineScope(SupervisorJob() + dispatchers.io)
fun wcRequestService(default: DefaultWcRequestService): WcRequestService = default
@Provides
@Singleton
fun defaultWcRequestService(diHelperBox: DiHelperBox): DefaultWcRequestService {
return DefaultWcRequestService(
requestConverters = diHelperBox.handlers,
scope = scope,
)
}
@ -185,6 +188,12 @@ internal object WalletConnectDataModule {
),
)
@Provides
@Singleton
fun wcRequestUseCaseFactory(diHelperBox: DiHelperBox): WcRequestUseCaseFactory {
return DefaultWcRequestUseCaseFactory(diHelperBox.handlers)
}
internal class DiHelperBox(
val converters: Set<WcNamespaceConverter>,
val handlers: Set<WcRequestToUseCaseConverter>,

View file

@ -11,6 +11,7 @@ import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.walletconnect.model.WcEthMethod
import com.tangem.domain.walletconnect.model.WcEthMethodName
import com.tangem.domain.walletconnect.model.WcEthTransactionParams
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.domain.walletconnect.repository.WcSessionsManager
@ -27,9 +28,14 @@ internal class WcEthNetwork(
override val namespaceKey: NamespaceKey = NamespaceKey("eip155")
override suspend fun toUseCase(request: WcSdkSessionRequest): WcMethodUseCase? {
override fun toWcMethodName(request: WcSdkSessionRequest): WcEthMethodName? {
val methodKey = request.request.method
val name = Name.entries.find { it.raw == methodKey } ?: return null
val name = WcEthMethodName.entries.find { it.raw == methodKey } ?: return null
return name
}
override suspend fun toUseCase(request: WcSdkSessionRequest): WcMethodUseCase? {
val name = toWcMethodName(request) ?: return null
val method: WcEthMethod = name.toMethod(request) ?: return null
val session = sessionsManager.findSessionByTopic(request.topic) ?: return null
val network = toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null
@ -41,26 +47,26 @@ internal class WcEthNetwork(
}
}
private fun Name.toMethod(request: WcSdkSessionRequest): WcEthMethod? {
private fun WcEthMethodName.toMethod(request: WcSdkSessionRequest): WcEthMethod? {
val rawParams = request.request.params
return when (this) {
Name.EthSign,
Name.PersonalSign,
WcEthMethodName.EthSign,
WcEthMethodName.PersonalSign,
-> moshi.fromJson<List<String>>(rawParams)?.let { list ->
val accountIndex = if (this == Name.EthSign) 0 else 1
val messageIndex = if (this == Name.EthSign) 1 else 0
val accountIndex = if (this == WcEthMethodName.EthSign) 0 else 1
val messageIndex = if (this == WcEthMethodName.EthSign) 1 else 0
val account = list.getOrNull(accountIndex) ?: return@let null
val message = list.getOrNull(messageIndex) ?: return@let null
WcEthMethod.MessageSign(account = account, message = message)
}
Name.SignTypeData -> TODO()
Name.SignTypeDataV4 -> TODO()
Name.SignTransaction,
Name.SendTransaction,
WcEthMethodName.SignTypeData -> TODO()
WcEthMethodName.SignTypeDataV4 -> TODO()
WcEthMethodName.SignTransaction,
WcEthMethodName.SendTransaction,
-> moshi.fromJson<List<WcEthTransactionParams>>(rawParams)
?.firstOrNull()
?.let {
if (this == Name.SignTransaction) {
if (this == WcEthMethodName.SignTransaction) {
WcEthMethod.SignTransaction(transaction = it)
} else {
WcEthMethod.SendTransaction(transaction = it)
@ -69,15 +75,6 @@ internal class WcEthNetwork(
}
}
enum class Name(val raw: String) {
EthSign("eth_sign"),
PersonalSign("personal_sign"),
SignTypeData("eth_signTypedData"),
SignTypeDataV4("eth_signTypedData_v4"),
SignTransaction("eth_signTransaction"),
SendTransaction("eth_sendTransaction"),
}
override fun toNetwork(chainId: String, wallet: UserWallet): Network? {
return toNetwork(chainId, wallet, excludedBlockchains)
}

View file

@ -11,6 +11,7 @@ import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.walletconnect.model.WcSolanaMethod
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.WcMethodUseCase
@ -26,9 +27,14 @@ internal class WcSolanaNetwork constructor(
override val namespaceKey: NamespaceKey = NamespaceKey("solana")
override suspend fun toUseCase(request: WcSdkSessionRequest): WcMethodUseCase? {
override fun toWcMethodName(request: WcSdkSessionRequest): WcSolanaMethodName? {
val methodKey = request.request.method
val name = Name.entries.find { it.raw == methodKey } ?: return null
val name = WcSolanaMethodName.entries.find { it.raw == methodKey } ?: return null
return name
}
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 = toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null
@ -67,27 +73,20 @@ internal class WcSolanaNetwork constructor(
)
}
private fun Name.toMethod(request: WcSdkSessionRequest): WcSolanaMethod? {
private fun WcSolanaMethodName.toMethod(request: WcSdkSessionRequest): WcSolanaMethod? {
val rawParams = request.request.params
return when (this) {
Name.SignMessage -> moshi.fromJson<WcSolanaSignMessageRequest>(rawParams)?.let { request ->
WcSolanaMethodName.SignMessage -> moshi.fromJson<WcSolanaSignMessageRequest>(rawParams)?.let { request ->
WcSolanaMethod.SignMessage(pubKey = request.publicKey, message = request.message)
}
Name.SignTransaction -> moshi.fromJson<WcSolanaSignTransactionRequest>(rawParams)?.let { request ->
WcSolanaMethod.SignTransaction(request.transaction)
}
Name.SendAllTransaction -> moshi.fromJson<List<String>>(rawParams)?.let { list ->
WcSolanaMethodName.SignTransaction -> moshi.fromJson<WcSolanaSignTransactionRequest>(rawParams)
?.let { request -> WcSolanaMethod.SignTransaction(request.transaction) }
WcSolanaMethodName.SendAllTransaction -> moshi.fromJson<List<String>>(rawParams)?.let { list ->
WcSolanaMethod.SignAllTransaction(list)
}
}
}
private enum class Name(val raw: String) {
SignMessage("solana_signMessage"),
SignTransaction("solana_signTransaction"),
SendAllTransaction("solana_signAllTransactions"),
}
internal class Factories @Inject constructor(
val signTransaction: DefaultWcSolanaSignTransactionUseCase.Factory,
val signAllTransaction: DefaultWcSolanaSignAllTransactionUseCase.Factory,

View file

@ -3,20 +3,19 @@ package com.tangem.data.walletconnect.request
import com.reown.walletkit.client.Wallet
import com.tangem.data.walletconnect.utils.WcSdkObserver
import com.tangem.data.walletconnect.utils.WcSdkSessionRequestConverter
import com.tangem.domain.walletconnect.usecase.WcMethodUseCase
import com.tangem.domain.walletconnect.usecase.WcUseCasesFlowProvider
import kotlinx.coroutines.CoroutineScope
import com.tangem.domain.walletconnect.WcRequestService
import com.tangem.domain.walletconnect.model.WcMethodName
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
internal class DefaultWcRequestService(
private val requestConverters: Set<WcRequestToUseCaseConverter>,
private val scope: CoroutineScope,
) : WcSdkObserver, WcUseCasesFlowProvider {
) : WcSdkObserver, WcRequestService {
private val _useCases: Channel<WcMethodUseCase> = Channel(Channel.BUFFERED)
override val useCases = _useCases.receiveAsFlow()
private val _wcRequest: Channel<Pair<WcMethodName, WcSdkSessionRequest>> = Channel(Channel.BUFFERED)
override val wcRequest: Flow<Pair<WcMethodName, WcSdkSessionRequest>> = _wcRequest.receiveAsFlow()
override fun onSessionRequest(
sessionRequest: Wallet.Model.SessionRequest,
@ -24,10 +23,8 @@ internal class DefaultWcRequestService(
) {
// Triggered when a Dapp sends SessionRequest to sign a transaction or a message
val sr = WcSdkSessionRequestConverter.convert(sessionRequest)
scope.launch {
val useCase = requestConverters.firstNotNullOfOrNull { it.toUseCase(sr) }
?: return@launch // todo(wc) handle unsupported
_useCases.trySend(useCase)
}
val name = requestConverters.firstNotNullOfOrNull { it.toWcMethodName(sr) }
?: WcMethodName.Unsupported(sr.request.method)
_wcRequest.trySend(name to sr)
}
}

View file

@ -0,0 +1,17 @@
package com.tangem.data.walletconnect.request
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.domain.walletconnect.usecase.WcMethodUseCase
import javax.inject.Inject
internal class DefaultWcRequestUseCaseFactory @Inject constructor(
private val requestConverters: Set<WcRequestToUseCaseConverter>,
) : WcRequestUseCaseFactory {
@Suppress("UNCHECKED_CAST")
override suspend fun <T : WcMethodUseCase> createUseCase(request: WcSdkSessionRequest): T {
return requestConverters.firstNotNullOf { it.toUseCase(request) } as? T
?: error("Should check request before in WcRouting")
}
}

View file

@ -2,10 +2,12 @@ package com.tangem.data.walletconnect.request
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
import com.tangem.domain.walletconnect.model.WcMethodName
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.domain.walletconnect.usecase.WcMethodUseCase
interface WcRequestToUseCaseConverter {
fun toWcMethodName(request: WcSdkSessionRequest): WcMethodName?
suspend fun toUseCase(request: WcSdkSessionRequest): WcMethodUseCase?
companion object {

View file

@ -0,0 +1,22 @@
package com.tangem.domain.walletconnect.model
sealed interface WcMethodName {
val raw: String
data class Unsupported(override val raw: String) : WcMethodName
}
enum class WcEthMethodName(override val raw: String) : WcMethodName {
EthSign("eth_sign"),
PersonalSign("personal_sign"),
SignTypeData("eth_signTypedData"),
SignTypeDataV4("eth_signTypedData_v4"),
SignTransaction("eth_signTransaction"),
SendTransaction("eth_sendTransaction"),
}
enum class WcSolanaMethodName(override val raw: String) : WcMethodName {
SignMessage("solana_signMessage"),
SignTransaction("solana_signTransaction"),
SendAllTransaction("solana_signAllTransactions"),
}

View file

@ -0,0 +1,9 @@
package com.tangem.domain.walletconnect
import com.tangem.domain.walletconnect.model.WcMethodName
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import kotlinx.coroutines.flow.Flow
interface WcRequestService {
val wcRequest: Flow<Pair<WcMethodName, WcSdkSessionRequest>>
}

View file

@ -0,0 +1,9 @@
package com.tangem.domain.walletconnect
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.domain.walletconnect.usecase.WcMethodUseCase
interface WcRequestUseCaseFactory {
suspend fun <T : WcMethodUseCase> createUseCase(request: WcSdkSessionRequest): T
}