diff --git a/data/wallet-connect/build.gradle.kts b/data/wallet-connect/build.gradle.kts index fab7b8d0e6..159348516a 100644 --- a/data/wallet-connect/build.gradle.kts +++ b/data/wallet-connect/build.gradle.kts @@ -14,7 +14,14 @@ dependencies { /* Project - Domain */ implementation(projects.domain.walletConnect) implementation(projects.domain.walletConnect.models) + implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) + implementation(projects.domain.tokens) + implementation(projects.domain.tokens.models) + implementation(projects.domain.models) + implementation(projects.domain.legacy) + implementation(projects.libs.blockchainSdk) + implementation(projects.data.common) /* Project - Data */ implementation(projects.core.datasource) @@ -26,6 +33,9 @@ dependencies { implementation(deps.hilt.core) kapt(deps.hilt.kapt) + /* Tangem libraries */ + implementation(tangemDeps.blockchain) + /* Reown - WalletConnect */ implementation(deps.reownCore) { exclude(group = "app.cash.sqldelight", module = "android-driver") 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 02c6f391db..57dd79d196 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 @@ -1,13 +1,40 @@ package com.tangem.data.walletconnect.di +import android.app.Application +import com.squareup.moshi.Moshi +import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.data.walletconnect.DefaultWalletConnectRepository +import com.tangem.data.walletconnect.initialize.DefaultWcInitializeUseCase +import com.tangem.data.walletconnect.model.NamespaceKey +import com.tangem.data.walletconnect.network.ethereum.WcEthNetwork +import com.tangem.data.walletconnect.network.solana.WcSolanaNetwork +import com.tangem.data.walletconnect.pair.AssociateNetworksDelegate +import com.tangem.data.walletconnect.pair.CaipNamespaceDelegate +import com.tangem.data.walletconnect.pair.DefaultWcPairUseCase +import com.tangem.data.walletconnect.request.DefaultWcRequestService +import com.tangem.data.walletconnect.request.WcMethodHandler +import com.tangem.data.walletconnect.respond.DefaultWcRespondService +import com.tangem.data.walletconnect.sessions.DefaultWcSessionsManager +import com.tangem.data.walletconnect.utils.WcNamespaceConverter +import com.tangem.datasource.di.SdkMoshi import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.walletconnect.model.WcMethod import com.tangem.domain.walletconnect.repository.WalletConnectRepository +import com.tangem.domain.walletconnect.repository.WcSessionsManager +import com.tangem.domain.walletconnect.request.WcRequestService +import com.tangem.domain.walletconnect.respond.WcRespondService +import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase +import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob import javax.inject.Singleton @Module @@ -22,4 +49,119 @@ internal object WalletConnectDataModule { ): WalletConnectRepository { return DefaultWalletConnectRepository(userWalletsStore, dispatchers) } + + @Provides + @Singleton + fun wcInitializeUseCase( + application: Application, + sessionsManager: DefaultWcSessionsManager, + networkService: DefaultWcRequestService, + wcPairFlow: DefaultWcPairUseCase, + ): WcInitializeUseCase = DefaultWcInitializeUseCase( + application = application, + sessionsManager = sessionsManager, + networkService = networkService, + wcPairFlow = wcPairFlow, + ) + + @Provides + @Singleton + fun defaultWcPairUseCase( + sessionsManager: WcSessionsManager, + associateNetworksDelegate: AssociateNetworksDelegate, + caipNamespaceDelegate: CaipNamespaceDelegate, + ): DefaultWcPairUseCase = DefaultWcPairUseCase( + sessionsManager = sessionsManager, + associateNetworksDelegate = associateNetworksDelegate, + caipNamespaceDelegate = caipNamespaceDelegate, + ) + + @Provides + @Singleton + fun wcPairUseCase(default: DefaultWcPairUseCase): WcPairUseCase = default + + @Provides + @Singleton + fun defaultWcSessionsManager(): DefaultWcSessionsManager = DefaultWcSessionsManager() + + @Provides + @Singleton + fun wcSessionsManager(default: DefaultWcSessionsManager): WcSessionsManager = default + + @Provides + @Singleton + fun defaultWcRequestService( + sessionsManager: WcSessionsManager, + respondService: WcRespondService, + dispatchers: CoroutineDispatcherProvider, + diHelperBox: DiHelperBox, + ): DefaultWcRequestService { + val scope = CoroutineScope(SupervisorJob() + dispatchers.io) + return DefaultWcRequestService( + sessionsManager = sessionsManager, + respondService = respondService, + requestAdapters = diHelperBox.handlers, + scope = scope, + ) + } + + @Provides + @Singleton + fun wcRequestService(default: DefaultWcRequestService): WcRequestService = default + + @Provides + @Singleton + fun wcRespondService(): WcRespondService = DefaultWcRespondService() + + @Provides + @Singleton + fun wcEthNetwork(@SdkMoshi moshi: Moshi, respondService: WcRespondService): WcEthNetwork = WcEthNetwork( + moshi = moshi, + respondService = respondService, + ) + + @Provides + @Singleton + fun wcSolanaNetwork(): WcSolanaNetwork = WcSolanaNetwork() + + @Provides + @Singleton + fun caipNamespaceDelegate( + diHelperBox: DiHelperBox, + walletManagersFacade: WalletManagersFacade, + ): CaipNamespaceDelegate = CaipNamespaceDelegate( + namespaceConverters = diHelperBox.converters, + walletManagersFacade = walletManagersFacade, + ) + + @Provides + @Singleton + fun associateNetworksDelegate( + diHelperBox: DiHelperBox, + getWallets: GetWalletsUseCase, + currenciesRepository: CurrenciesRepository, + excludedBlockchains: ExcludedBlockchains, + ): AssociateNetworksDelegate = AssociateNetworksDelegate( + namespaceConverters = diHelperBox.converters, + getWallets = getWallets, + currenciesRepository = currenciesRepository, + excludedBlockchains = excludedBlockchains, + ) + + @Provides + @Singleton + fun diHelperBox(ethNetwork: WcEthNetwork, solanaNetwork: WcSolanaNetwork) = DiHelperBox( + handlers = setOf( + ethNetwork, + ), + converters = buildMap { + ethNetwork.namespaceKey to ethNetwork + solanaNetwork.namespaceKey to solanaNetwork + }, + ) + + internal class DiHelperBox( + val converters: Map, + val handlers: Set>, + ) } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/initialize/DefaultWcInitializeUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/initialize/DefaultWcInitializeUseCase.kt index c7e39cd519..5889dbf628 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/initialize/DefaultWcInitializeUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/initialize/DefaultWcInitializeUseCase.kt @@ -6,9 +6,9 @@ import com.reown.android.CoreClient import com.reown.android.relay.ConnectionType import com.reown.walletkit.client.Wallet import com.reown.walletkit.client.WalletKit -import com.tangem.data.walletconnect.DefaultWcSessionsManager import com.tangem.data.walletconnect.pair.DefaultWcPairUseCase import com.tangem.data.walletconnect.request.DefaultWcRequestService +import com.tangem.data.walletconnect.sessions.DefaultWcSessionsManager import com.tangem.data.walletconnect.utils.WcSdkObserver import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase import timber.log.Timber diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/model/CAIP10.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/model/CAIP10.kt new file mode 100644 index 0000000000..c338a4d343 --- /dev/null +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/model/CAIP10.kt @@ -0,0 +1,40 @@ +package com.tangem.data.walletconnect.model + +import com.tangem.data.walletconnect.model.CAIP2.Companion.CAIP_SEPARATOR + +/** + * CAIP-10 defines a way to identify an account in any blockchain specified by [CAIP2] blockchain id. + * + * Syntax + * account_id: chain_id + ":" + account_address + * chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} @see[CAIP2] + * account_address: [-.%a-zA-Z0-9]{1,128} + * + * Examples + * # Ethereum mainnet + * eip155:1:0xab16a96D359eC26a11e2C2b3d8f8B8942d5Bfcdb + * + * # Bitcoin mainnet + * bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6 + * + * For more information https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md + */ +internal data class CAIP10 constructor( + val chainId: CAIP2, + val accountAddress: String, +) { + val raw get() = "${chainId.raw}$CAIP_SEPARATOR$accountAddress" + + companion object { + private const val CAIP10_SUBSTRINGS_SIZE = 3 + fun fromRaw(s: String): CAIP10? { + val parsed = s.split(CAIP_SEPARATOR) + if (parsed.size != CAIP10_SUBSTRINGS_SIZE) return null + val namespace = parsed[0] + val reference = parsed[1] + val accountAddress = parsed[2] + val chainId = CAIP2.fromRaw(namespace + CAIP_SEPARATOR + reference) ?: return null + return CAIP10(chainId, accountAddress) + } + } +} \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/model/CAIP2.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/model/CAIP2.kt new file mode 100644 index 0000000000..fdd24f1ddd --- /dev/null +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/model/CAIP2.kt @@ -0,0 +1,36 @@ +package com.tangem.data.walletconnect.model + +/** + * CAIP-2 defines a way to identify a blockchain (e.g. Ethereum Mainnet, Görli, Bitcoin, Cosmos Hub) + * in a human-readable, developer-friendly and transaction-friendly way. + * + * Syntax + * chain_id: namespace + ":" + reference + * namespace: [-a-z0-9]{3,8} + * reference: [-_a-zA-Z0-9]{1,32} + * + * Examples + * # Ethereum mainnet + * eip155:1 + * + * # Bitcoin mainnet + * bip122:000000000019d6689c085ae165831e93 + * + * For more information https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md + */ +internal data class CAIP2 constructor( + val namespace: String, + val reference: String, +) { + val raw get() = "$namespace$CAIP_SEPARATOR$reference" + + companion object { + const val CAIP_SEPARATOR = ":" + + fun fromRaw(s: String): CAIP2? { + val parsed = s.split(CAIP_SEPARATOR) + if (parsed.size != 2) return null + return CAIP2(parsed[0], parsed[1]) + } + } +} \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/model/NamespaceKey.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/model/NamespaceKey.kt new file mode 100644 index 0000000000..d212088f0c --- /dev/null +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/model/NamespaceKey.kt @@ -0,0 +1,4 @@ +package com.tangem.data.walletconnect.model + +@JvmInline +internal value class NamespaceKey(val key: String) \ No newline at end of file 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 new file mode 100644 index 0000000000..0ce30801eb --- /dev/null +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt @@ -0,0 +1,78 @@ +package com.tangem.data.walletconnect.network.ethereum + +import com.squareup.moshi.Moshi +import com.tangem.blockchain.common.Blockchain +import com.tangem.data.walletconnect.model.CAIP2 +import com.tangem.data.walletconnect.model.NamespaceKey +import com.tangem.data.walletconnect.request.WcMethodHandler +import com.tangem.data.walletconnect.utils.WcNamespaceConverter +import com.tangem.domain.walletconnect.model.WcMethod +import com.tangem.domain.walletconnect.model.WcRequest +import com.tangem.domain.walletconnect.respond.WcRespondService +import com.tangem.domain.walletconnect.usecase.WcUseCase +import com.tangem.domain.walletconnect.usecase.WcUseCasesFlowProvider +import com.tangem.domain.walletconnect.usecase.ethereum.EthPersonalSignUseCase +import com.tangem.domain.walletconnect.usecase.ethereum.WcEthMethod +import com.tangem.domain.walletconnect.usecase.ethereum.WcEthMethod.SignMessage +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.receiveAsFlow + +@Suppress("UnusedPrivateClass") // todo(wc) remove later +internal class WcEthNetwork( + private val moshi: Moshi, + private val respondService: WcRespondService, +) : WcMethodHandler, WcUseCasesFlowProvider, WcNamespaceConverter { + + private val _useCases: Channel = Channel(Channel.BUFFERED) + override val useCases = _useCases.receiveAsFlow() + + override val namespaceKey: NamespaceKey = NamespaceKey("eip155") + + override fun canHandle(methodName: String): Boolean { + return Name.entries.find { it.raw == methodName } != null + } + + override fun deserialize(methodName: String, params: String): WcEthMethod? { + val name = Name.entries.find { it.raw == methodName } ?: return null + return when (name) { + Name.Sign -> TODO() + Name.PersonalSign -> WcMethodHandler.fromJson(params, moshi) + Name.SignTypeData -> TODO() + Name.SignTypeDataV4 -> TODO() + Name.SignTransaction -> TODO() + Name.SendTransaction -> TODO() + } + } + + override fun handle(wcRequest: WcRequest) { + wcRequest as WcRequest + val useCase = when (wcRequest.method) { + is SignMessage -> EthPersonalSignUseCase(wcRequest as WcRequest, respondService) + } + _useCases.trySend(useCase) + } + + enum class Name(val raw: String) { + Sign("eth_sign"), + PersonalSign("personal_sign"), + SignTypeData("eth_signTypedData"), + SignTypeDataV4("eth_signTypedData_v4"), + SignTransaction("eth_signTransaction"), + SendTransaction("eth_sendTransaction"), + } + + override fun toBlockchain(chainId: CAIP2): Blockchain? { + if (chainId.namespace != namespaceKey.key) return null + val ethChainId = chainId.reference.toIntOrNull() ?: return null + return Blockchain.fromChainId(ethChainId) + } + + override fun toCAIP2(blockchain: Blockchain): CAIP2? { + if (!blockchain.isEvm()) return null + val chainId = blockchain.getChainId() ?: return null + return CAIP2( + namespace = namespaceKey.key, + reference = chainId.toString(), + ) + } +} \ 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 new file mode 100644 index 0000000000..bda63dde43 --- /dev/null +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt @@ -0,0 +1,37 @@ +package com.tangem.data.walletconnect.network.solana + +import com.tangem.blockchain.common.Blockchain +import com.tangem.data.walletconnect.model.CAIP2 +import com.tangem.data.walletconnect.model.NamespaceKey +import com.tangem.data.walletconnect.utils.WcNamespaceConverter + +internal class WcSolanaNetwork : WcNamespaceConverter { + override val namespaceKey: NamespaceKey = NamespaceKey("solana") + + override fun toBlockchain(chainId: CAIP2): Blockchain? { + if (chainId.namespace != namespaceKey.key) return null + return when (chainId.reference) { + MAINNET_CHAIN_ID -> Blockchain.Solana + TESTNET_CHAIN_ID -> Blockchain.SolanaTestnet + else -> null + } + } + + override fun toCAIP2(blockchain: Blockchain): CAIP2? { + val chainId = when (blockchain) { + Blockchain.Solana -> MAINNET_CHAIN_ID + Blockchain.SolanaTestnet -> TESTNET_CHAIN_ID + else -> null + } + chainId ?: return null + return CAIP2( + namespace = namespaceKey.key, + reference = chainId, + ) + } + + companion object { + private const val MAINNET_CHAIN_ID = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" + private const val TESTNET_CHAIN_ID = "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z" + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..e24b2641ac --- /dev/null +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/AssociateNetworksDelegate.kt @@ -0,0 +1,103 @@ +package com.tangem.data.walletconnect.pair + +import com.reown.walletkit.client.Wallet +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.ExcludedBlockchains +import com.tangem.data.common.currency.getNetwork +import com.tangem.data.walletconnect.model.CAIP2 +import com.tangem.data.walletconnect.model.NamespaceKey +import com.tangem.data.walletconnect.utils.WcNamespaceConverter +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.walletconnect.model.WcNetwork +import com.tangem.domain.walletconnect.model.WcPairError +import com.tangem.domain.walletconnect.model.WcSessionProposal.ProposalNetwork +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.usecase.GetWalletsUseCase + +internal class AssociateNetworksDelegate constructor( + private val namespaceConverters: Map, + private val getWallets: GetWalletsUseCase, + private val currenciesRepository: CurrenciesRepository, + private val excludedBlockchains: ExcludedBlockchains, +) { + + @Throws(WcPairError.UnsupportedNetworks::class) + suspend fun associate(sessionProposal: Wallet.Model.SessionProposal): Map { + val userWallets = getWallets.invokeSync().filter { it.isMultiCurrency } + val requiredNamespaces: Set = sessionProposal.requiredNamespaces.setOfChainId() + val optionalNamespaces: Set = sessionProposal.optionalNamespaces.setOfChainId() + + return userWallets.associate { wallet -> + wallet.walletId to mapNetworksForWallet(wallet, requiredNamespaces, optionalNamespaces) + } + } + + private suspend fun mapNetworksForWallet( + wallet: UserWallet, + requiredNamespaces: Set, + optionalNamespaces: Set, + ): ProposalNetwork { + val walletNetworks = currenciesRepository.getMultiCurrencyWalletCurrenciesSync(wallet.walletId) + .filterIsInstance() + .map { it.network } + + val unknownRequired = mutableSetOf() + val missingRequired = mutableSetOf() + val required = mutableSetOf() + val available = mutableSetOf() + val notAdded = mutableSetOf() + + fun CAIP2.toBlockchain() = namespaceConverters[NamespaceKey(this.namespace)]?.toBlockchain(this) + fun Blockchain.toNetwork() = getNetwork( + blockchain = this, + extraDerivationPath = null, + scanResponse = wallet.scanResponse, + excludedBlockchains = excludedBlockchains, + ) + + requiredNamespaces.forEach { chainId -> + val blockchain = chainId.toBlockchain() + if (blockchain == null) { + unknownRequired.add(WcNetwork.Unknown(missingNetworkName(chainId))) + return@forEach + } + val wcNetwork = blockchain.toNetwork() + if (wcNetwork == null) { + unknownRequired.add(WcNetwork.Unknown(missingNetworkName(blockchain))) + return@forEach + } + val walletNetwork = walletNetworks.find { network -> wcNetwork.id == network.id } + if (walletNetwork == null) { + missingRequired.add(WcNetwork.Supported(wcNetwork)) + } else { + required.add(WcNetwork.Supported(walletNetwork)) + } + } + optionalNamespaces.forEach { chainId -> + val wcNetwork = chainId.toBlockchain()?.toNetwork() ?: return@forEach + val walletNetwork = walletNetworks.find { network -> wcNetwork.id == network.id } + if (walletNetwork == null) { + available.add(WcNetwork.Supported(wcNetwork)) + } else { + notAdded.add(WcNetwork.Supported(walletNetwork)) + } + } + if (unknownRequired.isNotEmpty()) throw WcPairError.UnsupportedNetworks(unknownRequired) + return ProposalNetwork( + walletId = wallet.walletId, + missingRequired = missingRequired, + required = required, + available = available, + notAdded = notAdded, + ) + } + + private fun Map.setOfChainId(): Set = + this.values.flatMap { proposal -> proposal.chains ?: listOf() } + .mapNotNull { rawChainId -> CAIP2.fromRaw(rawChainId) }.toSet() + + private fun missingNetworkName(chainId: CAIP2): String = chainId.namespace.replaceFirstChar(Char::titlecase) + private fun missingNetworkName(blockchain: Blockchain): String = blockchain.getCoinName() +} \ No newline at end of file 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 new file mode 100644 index 0000000000..98e5e6be32 --- /dev/null +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt @@ -0,0 +1,62 @@ +package com.tangem.data.walletconnect.pair + +import com.reown.walletkit.client.Wallet +import com.tangem.blockchain.common.Blockchain +import com.tangem.data.walletconnect.model.CAIP10 +import com.tangem.data.walletconnect.model.NamespaceKey +import com.tangem.data.walletconnect.utils.WcNamespaceConverter +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.models.UserWalletId + +internal class CaipNamespaceDelegate constructor( + private val namespaceConverters: Map, + private val walletManagersFacade: WalletManagersFacade, +) { + + suspend fun associate( + sessionProposal: Wallet.Model.SessionProposal, + userWalletId: UserWalletId, + networks: List, + ): Map { + val converters = namespaceConverters.values + + val result = mutableMapOf() + + networks.map { network -> + val blockchain = Blockchain.fromId(network.id.value) + val address = walletManagersFacade.getDefaultAddress(userWalletId, network) + val chainId = converters.firstOrNull { it.toCAIP2(blockchain) != null }?.toCAIP2(blockchain) + 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) } + } + session.chains.add(account.chainId.raw) + session.accounts.add(account.raw) + session.methods.addAll(methods) + } + return result.mapValues { (namespaceKey, session) -> + Wallet.Model.Namespace.Session( + chains = session.chains.toList(), + accounts = session.accounts.toList(), + methods = session.methods.toList(), + events = session.events.toList(), + ) + } + } + + private data class Session( + val chains: MutableSet = mutableSetOf(), + val accounts: MutableSet = mutableSetOf(), + val methods: MutableSet = mutableSetOf(), + val events: MutableSet = mutableSetOf(), + ) +} \ No newline at end of file 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 89099dc766..f574bcd29e 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 @@ -7,15 +7,21 @@ import com.reown.walletkit.client.Wallet import com.reown.walletkit.client.WalletKit import com.tangem.data.walletconnect.utils.WcSdkObserver import com.tangem.data.walletconnect.utils.toOurModel +import com.tangem.domain.walletconnect.model.WcPairError import com.tangem.domain.walletconnect.model.WcSession +import com.tangem.domain.walletconnect.model.WcSessionApprove import com.tangem.domain.walletconnect.model.WcSessionProposal -import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionProposal +import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData import com.tangem.domain.walletconnect.repository.WcSessionsManager import com.tangem.domain.walletconnect.usecase.pair.WcPairState import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.suspendCancellableCoroutine import timber.log.Timber import kotlin.coroutines.resume @@ -25,10 +31,10 @@ val unsupportedDApps = listOf("dYdX", "dYdX v4", "Apex Pro", "The Sandbox") @Suppress("UnusedPrivateMember") // todo(wc) remove after add mapping internal class DefaultWcPairUseCase( private val sessionsManager: WcSessionsManager, + private val associateNetworksDelegate: AssociateNetworksDelegate, + private val caipNamespaceDelegate: CaipNamespaceDelegate, ) : WcPairUseCase, WcSdkObserver { - private val onWalletSelect = Channel() - private val onAccountSelect = Channel() private val onCallTerminalAction = Channel() private val onSessionProposal = Channel>(Channel.BUFFERED) @@ -40,91 +46,66 @@ internal class DefaultWcPairUseCase( // call sdk.pair and wait result, finish flow on error walletKitPair(uri).onLeft { throwable -> - emit(WcPairState.Error(throwable)) + emit(WcPairState.Error(WcPairError.Unknown(throwable.localizedMessage.orEmpty()))) return@flow } // wait for sdk onSessionProposal callback - val (sdkSessionProposal, verifyContext) = onSessionProposal.receiveAsFlow().map { (fist, second) -> - val ourCopy = WcSdkSessionProposal( - name = fist.name, - description = fist.description, - url = fist.url, - proposerPublicKey = fist.proposerPublicKey, - ) - ourCopy to second - }.first { (sessionProposal, verifyContext) -> - true // todo(wc) check verifyContext? compare uri? - } + val (sdkSessionProposal, verifyContext) = onSessionProposal.receiveAsFlow() + .first { (sessionProposal, verifyContext) -> + true // todo(wc) check verifyContext? compare uri? + } // check unsupported dApps, just local constant for now, finish if unsupported if (sdkSessionProposal.name in unsupportedDApps) { Timber.i("Unsupported DApp") - val error = WcPairState - .Error(RuntimeException("todo(wc) use domain exception")) // todo(wc) WalletConnectError.UnsupportedDApp + val error = WcPairState.Error(WcPairError.UnsupportedDApp) emit(error) return@flow } - // flow that collect terminal and all middle actions - val actionsFlow = channelFlow { - val userWallet = onWalletSelect.receiveAsFlow() - .stateIn(scope = this, started = SharingStarted.Lazily, initialValue = selectedWallet) - val accountSelect = onAccountSelect.receiveAsFlow() - .stateIn(scope = this, started = SharingStarted.Lazily, initialValue = Any()) - // combine all middle variables and emit new ProposalState to main FlowCollector - combine(accountSelect, userWallet) { account, selectedWallet -> - buildProposalState(sdkSessionProposal, verifyContext, account, selectedWallet) - }.onEach { newProposalState -> this@flow.emit(newProposalState) } - .launchIn(this) - - // collect terminal action and emit to this inner channelFlow, unlike combine above - onCallTerminalAction.receiveAsFlow() - .onEach { this.channel.send(it) } - .launchIn(this) - } + val proposalState = buildProposalState(sdkSessionProposal, verifyContext) + .fold(ifLeft = { WcPairState.Error(it) }, ifRight = { it }) + emit(proposalState) // wait first terminal action and continue WC pair flow - val sessionForApprove: WcSessionProposal = when (val terminalAction = actionsFlow.first()) { + val terminalAction = onCallTerminalAction.receiveAsFlow().first() + val sessionForApprove: WcSessionApprove? = when (terminalAction) { is TerminalAction.Approve -> terminalAction.sessionForApprove TerminalAction.Reject -> { // non suspending WalletKit.rejectSession call - rejectSession(sdkSessionProposal) - return@flow + rejectSession(sdkSessionProposal.proposerPublicKey) + null } } + // finish flow if rejected above + sessionForApprove ?: return@flow // start flow of approving in wc sdk emit(WcPairState.Approving.Loading(sessionForApprove)) - fun mapResult(either: Either) = - WcPairState.Approving.Result(sessionForApprove, either) // call sdk approve and wait result - walletKitApproveSession(sessionForApprove).onLeft { emit(mapResult(it.left())) }.onRight { - // wait for sdk callback - val result = when (val settledSession = onSessionSettleResponse.receiveAsFlow().first()) { - is Wallet.Model.SettledSessionResponse.Error -> mapResult( - // WalletConnectError.ExternalApprovalError(settledSession.errorMessage) todo(wc) - RuntimeException("todo(wc) use domain exception").left(), - ) + val either = walletKitApproveSession( + sessionForApprove = sessionForApprove, + sdkSessionProposal = sdkSessionProposal, + ).fold( + ifLeft = { WcPairError.ExternalApprovalError(it.localizedMessage.orEmpty()).left() }, + ifRight = { + when (val settledSession = onSessionSettleResponse.receiveAsFlow().first()) { + is Wallet.Model.SettledSessionResponse.Error -> WcPairError.ExternalApprovalError( + settledSession.errorMessage, + ).left() - is Wallet.Model.SettledSessionResponse.Result -> { - val newSession = settledSession.session.toDomain(sessionForApprove.wallet) - sessionsManager.saveSessions(sessionForApprove.wallet.walletId, newSession) - mapResult(newSession.right()) + is Wallet.Model.SettledSessionResponse.Result -> { + val newSession = settledSession.session.toDomain(sessionForApprove.walletId) + sessionsManager.saveSessions(sessionForApprove.walletId, newSession) + newSession.right() + } } - } - emit(result) - } + }, + ) + emit(WcPairState.Approving.Result(sessionForApprove, either)) } - override fun onWalletSelect(selectedWallet: UserWallet) { - onWalletSelect.trySend(selectedWallet) - } - - override fun onAccountSelect(account: Any) { - onAccountSelect.trySend(account) - } - - override fun approve(sessionForApprove: WcSessionProposal) { + override fun approve(sessionForApprove: WcSessionApprove) { onCallTerminalAction.trySend(TerminalAction.Approve(sessionForApprove)) } @@ -162,10 +143,22 @@ internal class DefaultWcPairUseCase( ) } - private suspend fun walletKitApproveSession(sessionForApprove: WcSessionProposal): Either = - suspendCancellableCoroutine { continuation -> + private suspend fun walletKitApproveSession( + sessionForApprove: WcSessionApprove, + sdkSessionProposal: Wallet.Model.SessionProposal, + ): Either { + val namespaces = caipNamespaceDelegate.associate( + sdkSessionProposal, + sessionForApprove.walletId, + sessionForApprove.network.map { it.network }, + ) + val sessionApprove = Wallet.Params.SessionApprove( + proposerPublicKey = sdkSessionProposal.proposerPublicKey, + namespaces = namespaces, + ) + return suspendCancellableCoroutine { continuation -> WalletKit.approveSession( - params = TODO("wc sdk model"), + params = sessionApprove, onSuccess = { Timber.i("Approved successfully: $it") continuation.resume(Unit.right()) @@ -176,11 +169,12 @@ internal class DefaultWcPairUseCase( }, ) } + } - private fun rejectSession(sessionProposal: WcSdkSessionProposal) { + private fun rejectSession(proposerPublicKey: String) { WalletKit.rejectSession( params = Wallet.Params.SessionReject( - proposerPublicKey = sessionProposal.proposerPublicKey, + proposerPublicKey = proposerPublicKey, reason = "", ), onSuccess = { @@ -192,25 +186,38 @@ internal class DefaultWcPairUseCase( ) } - private fun buildProposalState( - sessionProposal: WcSdkSessionProposal, + private suspend fun buildProposalState( + sessionProposal: Wallet.Model.SessionProposal, verifyContext: Wallet.Model.VerifyContext, - selectedAccount: Any, - userWallet: UserWallet, - ): WcPairState.Proposal { - // todo(wc) a lot of mapping from Wallet.Model.SessionProposal to our Blockchain, Network, ect - // todo(wc) check security status by Wallet.Model.VerifyContext or Blockaid, don't know for now - val mock: WcSessionProposal = TODO() - return WcPairState.Proposal(mock, Any()) - } + ): Either = runCatching { + val proposalNetwork = associateNetworksDelegate.associate(sessionProposal) + val appMetaData = WcAppMetaData( + name = sessionProposal.name, + description = sessionProposal.description, + url = sessionProposal.url, + icons = sessionProposal.icons.map { it.toString() }, + redirect = sessionProposal.redirect, + ) + val dAppSession = WcSessionProposal( + dAppMetaData = appMetaData, + proposalNetwork = proposalNetwork, + securityStatus = Any(), + ) + WcPairState.Proposal(dAppSession) + }.fold(onSuccess = { it.right() }, onFailure = { + when (it) { + is WcPairError -> it.left() + else -> WcPairError.Unknown(it.localizedMessage.orEmpty()).left() + } + },) - private fun Wallet.Model.Session.toDomain(userWallet: UserWallet): WcSession = WcSession( - userWalletId = userWallet.walletId, + private fun Wallet.Model.Session.toDomain(walletId: UserWalletId): WcSession = WcSession( + userWalletId = walletId, sdkModel = this.toOurModel(), ) private sealed interface TerminalAction { - data class Approve(val sessionForApprove: WcSessionProposal) : TerminalAction + data class Approve(val sessionForApprove: WcSessionApprove) : TerminalAction data object Reject : TerminalAction } } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt index c36cfbdb75..f69f2f4563 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt @@ -6,7 +6,6 @@ import com.tangem.data.walletconnect.utils.toOurModel import com.tangem.domain.walletconnect.model.WcMethod import com.tangem.domain.walletconnect.model.WcRequest import com.tangem.domain.walletconnect.repository.WcSessionsManager -import com.tangem.domain.walletconnect.request.WcRequestHandler import com.tangem.domain.walletconnect.request.WcRequestService import com.tangem.domain.walletconnect.respond.WcRespondService import kotlinx.coroutines.CoroutineScope @@ -16,7 +15,7 @@ import kotlinx.coroutines.launch internal class DefaultWcRequestService( private val sessionsManager: WcSessionsManager, private val respondService: WcRespondService, - private val requestAdapters: List>, + private val requestAdapters: Set>, private val scope: CoroutineScope, // todo(wc) is ok inject scope? featureScope like Decompose Model scope ) : WcRequestService, WcSdkObserver { @@ -32,7 +31,7 @@ internal class DefaultWcRequestService( val params = sr.request.params scope.launch { val session = sessionsManager.findSessionByTopic(sr.topic) - val handler: WcRequestHandler? = requestAdapters.firstOrNull { it.canHandle(method) } + val handler: WcMethodHandler? = requestAdapters.firstOrNull { it.canHandle(method) } val deserialized: WcMethod? = handler?.deserialize(method, params) if (handler == null || deserialized == null || session == null) { diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/request/WcRequestHandler.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/WcMethodHandler.kt similarity index 78% rename from domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/request/WcRequestHandler.kt rename to data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/WcMethodHandler.kt index 410815ae03..b31b707d58 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/request/WcRequestHandler.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/WcMethodHandler.kt @@ -1,14 +1,14 @@ -package com.tangem.domain.walletconnect.request +package com.tangem.data.walletconnect.request import com.squareup.moshi.Moshi import com.squareup.moshi.adapter import com.tangem.domain.walletconnect.model.WcMethod import com.tangem.domain.walletconnect.model.WcRequest -interface WcRequestHandler { +interface WcMethodHandler { fun canHandle(methodName: String): Boolean fun deserialize(methodName: String, params: String): M? - fun handle(wcRequest: WcRequest) + fun handle(wcRequest: WcRequest) companion object { @OptIn(ExperimentalStdlibApi::class) diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/DefaultWcSessionsManager.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt similarity index 96% rename from data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/DefaultWcSessionsManager.kt rename to data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt index 388c5c30a1..cfd0a023d0 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/DefaultWcSessionsManager.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt @@ -1,4 +1,4 @@ -package com.tangem.data.walletconnect +package com.tangem.data.walletconnect.sessions import com.reown.walletkit.client.Wallet import com.tangem.data.walletconnect.utils.WcSdkObserver 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 new file mode 100644 index 0000000000..bb1e86298b --- /dev/null +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNamespaceConverter.kt @@ -0,0 +1,12 @@ +package com.tangem.data.walletconnect.utils + +import com.tangem.blockchain.common.Blockchain +import com.tangem.data.walletconnect.model.CAIP2 +import com.tangem.data.walletconnect.model.NamespaceKey + +internal interface WcNamespaceConverter { + + val namespaceKey: NamespaceKey + fun toBlockchain(chainId: CAIP2): Blockchain? + fun toCAIP2(blockchain: Blockchain): CAIP2? +} \ No newline at end of file diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcNetwork.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcNetwork.kt new file mode 100644 index 0000000000..1ef5352c31 --- /dev/null +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcNetwork.kt @@ -0,0 +1,14 @@ +package com.tangem.domain.walletconnect.model + +import com.tangem.domain.tokens.model.Network + +sealed interface WcNetwork { + + val name: String + + data class Supported(val network: Network) : WcNetwork { + override val name: String get() = network.name + } + + data class Unknown(override val name: String) : WcNetwork +} \ No newline at end of file diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairError.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairError.kt new file mode 100644 index 0000000000..8f90d64512 --- /dev/null +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairError.kt @@ -0,0 +1,15 @@ +package com.tangem.domain.walletconnect.model + +sealed class WcPairError(override val message: String) : Exception(message) { + + data object UnsupportedDApp : WcPairError("UnsupportedDApp") + data class UnsupportedNetworks( + val chains: Set, + ) : WcPairError("ApprovalErrorMissingNetworks") + + data class ExternalApprovalError( + override val message: String, + ) : WcPairError("ExternalApprovalError") + + data class Unknown(override val message: String) : WcPairError(message) +} \ No newline at end of file diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSessionApprove.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSessionApprove.kt new file mode 100644 index 0000000000..06f4291649 --- /dev/null +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSessionApprove.kt @@ -0,0 +1,8 @@ +package com.tangem.domain.walletconnect.model + +import com.tangem.domain.wallets.models.UserWalletId + +data class WcSessionApprove( + val walletId: UserWalletId, + val network: List, +) \ No newline at end of file diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSessionProposal.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSessionProposal.kt index 45c189cf20..648163817b 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSessionProposal.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSessionProposal.kt @@ -1,15 +1,19 @@ package com.tangem.domain.walletconnect.model -import com.tangem.domain.tokens.model.Network -import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionProposal -import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData +import com.tangem.domain.wallets.models.UserWalletId data class WcSessionProposal( - val dAppInfo: WcSdkSessionProposal, + val dAppMetaData: WcAppMetaData, + val proposalNetwork: Map, + val securityStatus: Any, +) { - // todo(wc) should map for all user wallets? - val wallet: UserWallet, - val missingChains: List, - val availableChains: List, - val notAddedChains: List, -) \ No newline at end of file + data class ProposalNetwork( + val walletId: UserWalletId, + val missingRequired: Set, + val required: Set, + val available: Set, + val notAdded: Set, + ) +} \ No newline at end of file diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcAppMetaData.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcAppMetaData.kt new file mode 100644 index 0000000000..f8de996d34 --- /dev/null +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcAppMetaData.kt @@ -0,0 +1,15 @@ +package com.tangem.domain.walletconnect.model.sdkcopy + +/** + * copy of [com.reown.android.Core.Model.AppMetaData] + */ +data class WcAppMetaData( + val name: String, + val description: String, + val url: String, + val icons: List, + val redirect: String?, + val appLink: String? = null, + val linkMode: Boolean = false, + val verifyUrl: String? = null, +) \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcUseCasesFlowProvider.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcUseCasesFlowProvider.kt index 700cf1865c..d82ecfdb41 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcUseCasesFlowProvider.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcUseCasesFlowProvider.kt @@ -3,5 +3,5 @@ package com.tangem.domain.walletconnect.usecase import kotlinx.coroutines.flow.Flow interface WcUseCasesFlowProvider { - val flow: Flow + val useCases: Flow } \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/ethereum/WcEthChain.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/ethereum/WcEthChain.kt deleted file mode 100644 index ab18902b6e..0000000000 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/ethereum/WcEthChain.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.tangem.domain.walletconnect.usecase.ethereum - -import com.squareup.moshi.Moshi -import com.tangem.domain.walletconnect.model.WcRequest -import com.tangem.domain.walletconnect.request.WcRequestHandler -import com.tangem.domain.walletconnect.respond.WcRespondService -import com.tangem.domain.walletconnect.usecase.WcUseCase -import com.tangem.domain.walletconnect.usecase.WcUseCasesFlowProvider -import com.tangem.domain.walletconnect.usecase.ethereum.WcEthMethod.SignMessage -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.receiveAsFlow - -@Suppress("UnusedPrivateClass") // todo(wc) remove later -private class WcEthChain( - val moshi: Moshi, - private val respondService: WcRespondService, -) : WcRequestHandler, WcUseCasesFlowProvider { - - private val _flow: Channel = Channel(Channel.BUFFERED) - override val flow = _flow.receiveAsFlow() - - override fun canHandle(methodName: String): Boolean { - return Name.entries.find { it.raw == methodName } != null - } - - override fun deserialize(methodName: String, params: String): WcEthMethod? { - val name = Name.entries.find { it.raw == methodName } ?: return null - return when (name) { - Name.Sign -> TODO() - Name.PersonalSign -> WcRequestHandler.fromJson(params, moshi) - Name.SignTypeData -> TODO() - Name.SignTypeDataV4 -> TODO() - Name.SignTransaction -> TODO() - Name.SendTransaction -> TODO() - } - } - - override fun handle(wcRequest: WcRequest) { - val useCase = when (wcRequest.method) { - is SignMessage -> EthPersonalSignUseCase(wcRequest as WcRequest, respondService) - } - _flow.trySend(useCase) - } - - enum class Name(val raw: String) { - Sign("eth_sign"), - PersonalSign("personal_sign"), - SignTypeData("eth_signTypedData"), - SignTypeDataV4("eth_signTypedData_v4"), - SignTransaction("eth_signTransaction"), - SendTransaction("eth_sendTransaction"), - } -} \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/pair/WcPairState.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/pair/WcPairState.kt index 1472db789e..07edd2b5f8 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/pair/WcPairState.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/pair/WcPairState.kt @@ -1,24 +1,23 @@ package com.tangem.domain.walletconnect.usecase.pair import arrow.core.Either +import com.tangem.domain.walletconnect.model.WcPairError import com.tangem.domain.walletconnect.model.WcSession +import com.tangem.domain.walletconnect.model.WcSessionApprove import com.tangem.domain.walletconnect.model.WcSessionProposal sealed interface WcPairState { data object Loading : WcPairState - data class Error(val throwable: Throwable) : WcPairState - data class Proposal( - val dAppSession: WcSessionProposal, - val securityStatus: Any, - ) : WcPairState + data class Error(val error: WcPairError) : WcPairState + data class Proposal(val dAppSession: WcSessionProposal) : WcPairState sealed interface Approving : WcPairState { - val session: WcSessionProposal + val session: WcSessionApprove - data class Loading(override val session: WcSessionProposal) : Approving + data class Loading(override val session: WcSessionApprove) : Approving data class Result( - override val session: WcSessionProposal, - val result: Either, + override val session: WcSessionApprove, + val result: Either, ) : Approving } } \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/pair/WcPairUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/pair/WcPairUseCase.kt index 4bf3e9b047..2600c0d889 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/pair/WcPairUseCase.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/pair/WcPairUseCase.kt @@ -1,6 +1,6 @@ package com.tangem.domain.walletconnect.usecase.pair -import com.tangem.domain.walletconnect.model.WcSessionProposal +import com.tangem.domain.walletconnect.model.WcSessionApprove import com.tangem.domain.wallets.models.UserWallet import kotlinx.coroutines.flow.Flow @@ -8,12 +8,7 @@ interface WcPairUseCase { fun pairFlow(uri: String, source: Source, selectedWallet: UserWallet): Flow - // todo(wc) should map for all user wallets without this action in domain layer? - fun onWalletSelect(selectedWallet: UserWallet) - // todo(wc) accounts in future? - fun onAccountSelect(account: Any) - - fun approve(sessionForApprove: WcSessionProposal) + fun approve(sessionForApprove: WcSessionApprove) fun reject() enum class Source { QR, DEEPLINK, ETC }