Updated on 2026-08-14
This commit is contained in:
parent
ac6f73170e
commit
bdcd8f2ce8
25 changed files with 224 additions and 30 deletions
|
|
@ -4,7 +4,7 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcBlockchainHelper
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.Account
|
||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||
|
||||
internal class TangemWcBlockchainHelper : WcBlockchainHelper {
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.reown.walletkit.client.Wallet
|
|||
import com.reown.walletkit.client.WalletKit
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.walletconnect.pair.unsupportedDApps
|
||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||
import com.tangem.tap.common.analytics.events.WalletConnect
|
||||
import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import com.squareup.moshi.JsonAdapter
|
|||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.datasource.files.FileReader
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.Session
|
||||
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
|
||||
import com.tangem.domain.walletconnect.model.legacy.Session
|
||||
import timber.log.Timber
|
||||
|
||||
internal class DefaultWalletConnectSessionsRepository(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
|||
import com.tangem.datasource.di.SdkMoshi
|
||||
import com.tangem.datasource.files.FileReader
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.*
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import com.tangem.blockchainsdk.utils.toNetworkId
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||
import com.tangem.domain.walletconnect.model.legacy.Session
|
||||
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.Account
|
||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||
|
||||
interface WcBlockchainHelper {
|
||||
fun chainIdToNetworkIdOrNull(chainId: String): String?
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.BnbData
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models
|
||||
|
||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcRequest
|
||||
import java.net.URI
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ dependencies {
|
|||
implementation(projects.domain.onramp.models)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.nft.models)
|
||||
implementation(projects.domain.walletConnect.models)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.local.walletconnect.DefaultWalletConnectStore
|
||||
import com.tangem.datasource.local.walletconnect.WalletConnectStore
|
||||
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
||||
import com.tangem.datasource.utils.setTypes
|
||||
import com.tangem.domain.walletconnect.model.WcSessionDTO
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object WalletConnectModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWalletConnectStore(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): WalletConnectStore {
|
||||
return DefaultWalletConnectStore(
|
||||
persistenceStore = DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = setTypes<WcSessionDTO>(),
|
||||
defaultValue = emptySet(),
|
||||
),
|
||||
produceFile = { context.dataStoreFile(fileName = "wallet_connect_sessions") },
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.datasource.local.walletconnect
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.tangem.domain.walletconnect.model.WcSessionDTO
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
internal typealias WcSessionCollection = Set<WcSessionDTO>
|
||||
|
||||
class DefaultWalletConnectStore(
|
||||
private val persistenceStore: DataStore<WcSessionCollection>,
|
||||
) : WalletConnectStore {
|
||||
|
||||
override val sessions: Flow<WcSessionCollection>
|
||||
get() = persistenceStore.data
|
||||
|
||||
override suspend fun saveSessions(sessions: WcSessionCollection) {
|
||||
persistenceStore.updateData { data -> data.plus(sessions) }
|
||||
}
|
||||
|
||||
override suspend fun removeSessions(sessions: WcSessionCollection) {
|
||||
persistenceStore.updateData { data -> data.minus(sessions) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.datasource.local.walletconnect
|
||||
|
||||
import com.tangem.domain.walletconnect.model.WcSessionDTO
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
interface WalletConnectStore {
|
||||
|
||||
val sessions: Flow<WcSessionCollection>
|
||||
suspend fun findSessionByTopic(topic: String) = sessions.first().find { it.topic == topic }
|
||||
|
||||
suspend fun saveSessions(sessions: WcSessionCollection)
|
||||
suspend fun saveSession(session: WcSessionDTO) = saveSessions(setOf(session))
|
||||
|
||||
suspend fun removeSessions(sessions: WcSessionCollection)
|
||||
suspend fun removeSession(session: WcSessionDTO) = removeSessions(setOf(session))
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
alias(deps.plugins.ksp)
|
||||
id("configuration")
|
||||
}
|
||||
dependencies {
|
||||
|
|
@ -7,4 +8,8 @@ dependencies {
|
|||
/* Domain */
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
|
||||
/* Other */
|
||||
implementation(deps.moshi)
|
||||
ksp(deps.moshi.kotlin.codegen)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.domain.walletconnect.model
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WcSessionDTO(
|
||||
val topic: String,
|
||||
val walletId: UserWalletId,
|
||||
)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models
|
||||
package com.tangem.domain.walletconnect.model.legacy
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models
|
||||
package com.tangem.domain.walletconnect.model.legacy
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.Session
|
||||
package com.tangem.domain.walletconnect.model.legacy
|
||||
|
||||
interface WalletConnectSessionsRepository {
|
||||
suspend fun loadSessions(userWallet: String): List<Session>
|
||||
|
|
@ -5,4 +5,5 @@ package com.tangem.domain.walletconnect.model.sdkcopy
|
|||
*/
|
||||
data class WcSdkSession(
|
||||
val topic: String,
|
||||
val appMetaData: WcAppMetaData,
|
||||
)
|
||||
|
|
@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.Flow
|
|||
|
||||
interface WcSessionsManager {
|
||||
val sessions: Flow<Map<UserWalletId, List<WcSession>>>
|
||||
suspend fun saveSessions(userWalletId: UserWalletId, session: WcSession)
|
||||
suspend fun removeSessions(userWalletId: UserWalletId, session: WcSession)
|
||||
suspend fun saveSession(userWalletId: UserWalletId, session: WcSession)
|
||||
suspend fun removeSession(userWalletId: UserWalletId, session: WcSession)
|
||||
suspend fun findSessionByTopic(topic: String): WcSession?
|
||||
}
|
||||
|
|
@ -13,6 +13,8 @@ android {
|
|||
|
||||
dependencies {
|
||||
implementation(projects.features.walletconnect.api)
|
||||
implementation(projects.domain.walletConnect)
|
||||
implementation(projects.domain.walletConnect.models)
|
||||
|
||||
/** Core */
|
||||
implementation(projects.core.configToggles)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue