Updated on 2026-08-14
This commit is contained in:
commit
1f9aa7aca8
24 changed files with 696 additions and 169 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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<NamespaceKey, WcNamespaceConverter>,
|
||||
val handlers: Set<WcMethodHandler<WcMethod>>,
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package com.tangem.data.walletconnect.model
|
||||
|
||||
@JvmInline
|
||||
internal value class NamespaceKey(val key: String)
|
||||
|
|
@ -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<WcEthMethod>, WcUseCasesFlowProvider, WcNamespaceConverter {
|
||||
|
||||
private val _useCases: Channel<WcUseCase> = 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<SignMessage>(params, moshi)
|
||||
Name.SignTypeData -> TODO()
|
||||
Name.SignTypeDataV4 -> TODO()
|
||||
Name.SignTransaction -> TODO()
|
||||
Name.SendTransaction -> TODO()
|
||||
}
|
||||
}
|
||||
|
||||
override fun handle(wcRequest: WcRequest<WcMethod>) {
|
||||
wcRequest as WcRequest<WcEthMethod>
|
||||
val useCase = when (wcRequest.method) {
|
||||
is SignMessage -> EthPersonalSignUseCase(wcRequest as WcRequest<SignMessage>, 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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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<NamespaceKey, WcNamespaceConverter>,
|
||||
private val getWallets: GetWalletsUseCase,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
) {
|
||||
|
||||
@Throws(WcPairError.UnsupportedNetworks::class)
|
||||
suspend fun associate(sessionProposal: Wallet.Model.SessionProposal): Map<UserWalletId, ProposalNetwork> {
|
||||
val userWallets = getWallets.invokeSync().filter { it.isMultiCurrency }
|
||||
val requiredNamespaces: Set<CAIP2> = sessionProposal.requiredNamespaces.setOfChainId()
|
||||
val optionalNamespaces: Set<CAIP2> = sessionProposal.optionalNamespaces.setOfChainId()
|
||||
|
||||
return userWallets.associate { wallet ->
|
||||
wallet.walletId to mapNetworksForWallet(wallet, requiredNamespaces, optionalNamespaces)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun mapNetworksForWallet(
|
||||
wallet: UserWallet,
|
||||
requiredNamespaces: Set<CAIP2>,
|
||||
optionalNamespaces: Set<CAIP2>,
|
||||
): ProposalNetwork {
|
||||
val walletNetworks = currenciesRepository.getMultiCurrencyWalletCurrenciesSync(wallet.walletId)
|
||||
.filterIsInstance<CryptoCurrency.Coin>()
|
||||
.map { it.network }
|
||||
|
||||
val unknownRequired = mutableSetOf<WcNetwork.Unknown>()
|
||||
val missingRequired = mutableSetOf<WcNetwork.Supported>()
|
||||
val required = mutableSetOf<WcNetwork.Supported>()
|
||||
val available = mutableSetOf<WcNetwork.Supported>()
|
||||
val notAdded = mutableSetOf<WcNetwork.Supported>()
|
||||
|
||||
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<String, Wallet.Model.Namespace.Proposal>.setOfChainId(): Set<CAIP2> =
|
||||
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()
|
||||
}
|
||||
|
|
@ -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<NamespaceKey, WcNamespaceConverter>,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) {
|
||||
|
||||
suspend fun associate(
|
||||
sessionProposal: Wallet.Model.SessionProposal,
|
||||
userWalletId: UserWalletId,
|
||||
networks: List<Network>,
|
||||
): Map<String, Wallet.Model.Namespace.Session> {
|
||||
val converters = namespaceConverters.values
|
||||
|
||||
val result = mutableMapOf<String, Session>()
|
||||
|
||||
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<String> = mutableSetOf(),
|
||||
val accounts: MutableSet<String> = mutableSetOf(),
|
||||
val methods: MutableSet<String> = mutableSetOf(),
|
||||
val events: MutableSet<String> = mutableSetOf(),
|
||||
)
|
||||
}
|
||||
|
|
@ -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<UserWallet>()
|
||||
private val onAccountSelect = Channel<Any>()
|
||||
private val onCallTerminalAction = Channel<TerminalAction>()
|
||||
private val onSessionProposal =
|
||||
Channel<Pair<Wallet.Model.SessionProposal, Wallet.Model.VerifyContext>>(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<TerminalAction> {
|
||||
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<Throwable, WcSession>) =
|
||||
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<Throwable, Unit> =
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
private suspend fun walletKitApproveSession(
|
||||
sessionForApprove: WcSessionApprove,
|
||||
sdkSessionProposal: Wallet.Model.SessionProposal,
|
||||
): Either<Throwable, Unit> {
|
||||
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<WcPairError, WcPairState.Proposal> = 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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<WcRequestHandler<WcMethod>>,
|
||||
private val requestAdapters: Set<WcMethodHandler<WcMethod>>,
|
||||
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<WcMethod>? = requestAdapters.firstOrNull { it.canHandle(method) }
|
||||
val handler: WcMethodHandler<WcMethod>? = requestAdapters.firstOrNull { it.canHandle(method) }
|
||||
|
||||
val deserialized: WcMethod? = handler?.deserialize(method, params)
|
||||
if (handler == null || deserialized == null || session == null) {
|
||||
|
|
|
|||
|
|
@ -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<M : WcMethod> {
|
||||
interface WcMethodHandler<out M : WcMethod> {
|
||||
fun canHandle(methodName: String): Boolean
|
||||
fun deserialize(methodName: String, params: String): M?
|
||||
fun handle(wcRequest: WcRequest<M>)
|
||||
fun handle(wcRequest: WcRequest<WcMethod>)
|
||||
|
||||
companion object {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
|
|
@ -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
|
||||
|
|
@ -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?
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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<WcNetwork.Unknown>,
|
||||
) : WcPairError("ApprovalErrorMissingNetworks")
|
||||
|
||||
data class ExternalApprovalError(
|
||||
override val message: String,
|
||||
) : WcPairError("ExternalApprovalError")
|
||||
|
||||
data class Unknown(override val message: String) : WcPairError(message)
|
||||
}
|
||||
|
|
@ -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<WcNetwork.Supported>,
|
||||
)
|
||||
|
|
@ -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<UserWalletId, ProposalNetwork>,
|
||||
val securityStatus: Any,
|
||||
) {
|
||||
|
||||
// todo(wc) should map for all user wallets?
|
||||
val wallet: UserWallet,
|
||||
val missingChains: List<Network>,
|
||||
val availableChains: List<Network>,
|
||||
val notAddedChains: List<Network>,
|
||||
)
|
||||
data class ProposalNetwork(
|
||||
val walletId: UserWalletId,
|
||||
val missingRequired: Set<WcNetwork.Supported>,
|
||||
val required: Set<WcNetwork.Supported>,
|
||||
val available: Set<WcNetwork.Supported>,
|
||||
val notAdded: Set<WcNetwork.Supported>,
|
||||
)
|
||||
}
|
||||
|
|
@ -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<String>,
|
||||
val redirect: String?,
|
||||
val appLink: String? = null,
|
||||
val linkMode: Boolean = false,
|
||||
val verifyUrl: String? = null,
|
||||
)
|
||||
|
|
@ -3,5 +3,5 @@ package com.tangem.domain.walletconnect.usecase
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface WcUseCasesFlowProvider {
|
||||
val flow: Flow<WcUseCase>
|
||||
val useCases: Flow<WcUseCase>
|
||||
}
|
||||
|
|
@ -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<WcEthMethod>, WcUseCasesFlowProvider {
|
||||
|
||||
private val _flow: Channel<WcUseCase> = 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<SignMessage>(params, moshi)
|
||||
Name.SignTypeData -> TODO()
|
||||
Name.SignTypeDataV4 -> TODO()
|
||||
Name.SignTransaction -> TODO()
|
||||
Name.SendTransaction -> TODO()
|
||||
}
|
||||
}
|
||||
|
||||
override fun handle(wcRequest: WcRequest<WcEthMethod>) {
|
||||
val useCase = when (wcRequest.method) {
|
||||
is SignMessage -> EthPersonalSignUseCase(wcRequest as WcRequest<SignMessage>, 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"),
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Throwable, WcSession>,
|
||||
override val session: WcSessionApprove,
|
||||
val result: Either<WcPairError, WcSession>,
|
||||
) : Approving
|
||||
}
|
||||
}
|
||||
|
|
@ -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<WcPairState>
|
||||
|
||||
// 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 }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue