Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-02 14:24:40 +07:00
parent ac6f73170e
commit bdcd8f2ce8
25 changed files with 224 additions and 30 deletions

View file

@ -18,8 +18,10 @@ 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.datasource.local.walletconnect.WalletConnectStore
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletconnect.model.WcMethod
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
import com.tangem.domain.walletconnect.repository.WalletConnectRepository
import com.tangem.domain.walletconnect.repository.WcSessionsManager
import com.tangem.domain.walletconnect.request.WcRequestService
@ -82,7 +84,21 @@ internal object WalletConnectDataModule {
@Provides
@Singleton
fun defaultWcSessionsManager(): DefaultWcSessionsManager = DefaultWcSessionsManager()
fun defaultWcSessionsManager(
store: WalletConnectStore,
dispatchers: CoroutineDispatcherProvider,
legacyStore: WalletConnectSessionsRepository,
getWallets: GetWalletsUseCase,
): DefaultWcSessionsManager {
val scope = CoroutineScope(SupervisorJob() + dispatchers.io)
return DefaultWcSessionsManager(
store = store,
dispatchers = dispatchers,
legacyStore = legacyStore,
getWallets = getWallets,
scope = scope,
)
}
@Provides
@Singleton

View file

@ -96,7 +96,7 @@ internal class DefaultWcPairUseCase(
is Wallet.Model.SettledSessionResponse.Result -> {
val newSession = settledSession.session.toDomain(sessionForApprove.walletId)
sessionsManager.saveSessions(sessionForApprove.walletId, newSession)
sessionsManager.saveSession(sessionForApprove.walletId, newSession)
newSession.right()
}
}

View file

@ -1,38 +1,92 @@
package com.tangem.data.walletconnect.sessions
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.datasource.local.walletconnect.WalletConnectStore
import com.tangem.domain.walletconnect.model.WcSession
import com.tangem.domain.walletconnect.model.WcSessionDTO
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
import com.tangem.domain.walletconnect.repository.WcSessionsManager
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
internal class DefaultWcSessionsManager : WcSessionsManager, WcSdkObserver {
internal class DefaultWcSessionsManager constructor(
private val store: WalletConnectStore,
private val legacyStore: WalletConnectSessionsRepository,
private val getWallets: GetWalletsUseCase,
private val dispatchers: CoroutineDispatcherProvider,
private val scope: CoroutineScope,
) : WcSessionsManager, WcSdkObserver {
private val _sessions = MutableSharedFlow<Map<UserWalletId, List<WcSession>>>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
override val sessions get() = _sessions.distinctUntilChanged()
override val sessions: Flow<Map<UserWalletId, List<WcSession>>>
get() = store.sessions
.onEach(::migrateLegacyStore)
.map(::associateWithSdk)
.distinctUntilChanged()
.flowOn(dispatchers.io)
override suspend fun saveSessions(userWalletId: UserWalletId, session: WcSession) {
TODO("Not yet implemented")
override suspend fun saveSession(userWalletId: UserWalletId, session: WcSession) {
store.saveSession(WcSessionDTO(session.sdkModel.topic, session.userWalletId))
}
override suspend fun removeSessions(userWalletId: UserWalletId, session: WcSession) {
TODO("Not yet implemented")
override suspend fun removeSession(userWalletId: UserWalletId, session: WcSession) {
store.removeSession(WcSessionDTO(session.sdkModel.topic, session.userWalletId))
}
override suspend fun findSessionByTopic(topic: String): WcSession? {
TODO("Not yet implemented")
override suspend fun findSessionByTopic(topic: String): WcSession? = withContext(dispatchers.io) {
val storedSessions = store.findSessionByTopic(topic) ?: return@withContext null
val sdkSession = WalletKit.getActiveSessionByTopic(topic) ?: return@withContext null
WcSession(userWalletId = storedSessions.walletId, sdkModel = sdkSession.toOurModel())
}
override fun onSessionDelete(sessionDelete: Wallet.Model.SessionDelete) {
// Triggered when the session is deleted by the peer
if (sessionDelete !is Wallet.Model.SessionDelete.Success) return
Timber.i("onSessionDelete: $sessionDelete")
// todo (wc) find session, delete and update sessions Flow
scope.launch {
val storedSessions = store.findSessionByTopic(sessionDelete.topic) ?: return@launch
store.removeSession(storedSessions)
}
}
private suspend fun migrateLegacyStore(inNewStoreSessions: Set<WcSessionDTO>) {
val walletIds = getWallets.invokeSync().mapTo(mutableSetOf()) { it.walletId }
val inLegacyStoreSessions = walletIds
.map { walletId ->
flow { emit(legacyStore.loadSessions(walletId.stringValue).map { WcSessionDTO(it.topic, walletId) }) }
}
.merge()
.first()
val mustSaveInNewStore = inLegacyStoreSessions.subtract(inNewStoreSessions)
if (mustSaveInNewStore.isNotEmpty()) store.saveSessions(mustSaveInNewStore)
}
private suspend fun associateWithSdk(storeSessions: Set<WcSessionDTO>): Map<UserWalletId, List<WcSession>> {
val sdkSessions = WalletKit.getListOfActiveSessions()
val wcSessions = sdkSessions.mapNotNull { sdkSession ->
val storedSessions = storeSessions.find { it.topic == sdkSession.topic }
?: return@mapNotNull null
WcSession(userWalletId = storedSessions.walletId, sdkModel = sdkSession.toOurModel())
}
val unknownStoredSessions = storeSessions
.filterNot { dto -> wcSessions.any { it.sdkModel.topic == dto.topic } }
if (unknownStoredSessions.isNotEmpty()) {
unknownStoredSessions.forEach { unknown ->
legacyStore.removeSession(unknown.walletId.stringValue, unknown.topic)
}
store.removeSessions(unknownStoredSessions.toSet())
}
return wcSessions.groupBy { it.userWalletId }
}
}

View file

@ -1,12 +1,15 @@
package com.tangem.data.walletconnect.utils
import com.reown.android.Core
import com.reown.walletkit.client.Wallet
import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSession
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest.JSONRPCRequest
internal fun Wallet.Model.Session.toOurModel(): WcSdkSession = WcSdkSession(
topic = topic,
appMetaData = metaData.toOurModel(),
)
internal fun Wallet.Model.SessionRequest.toOurModel(): WcSdkSessionRequest = WcSdkSessionRequest(
@ -19,4 +22,15 @@ internal fun Wallet.Model.SessionRequest.JSONRPCRequest.toOurModel(): JSONRPCReq
id = this.id,
method = this.method,
params = this.params,
)
internal fun Core.Model.AppMetaData?.toOurModel(): WcAppMetaData = WcAppMetaData(
name = this?.name.orEmpty(),
description = this?.description.orEmpty(),
url = this?.url.orEmpty(),
icons = this?.icons.orEmpty(),
redirect = this?.redirect,
appLink = this?.appLink,
linkMode = this?.linkMode ?: false,
verifyUrl = this?.verifyUrl,
)