Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-25 15:44:00 +07:00
parent 4f4e0a19fc
commit 0f9ba0ce7e
13 changed files with 101 additions and 42 deletions

View file

@ -9,6 +9,7 @@ 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.DefaultWcPairService
import com.tangem.data.walletconnect.pair.DefaultWcPairUseCase
import com.tangem.data.walletconnect.pair.WcPairSdkDelegate
import com.tangem.data.walletconnect.request.DefaultWcRequestService
@ -18,10 +19,11 @@ import com.tangem.data.walletconnect.respond.WcRespondService
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.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.datasource.local.walletconnect.WalletConnectStore
import com.tangem.domain.blockaid.BlockAidVerifier
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletconnect.WcPairService
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
import com.tangem.domain.walletconnect.repository.WalletConnectRepository
import com.tangem.domain.walletconnect.repository.WcSessionsManager
@ -58,32 +60,24 @@ internal object WalletConnectDataModule {
sessionsManager: DefaultWcSessionsManager,
networkService: DefaultWcRequestService,
pairSdkDelegate: WcPairSdkDelegate,
environmentConfigStorage: EnvironmentConfigStorage,
dispatchers: CoroutineDispatcherProvider,
): WcInitializeUseCase = DefaultWcInitializeUseCase(
application = application,
sessionsManager = sessionsManager,
networkService = networkService,
pairSdkDelegate = pairSdkDelegate,
environmentConfigStorage = environmentConfigStorage,
dispatchers = dispatchers,
)
@Provides
@Singleton
fun defaultWcPairUseCase(
sessionsManager: WcSessionsManager,
associateNetworksDelegate: AssociateNetworksDelegate,
caipNamespaceDelegate: CaipNamespaceDelegate,
sdkDelegate: WcPairSdkDelegate,
blockAidVerifier: BlockAidVerifier,
): DefaultWcPairUseCase = DefaultWcPairUseCase(
sessionsManager = sessionsManager,
associateNetworksDelegate = associateNetworksDelegate,
caipNamespaceDelegate = caipNamespaceDelegate,
sdkDelegate = sdkDelegate,
blockAidVerifier = blockAidVerifier,
)
fun defaultWcPairUseCase(): WcPairService = DefaultWcPairService()
@Provides
@Singleton
fun wcPairUseCase(default: DefaultWcPairUseCase): WcPairUseCase = default
fun wcPairUseCaseFactory(default: DefaultWcPairUseCase.Factory): WcPairUseCase.Factory = default
@Provides
@Singleton

View file

@ -10,7 +10,10 @@ import com.tangem.data.walletconnect.pair.WcPairSdkDelegate
import com.tangem.data.walletconnect.request.DefaultWcRequestService
import com.tangem.data.walletconnect.sessions.DefaultWcSessionsManager
import com.tangem.data.walletconnect.utils.WcSdkObserver
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
import timber.log.Timber
internal class DefaultWcInitializeUseCase(
@ -18,6 +21,8 @@ internal class DefaultWcInitializeUseCase(
private val sessionsManager: DefaultWcSessionsManager,
private val networkService: DefaultWcRequestService,
private val pairSdkDelegate: WcPairSdkDelegate,
private val environmentConfigStorage: EnvironmentConfigStorage,
private val dispatchers: CoroutineDispatcherProvider,
) : WcInitializeUseCase {
private val wcSdkObservers = mutableSetOf<WcSdkObserver>(
@ -26,7 +31,8 @@ internal class DefaultWcInitializeUseCase(
pairSdkDelegate,
)
override fun init(projectId: String) {
override suspend fun init() = withContext(dispatchers.io) {
val projectId = environmentConfigStorage.getConfigSync().walletConnectProjectId
val relayUrl = "relay.walletconnect.com"
val serverUrl = "wss://$relayUrl?projectId=$projectId"
val connectionType = ConnectionType.AUTOMATIC

View file

@ -0,0 +1,17 @@
package com.tangem.data.walletconnect.pair
import com.tangem.domain.walletconnect.WcPairService
import com.tangem.domain.walletconnect.model.WcPairRequest
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.receiveAsFlow
import javax.inject.Inject
class DefaultWcPairService @Inject constructor() : WcPairService {
private val _pairFlow: Channel<WcPairRequest> = Channel(Channel.BUFFERED)
override val pairFlow: Flow<WcPairRequest> = _pairFlow.receiveAsFlow()
override fun pair(request: WcPairRequest) {
_pairFlow.trySend(request)
}
}

View file

@ -9,15 +9,15 @@ import com.domain.blockaid.models.dapp.DAppData
import com.reown.walletkit.client.Wallet
import com.tangem.data.walletconnect.utils.WcSdkSessionConverter
import com.tangem.domain.blockaid.BlockAidVerifier
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.*
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 dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
@ -27,17 +27,19 @@ import timber.log.Timber
val unsupportedDApps = listOf("dYdX", "dYdX v4", "Apex Pro", "The Sandbox")
internal class DefaultWcPairUseCase(
internal class DefaultWcPairUseCase @AssistedInject constructor(
private val sessionsManager: WcSessionsManager,
private val associateNetworksDelegate: AssociateNetworksDelegate,
private val caipNamespaceDelegate: CaipNamespaceDelegate,
private val sdkDelegate: WcPairSdkDelegate,
private val blockAidVerifier: BlockAidVerifier,
@Assisted private val pairRequest: WcPairRequest,
) : WcPairUseCase {
private val onCallTerminalAction = Channel<TerminalAction>()
override fun pairFlow(uri: String, source: WcPairUseCase.Source): Flow<WcPairState> {
override operator fun invoke(): Flow<WcPairState> {
val (uri: String, source: WcPairRequest.Source) = pairRequest
return flow {
emit(WcPairState.Loading)
@ -151,4 +153,9 @@ internal class DefaultWcPairUseCase(
data class Approve(val sessionForApprove: WcSessionApprove) : TerminalAction
data object Reject : TerminalAction
}
@AssistedFactory
interface Factory : WcPairUseCase.Factory {
override fun create(pairRequest: WcPairRequest): DefaultWcPairUseCase
}
}

View file

@ -15,11 +15,11 @@ import com.tangem.data.walletconnect.pair.WcPairSdkDelegate
import com.tangem.data.walletconnect.utils.WcSdkSessionConverter
import com.tangem.domain.blockaid.BlockAidVerifier
import com.tangem.domain.walletconnect.model.WcPairError
import com.tangem.domain.walletconnect.model.WcPairRequest
import com.tangem.domain.walletconnect.model.WcSession
import com.tangem.domain.walletconnect.model.WcSessionApprove
import com.tangem.domain.walletconnect.repository.WcSessionsManager
import com.tangem.domain.walletconnect.usecase.pair.WcPairState
import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase
import io.mockk.coEvery
import io.mockk.coVerifyOrder
import io.mockk.mockk
@ -37,7 +37,7 @@ internal class DefaultWcPairUseCaseTest {
private val blockAidVerifier: BlockAidVerifier = mockk<BlockAidVerifier>()
private val url = "testUrl"
private val source = WcPairUseCase.Source.QR
private val source = WcPairRequest.Source.QR
private val loading = WcPairState.Loading
private val sdkProposal: Wallet.Model.SessionProposal
get() = Wallet.Model.SessionProposal(
@ -93,12 +93,13 @@ internal class DefaultWcPairUseCaseTest {
securityStatus = CheckDAppResult.SAFE,
)
private val useCase = DefaultWcPairUseCase(
private fun useCaseFactory() = DefaultWcPairUseCase(
sessionsManager = sessionsManager,
associateNetworksDelegate = associateNetworksDelegate,
caipNamespaceDelegate = caipNamespaceDelegate,
sdkDelegate = sdkDelegate,
blockAidVerifier = blockAidVerifier,
pairRequest = WcPairRequest(url, source),
)
@Before
@ -118,7 +119,8 @@ internal class DefaultWcPairUseCaseTest {
coEvery { sdkDelegate.pair(url) } returns sdkProposal.right()
coEvery { blockAidVerifier.verifyDApp(any()) } returns Either.catch { CheckDAppResult.SAFE }
useCase.pairFlow(url, source).test {
val useCase = useCaseFactory()
useCase.invoke().test {
assertEquals(loading, awaitItem())
coVerifyOrder {
sdkDelegate.pair(url)
@ -140,7 +142,8 @@ internal class DefaultWcPairUseCaseTest {
coEvery { sessionsManager.saveSession(sessionForSave) } returns Unit
coEvery { blockAidVerifier.verifyDApp(any()) } returns Either.catch { CheckDAppResult.SAFE }
useCase.pairFlow(url, source).test {
val useCase = useCaseFactory()
useCase.invoke().test {
assertEquals(loading, awaitItem())
coVerifyOrder {
sdkDelegate.pair(url)
@ -169,7 +172,8 @@ internal class DefaultWcPairUseCaseTest {
coEvery { sdkDelegate.rejectSession(proposerPublicKey) } returns Unit
coEvery { blockAidVerifier.verifyDApp(any()) } returns Either.catch { CheckDAppResult.SAFE }
useCase.pairFlow(url, source).test {
val useCase = useCaseFactory()
useCase.invoke().test {
assertEquals(loading, awaitItem())
coVerifyOrder {
sdkDelegate.pair(url)
@ -189,7 +193,8 @@ internal class DefaultWcPairUseCaseTest {
coEvery { sdkDelegate.pair(url) } returns unsupportedSdkProposal.right()
val unsupportedDAppError = WcPairState.Error(WcPairError.UnsupportedDApp)
useCase.pairFlow(url, source).test {
val useCase = useCaseFactory()
useCase.invoke().test {
assertEquals(loading, awaitItem())
coVerifyOrder {
sdkDelegate.pair(url)
@ -205,7 +210,8 @@ internal class DefaultWcPairUseCaseTest {
coEvery { sdkDelegate.pair(url) } returns error.left()
val errorState = WcPairState.Error(error)
useCase.pairFlow(url, source).test {
val useCase = useCaseFactory()
useCase.invoke().test {
assertEquals(loading, awaitItem())
coVerifyOrder {
sdkDelegate.pair(url)
@ -225,7 +231,8 @@ internal class DefaultWcPairUseCaseTest {
val errorResult = WcPairState.Approving.Result(sessionForApprove, error)
useCase.pairFlow(url, source).test {
val useCase = useCaseFactory()
useCase.invoke().test {
assertEquals(loading, awaitItem())
coVerifyOrder {
sdkDelegate.pair(url)

View file

@ -1,6 +1,7 @@
plugins {
alias(deps.plugins.kotlin.jvm)
alias(deps.plugins.ksp)
alias(deps.plugins.kotlin.serialization)
id("configuration")
}
dependencies {
@ -13,4 +14,7 @@ dependencies {
/* Other */
implementation(deps.moshi)
ksp(deps.moshi.kotlin.codegen)
/* Utils */
implementation(deps.kotlin.serialization)
}

View file

@ -0,0 +1,11 @@
package com.tangem.domain.walletconnect.model
import kotlinx.serialization.Serializable
@Serializable
data class WcPairRequest(
val uri: String,
val source: Source,
) {
enum class Source { QR, DEEPLINK, CLIPBOARD, ETC }
}

View file

@ -1,14 +1,18 @@
package com.tangem.domain.walletconnect.model.sdkcopy
import kotlinx.serialization.Serializable
/**
* copy of [com.reown.walletkit.client.Wallet.Model.SessionRequest]
*/
@Serializable
data class WcSdkSessionRequest(
val topic: String,
val chainId: String?,
val request: JSONRPCRequest,
) {
@Serializable
data class JSONRPCRequest(
val id: Long,
val method: String,

View file

@ -0,0 +1,10 @@
package com.tangem.domain.walletconnect
import com.tangem.domain.walletconnect.model.WcPairRequest
import kotlinx.coroutines.flow.Flow
interface WcPairService {
val pairFlow: Flow<WcPairRequest>
fun pair(request: WcPairRequest)
}

View file

@ -1,5 +1,5 @@
package com.tangem.domain.walletconnect.usecase.initialize
interface WcInitializeUseCase {
fun init(projectId: String)
suspend fun init()
}

View file

@ -1,20 +1,19 @@
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
import com.tangem.domain.walletconnect.model.*
import kotlinx.coroutines.flow.Flow
interface WcPairUseCase {
fun pairFlow(uri: String, source: Source): Flow<WcPairState>
operator fun invoke(): Flow<WcPairState>
fun approve(sessionForApprove: WcSessionApprove)
fun reject()
enum class Source { QR, DEEPLINK, CLIPBOARD, ETC }
interface Factory {
fun create(pairRequest: WcPairRequest): WcPairUseCase
}
}
sealed interface WcPairState {

View file

@ -13,7 +13,7 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase
import com.tangem.domain.walletconnect.model.WcPairRequest
import com.tangem.features.walletconnect.connections.model.WcConnectionsModel
import com.tangem.features.walletconnect.connections.routes.WcConnectionsBottomSheetRoutes
import com.tangem.features.walletconnect.connections.ui.WcConnectionsContent
@ -48,7 +48,7 @@ internal class ConnectionsComponent(
appComponentContext = childByContext(componentContext),
params = WcAppInfoContainerComponent.Params(
wcUrl = config.wcUrl,
source = WcPairUseCase.Source.QR,
source = WcPairRequest.Source.QR,
onDismiss = model.bottomSheetNavigation::dismiss,
),
)

View file

@ -20,7 +20,7 @@ import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase
import com.tangem.domain.walletconnect.model.WcPairRequest
import com.tangem.features.walletconnect.connections.model.WcAppInfoModel
import com.tangem.features.walletconnect.connections.routes.WcAppInfoRoutes
import com.tangem.features.walletconnect.impl.R
@ -103,5 +103,5 @@ internal class WcAppInfoContainerComponent(
WcAppInfoRoutes.SelectWallet -> TODO()
}
data class Params(val wcUrl: String, val source: WcPairUseCase.Source, val onDismiss: () -> Unit)
data class Params(val wcUrl: String, val source: WcPairRequest.Source, val onDismiss: () -> Unit)
}