Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-08 15:05:06 +07:00
parent ce4df5bee6
commit 6fab6d1ce3
9 changed files with 59 additions and 47 deletions

View file

@ -29,6 +29,7 @@ import com.tangem.domain.walletconnect.request.WcRequestService
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.GetUserWalletUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
@ -88,6 +89,7 @@ internal object WalletConnectDataModule {
store: WalletConnectStore,
dispatchers: CoroutineDispatcherProvider,
legacyStore: WalletConnectSessionsRepository,
getUserWallet: GetUserWalletUseCase,
getWallets: GetWalletsUseCase,
): DefaultWcSessionsManager {
val scope = CoroutineScope(SupervisorJob() + dispatchers.io)
@ -96,6 +98,7 @@ internal object WalletConnectDataModule {
dispatchers = dispatchers,
legacyStore = legacyStore,
getWallets = getWallets,
getUserWallet = getUserWallet,
scope = scope,
)
}

View file

@ -7,7 +7,7 @@ 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
import com.tangem.domain.wallets.models.UserWallet
internal class CaipNamespaceDelegate constructor(
private val namespaceConverters: Map<NamespaceKey, WcNamespaceConverter>,
@ -16,7 +16,7 @@ internal class CaipNamespaceDelegate constructor(
suspend fun associate(
sessionProposal: Wallet.Model.SessionProposal,
userWalletId: UserWalletId,
userWallet: UserWallet,
networks: List<Network>,
): Map<String, Wallet.Model.Namespace.Session> {
val converters = namespaceConverters.values
@ -25,7 +25,7 @@ internal class CaipNamespaceDelegate constructor(
networks.map { network ->
val blockchain = Blockchain.fromId(network.id.value)
val address = walletManagersFacade.getDefaultAddress(userWalletId, network)
val address = walletManagersFacade.getDefaultAddress(userWallet.walletId, network)
val chainId = converters.firstOrNull { it.toCAIP2(blockchain) != null }?.toCAIP2(blockchain)
requireNotNull(chainId)
requireNotNull(address)

View file

@ -15,7 +15,7 @@ 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.UserWalletId
import com.tangem.domain.wallets.models.UserWallet
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
@ -93,8 +93,8 @@ internal class DefaultWcPairUseCase(
).left()
is Wallet.Model.SettledSessionResponse.Result -> {
val newSession = settledSession.session.toDomain(sessionForApprove.walletId)
sessionsManager.saveSession(sessionForApprove.walletId, newSession)
val newSession = settledSession.session.toDomain(sessionForApprove.wallet)
sessionsManager.saveSession(newSession)
newSession.right()
}
}
@ -148,7 +148,7 @@ internal class DefaultWcPairUseCase(
): Either<Throwable, Unit> {
val namespaces = caipNamespaceDelegate.associate(
sdkSessionProposal,
sessionForApprove.walletId,
sessionForApprove.wallet,
sessionForApprove.network.map { it.network },
)
val sessionApprove = Wallet.Params.SessionApprove(
@ -209,8 +209,8 @@ internal class DefaultWcPairUseCase(
}
},)
private fun Wallet.Model.Session.toDomain(walletId: UserWalletId): WcSession = WcSession(
userWalletId = walletId,
private fun Wallet.Model.Session.toDomain(wallet: UserWallet): WcSession = WcSession(
wallet = wallet,
sdkModel = WcSdkSessionConverter.convert(this),
)

View file

@ -12,7 +12,8 @@ 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 com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.CoroutineScope
@ -29,6 +30,7 @@ internal class DefaultWcSessionsManager constructor(
private val store: WalletConnectStore,
private val legacyStore: WalletConnectSessionsRepository,
private val getWallets: GetWalletsUseCase,
private val getUserWallet: GetUserWalletUseCase,
private val dispatchers: CoroutineDispatcherProvider,
private val scope: CoroutineScope,
) : WcSessionsManager, WcSdkObserver {
@ -36,20 +38,22 @@ internal class DefaultWcSessionsManager constructor(
private val onSessionDelete = Channel<Wallet.Model.SessionDelete>(capacity = Channel.BUFFERED)
private val oneTimeMigration = MutableStateFlow(true)
override val sessions: Flow<Map<UserWalletId, List<WcSession>>>
get() = store.sessions
.transform { inStore ->
override val sessions: Flow<Map<UserWallet, List<WcSession>>>
get() = combine(getWallets(), store.sessions) { wallets, inStore -> wallets to inStore }
.transform { pair ->
val (wallets, inStore) = pair
val inSdk: List<Wallet.Model.Session> = WalletKit.getListOfActiveSessions()
if (oneTimeMigration.value) {
oneTimeMigration.value = false
val someMigrated = migrateLegacyStore(inStore)
val someMigrated = migrateLegacyStore(inStore, inSdk, wallets)
if (someMigrated) return@transform // ignore emit, wait next one
}
val inSdk: List<Wallet.Model.Session> = WalletKit.getListOfActiveSessions()
val associatedSessions: List<WcSession> = associateWithSdk(inSdk, inStore)
val associatedSessions: List<WcSession> = associate(inSdk, inStore, wallets)
val someRemove = removeUnknownSessions(inStore, associatedSessions)
if (someRemove) return@transform // ignore emit, wait next one
emit(associatedSessions.groupBy { it.userWalletId })
emit(associatedSessions.groupBy { it.wallet })
}
.distinctUntilChanged()
.flowOn(dispatchers.io)
override fun onWcSdkInit() {
@ -57,11 +61,11 @@ internal class DefaultWcSessionsManager constructor(
listenOnSessionDelete()
}
override suspend fun saveSession(userWalletId: UserWalletId, session: WcSession) {
store.saveSession(WcSessionDTO(session.sdkModel.topic, session.userWalletId))
override suspend fun saveSession(session: WcSession) {
store.saveSession(WcSessionDTO(session.sdkModel.topic, session.wallet.walletId))
}
override suspend fun removeSession(userWalletId: UserWalletId, session: WcSession): Either<Throwable, Unit> {
override suspend fun removeSession(session: WcSession): Either<Throwable, Unit> {
val topic = session.sdkModel.topic
val sdkCall = sdkDisconnectSession(topic)
sdkCall.onLeft { return it.left() }
@ -82,7 +86,8 @@ internal class DefaultWcSessionsManager constructor(
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 = WcSdkSessionConverter.convert(sdkSession))
val wallet = getUserWallet.invoke(storedSessions.walletId).getOrNull() ?: return@withContext null
WcSession(wallet = wallet, sdkModel = WcSdkSessionConverter.convert(sdkSession))
}
override fun onSessionDelete(sessionDelete: Wallet.Model.SessionDelete) {
@ -90,28 +95,35 @@ internal class DefaultWcSessionsManager constructor(
onSessionDelete.trySend(sessionDelete)
}
private suspend fun migrateLegacyStore(inNewStoreSessions: Set<WcSessionDTO>): Boolean {
val walletIds = getWallets.invokeSync().mapTo(mutableSetOf()) { it.walletId }
val inLegacyStoreSessions = walletIds
private suspend fun migrateLegacyStore(
inNewStore: Set<WcSessionDTO>,
inSdk: List<Wallet.Model.Session>,
wallets: List<UserWallet>,
): Boolean {
val walletIds = wallets.map { wallet -> wallet.walletId }
val inLegacyStore = walletIds
.map { walletId ->
flow { emit(legacyStore.loadSessions(walletId.stringValue).map { WcSessionDTO(it.topic, walletId) }) }
}
.merge()
.reduce { accumulator, value -> accumulator.plus(value) }
// migrate only active legacySessions
.filter { legacySession -> inSdk.any { inSdkSession -> inSdkSession.topic == legacySession.topic } }
val mustSaveInNewStore = inLegacyStoreSessions.subtract(inNewStoreSessions)
val mustSaveInNewStore = inLegacyStore.subtract(inNewStore)
if (mustSaveInNewStore.isNotEmpty()) store.saveSessions(mustSaveInNewStore)
return mustSaveInNewStore.isNotEmpty()
}
private fun associateWithSdk(
sdkSessions: List<Wallet.Model.Session>,
storeSessions: Set<WcSessionDTO>,
private fun associate(
inSdk: List<Wallet.Model.Session>,
inStore: Set<WcSessionDTO>,
wallets: List<UserWallet>,
): List<WcSession> {
val wcSessions = sdkSessions.mapNotNull { sdkSession ->
val storedSessions = storeSessions.find { it.topic == sdkSession.topic }
?: return@mapNotNull null
WcSession(userWalletId = storedSessions.walletId, sdkModel = WcSdkSessionConverter.convert(sdkSession))
val wcSessions = inStore.mapNotNull { session ->
val wallet = wallets.find { it.walletId == session.walletId } ?: return@mapNotNull null
val sdkSession = inSdk.find { it.topic == session.topic } ?: return@mapNotNull null
WcSession(wallet = wallet, sdkModel = WcSdkSessionConverter.convert(sdkSession))
}
return wcSessions
}
@ -122,9 +134,6 @@ internal class DefaultWcSessionsManager constructor(
val haveSomeUnknown = unknownStoredSessions.isNotEmpty()
if (haveSomeUnknown) {
unknownStoredSessions.forEach { unknown ->
legacyStore.removeSession(unknown.walletId.stringValue, unknown.topic)
}
store.removeSessions(unknownStoredSessions.toSet())
}
return haveSomeUnknown

View file

@ -1,9 +1,9 @@
package com.tangem.domain.walletconnect.model
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSession
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.UserWallet
data class WcSession(
val userWalletId: UserWalletId,
val wallet: UserWallet,
val sdkModel: WcSdkSession,
)

View file

@ -1,8 +1,8 @@
package com.tangem.domain.walletconnect.model
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.UserWallet
data class WcSessionApprove(
val walletId: UserWalletId,
val wallet: UserWallet,
val network: List<WcNetwork.Supported>,
)

View file

@ -2,12 +2,12 @@ package com.tangem.domain.walletconnect.repository
import arrow.core.Either
import com.tangem.domain.walletconnect.model.WcSession
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.UserWallet
import kotlinx.coroutines.flow.Flow
interface WcSessionsManager {
val sessions: Flow<Map<UserWalletId, List<WcSession>>>
suspend fun saveSession(userWalletId: UserWalletId, session: WcSession)
suspend fun removeSession(userWalletId: UserWalletId, session: WcSession): Either<Throwable, Unit>
val sessions: Flow<Map<UserWallet, List<WcSession>>>
suspend fun saveSession(session: WcSession)
suspend fun removeSession(session: WcSession): Either<Throwable, Unit>
suspend fun findSessionByTopic(topic: String): WcSession?
}

View file

@ -2,16 +2,16 @@ package com.tangem.domain.walletconnect.usecase
import com.tangem.domain.walletconnect.model.WcSession
import com.tangem.domain.walletconnect.repository.WcSessionsManager
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.UserWallet
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
class WcSessionsUseCase(private val sessionsManager: WcSessionsManager) {
operator fun invoke(): Flow<Map<UserWalletId, List<WcSession>>> {
operator fun invoke(): Flow<Map<UserWallet, List<WcSession>>> {
return sessionsManager.sessions
}
suspend fun invokeSync(): Map<UserWalletId, List<WcSession>> {
suspend fun invokeSync(): Map<UserWallet, List<WcSession>> {
return sessionsManager.sessions.first()
}
}

View file

@ -25,6 +25,6 @@ class WcDisconnectUseCase(
}
suspend fun disconnect(session: WcSession) {
sessionsManager.removeSession(session.userWalletId, session)
sessionsManager.removeSession(session)
}
}