Updated on 2026-08-14
This commit is contained in:
commit
5ee276974f
16 changed files with 182 additions and 131 deletions
|
|
@ -8,6 +8,7 @@ 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.WcPendingApprovalSessionDTO
|
||||
import com.tangem.domain.walletconnect.model.WcSessionDTO
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -30,6 +31,7 @@ object WalletConnectModule {
|
|||
@ApplicationContext context: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): WalletConnectStore {
|
||||
val scope = CoroutineScope(context = dispatchers.io + SupervisorJob())
|
||||
return DefaultWalletConnectStore(
|
||||
persistenceStore = DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
|
|
@ -38,7 +40,16 @@ object WalletConnectModule {
|
|||
defaultValue = emptySet(),
|
||||
),
|
||||
produceFile = { context.dataStoreFile(fileName = "wallet_connect_sessions") },
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
scope = scope,
|
||||
),
|
||||
pendingApprovalSessionsStore = DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = setTypes<WcPendingApprovalSessionDTO>(),
|
||||
defaultValue = emptySet(),
|
||||
),
|
||||
produceFile = { context.dataStoreFile(fileName = "wallet_connect_pending_approval_sessions") },
|
||||
scope = scope,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,39 @@
|
|||
package com.tangem.datasource.local.walletconnect
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.tangem.domain.walletconnect.model.WcPendingApprovalSessionDTO
|
||||
import com.tangem.domain.walletconnect.model.WcSessionDTO
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.transform
|
||||
import org.joda.time.DateTime
|
||||
|
||||
internal typealias WcSessionCollection = Set<WcSessionDTO>
|
||||
|
||||
internal class DefaultWalletConnectStore(
|
||||
private val persistenceStore: DataStore<WcSessionCollection>,
|
||||
private val pendingApprovalSessionsStore: DataStore<Set<WcPendingApprovalSessionDTO>>,
|
||||
) : WalletConnectStore {
|
||||
|
||||
override val sessions: Flow<WcSessionCollection>
|
||||
get() = persistenceStore.data
|
||||
|
||||
override val pendingApproval: Flow<Set<WcPendingApprovalSessionDTO>>
|
||||
get() = pendingApprovalSessionsStore.data
|
||||
.transform { pendingApprovalSet ->
|
||||
val now = DateTime.now()
|
||||
val expired = pendingApprovalSet
|
||||
.filterTo(mutableSetOf()) { it.expiredTime < now.millis }
|
||||
val shouldSomeClear = expired.isNotEmpty()
|
||||
val actualData = if (shouldSomeClear) {
|
||||
removePendingApproval(expired)
|
||||
} else {
|
||||
pendingApprovalSet
|
||||
}
|
||||
emit(actualData)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
override suspend fun saveSessions(sessions: WcSessionCollection) {
|
||||
persistenceStore.updateData { data -> data.plus(sessions) }
|
||||
}
|
||||
|
|
@ -20,4 +41,16 @@ internal class DefaultWalletConnectStore(
|
|||
override suspend fun removeSessions(sessions: WcSessionCollection) {
|
||||
persistenceStore.updateData { data -> data.minus(sessions) }
|
||||
}
|
||||
|
||||
override suspend fun savePendingApproval(
|
||||
sessions: Set<WcPendingApprovalSessionDTO>,
|
||||
): Set<WcPendingApprovalSessionDTO> {
|
||||
return pendingApprovalSessionsStore.updateData { data -> data.plus(sessions) }
|
||||
}
|
||||
|
||||
override suspend fun removePendingApproval(
|
||||
sessions: Set<WcPendingApprovalSessionDTO>,
|
||||
): Set<WcPendingApprovalSessionDTO> {
|
||||
return pendingApprovalSessionsStore.updateData { data -> data.minus(sessions) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.datasource.local.walletconnect
|
||||
|
||||
import com.tangem.domain.walletconnect.model.WcPendingApprovalSessionDTO
|
||||
import com.tangem.domain.walletconnect.model.WcSessionDTO
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
|
@ -7,6 +8,8 @@ import kotlinx.coroutines.flow.first
|
|||
interface WalletConnectStore {
|
||||
|
||||
val sessions: Flow<WcSessionCollection>
|
||||
val pendingApproval: Flow<Set<WcPendingApprovalSessionDTO>>
|
||||
|
||||
suspend fun findSessionByTopic(topic: String) = sessions.first().find { it.topic == topic }
|
||||
|
||||
suspend fun saveSessions(sessions: WcSessionCollection)
|
||||
|
|
@ -14,4 +17,7 @@ interface WalletConnectStore {
|
|||
|
||||
suspend fun removeSessions(sessions: WcSessionCollection)
|
||||
suspend fun removeSession(session: WcSessionDTO) = removeSessions(setOf(session))
|
||||
|
||||
suspend fun savePendingApproval(sessions: Set<WcPendingApprovalSessionDTO>): Set<WcPendingApprovalSessionDTO>
|
||||
suspend fun removePendingApproval(sessions: Set<WcPendingApprovalSessionDTO>): Set<WcPendingApprovalSessionDTO>
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import com.tangem.data.walletconnect.respond.WcRespondService
|
|||
import com.tangem.data.walletconnect.sessions.DefaultWcSessionsManager
|
||||
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
|
||||
import com.tangem.data.walletconnect.utils.WcNetworksConverter
|
||||
import com.tangem.data.walletconnect.utils.WcScope
|
||||
import com.tangem.datasource.di.SdkMoshi
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.datasource.local.walletconnect.WalletConnectStore
|
||||
|
|
@ -36,8 +37,6 @@ 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
|
||||
|
|
@ -80,7 +79,10 @@ internal object WalletConnectDataModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun sdkDelegate(): WcPairSdkDelegate = WcPairSdkDelegate()
|
||||
fun sdkDelegate(wcScope: WcScope, store: WalletConnectStore): WcPairSdkDelegate = WcPairSdkDelegate(
|
||||
scope = wcScope,
|
||||
store = store,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
|
|
@ -90,15 +92,15 @@ internal object WalletConnectDataModule {
|
|||
getWallets: GetWalletsUseCase,
|
||||
wcNetworksConverter: WcNetworksConverter,
|
||||
analytics: AnalyticsEventHandler,
|
||||
wcScope: WcScope,
|
||||
): DefaultWcSessionsManager {
|
||||
val scope = CoroutineScope(SupervisorJob() + dispatchers.io)
|
||||
return DefaultWcSessionsManager(
|
||||
store = store,
|
||||
dispatchers = dispatchers,
|
||||
getWallets = getWallets,
|
||||
wcNetworksConverter = wcNetworksConverter,
|
||||
analytics = analytics,
|
||||
scope = scope,
|
||||
scope = wcScope,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -106,6 +108,10 @@ internal object WalletConnectDataModule {
|
|||
@Singleton
|
||||
fun wcSessionsManager(default: DefaultWcSessionsManager): WcSessionsManager = default
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun wcScope(dispatchers: CoroutineDispatcherProvider): WcScope = WcScope(dispatchers)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun wcRequestService(default: DefaultWcRequestService): WcRequestService = default
|
||||
|
|
|
|||
|
|
@ -74,12 +74,7 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor(
|
|||
logoUrl = tokenInfo.logoUrl,
|
||||
chainId = tokenInfo.chainId,
|
||||
)
|
||||
BlockAidTransactionCheck.Result.Approval(
|
||||
result = result,
|
||||
approval = this,
|
||||
tokenInfo = tokenInfo,
|
||||
isMutable = true,
|
||||
)
|
||||
BlockAidTransactionCheck.Result.Approval(result = result)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,12 +68,7 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor(
|
|||
chainId = tokenInfo.chainId,
|
||||
)
|
||||
}
|
||||
BlockAidTransactionCheck.Result.Approval(
|
||||
result = result,
|
||||
approval = this,
|
||||
tokenInfo = tokenInfo,
|
||||
isMutable = true,
|
||||
)
|
||||
BlockAidTransactionCheck.Result.Approval(result = result)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,13 +9,11 @@ import com.domain.blockaid.models.dapp.DAppData
|
|||
import com.reown.walletkit.client.Wallet
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.walletconnect.utils.WC_TAG
|
||||
import com.tangem.data.walletconnect.utils.WcSdkSessionConverter
|
||||
import com.tangem.data.walletconnect.utils.getDappOriginUrl
|
||||
import com.tangem.domain.blockaid.BlockAidVerifier
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents
|
||||
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 dagger.assisted.Assisted
|
||||
|
|
@ -25,12 +23,12 @@ import kotlinx.coroutines.TimeoutCancellationException
|
|||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.Duration
|
||||
import timber.log.Timber
|
||||
import java.net.URI
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultWcPairUseCase @AssistedInject constructor(
|
||||
private val sessionsManager: WcSessionsManager,
|
||||
private val associateNetworksDelegate: AssociateNetworksDelegate,
|
||||
private val caipNamespaceDelegate: CaipNamespaceDelegate,
|
||||
private val sdkDelegate: WcPairSdkDelegate,
|
||||
|
|
@ -105,24 +103,29 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
|
||||
// start flow of approving in wc sdk
|
||||
emit(WcPairState.Approving.Loading(sessionForApprove))
|
||||
|
||||
val connectingTime = DateTime.now().millis
|
||||
val expiredTime = connectingTime + Duration
|
||||
.standardMinutes(PENDING_SESSION_EXPIRED_DURATION_MIN)
|
||||
.millis
|
||||
val sessionDTO = WcSessionDTO(
|
||||
topic = "",
|
||||
walletId = sessionForApprove.wallet.walletId,
|
||||
url = sdkVerifyContext.getDappOriginUrl(),
|
||||
securityStatus = proposalState.dAppSession.securityStatus,
|
||||
connectingTime = connectingTime,
|
||||
)
|
||||
val pendingSessionForSave = WcPendingApprovalSessionDTO(
|
||||
pairingTopic = sdkSessionProposal.pairingTopic,
|
||||
session = sessionDTO,
|
||||
expiredTime = expiredTime,
|
||||
)
|
||||
|
||||
val either = walletKitApproveSession(
|
||||
pendingSessionForSave = pendingSessionForSave,
|
||||
sessionForApprove = sessionForApprove,
|
||||
sdkSessionProposal = sdkSessionProposal,
|
||||
).map { settledSession ->
|
||||
val newSession = WcSession(
|
||||
wallet = sessionForApprove.wallet,
|
||||
sdkModel = WcSdkSessionConverter.convert(
|
||||
value = WcSdkSessionConverter.Input(
|
||||
originUrl = sdkVerifyContext.getDappOriginUrl(),
|
||||
session = settledSession.session,
|
||||
),
|
||||
),
|
||||
securityStatus = proposalState.dAppSession.securityStatus,
|
||||
networks = sessionForApprove.network.toSet(),
|
||||
connectingTime = DateTime.now().millis,
|
||||
showWalletInfo = proposalState.dAppSession.proposalNetwork.keys.size > 1,
|
||||
)
|
||||
sessionsManager.saveSession(newSession)
|
||||
analytics.send(
|
||||
WcAnalyticEvents.DAppConnected(
|
||||
sessionProposal = proposalState.dAppSession,
|
||||
|
|
@ -130,7 +133,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
securityStatus = proposalState.dAppSession.securityStatus,
|
||||
),
|
||||
)
|
||||
newSession
|
||||
proposalState.dAppSession.dAppMetaData
|
||||
}.onLeft {
|
||||
analytics.send(
|
||||
WcAnalyticEvents.DAppConnectionFailed(
|
||||
|
|
@ -170,9 +173,10 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private suspend fun walletKitApproveSession(
|
||||
pendingSessionForSave: WcPendingApprovalSessionDTO,
|
||||
sessionForApprove: WcSessionApprove,
|
||||
sdkSessionProposal: Wallet.Model.SessionProposal,
|
||||
): Either<WcPairError, Wallet.Model.SettledSessionResponse.Result> = try {
|
||||
): Either<WcPairError, Unit> = try {
|
||||
val namespaces = caipNamespaceDelegate.associate(
|
||||
sdkSessionProposal,
|
||||
sessionForApprove,
|
||||
|
|
@ -181,7 +185,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
proposerPublicKey = sdkSessionProposal.proposerPublicKey,
|
||||
namespaces = namespaces,
|
||||
)
|
||||
sdkDelegate.approve(sessionApprove)
|
||||
sdkDelegate.approve(pendingSessionForSave, sessionApprove)
|
||||
} catch (e: Throwable) {
|
||||
Timber.tag(WC_TAG).e(e, "Failed to sdk approve session $pairRequest")
|
||||
WcPairError.ApprovalFailed(e.message.orEmpty()).left()
|
||||
|
|
@ -233,6 +237,10 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
},
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val PENDING_SESSION_EXPIRED_DURATION_MIN = 15L
|
||||
}
|
||||
|
||||
private sealed interface TerminalAction {
|
||||
data class Approve(val sessionForApprove: WcSessionApprove) : TerminalAction
|
||||
data object Reject : TerminalAction
|
||||
|
|
|
|||
|
|
@ -6,25 +6,45 @@ import arrow.core.right
|
|||
import com.reown.walletkit.client.Wallet
|
||||
import com.reown.walletkit.client.WalletKit
|
||||
import com.tangem.data.walletconnect.utils.WC_TAG
|
||||
import com.tangem.data.walletconnect.utils.WcScope
|
||||
import com.tangem.data.walletconnect.utils.WcSdkObserver
|
||||
import com.tangem.datasource.local.walletconnect.WalletConnectStore
|
||||
import com.tangem.data.walletconnect.utils.getDappOriginUrl
|
||||
import com.tangem.domain.walletconnect.model.WcPairError
|
||||
import com.tangem.domain.walletconnect.model.WcPairError.ApprovalFailed
|
||||
import com.tangem.domain.walletconnect.model.WcPendingApprovalSessionDTO
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.*
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
internal class WcPairSdkDelegate : WcSdkObserver {
|
||||
internal class WcPairSdkDelegate(
|
||||
private val scope: WcScope,
|
||||
private val store: WalletConnectStore,
|
||||
) : WcSdkObserver {
|
||||
|
||||
private val onSessionProposal = Channel<Pair<Wallet.Model.SessionProposal, Wallet.Model.VerifyContext>>()
|
||||
private val onSdkErrorCallback = Channel<Wallet.Model.Error>()
|
||||
private val onSessionSettleResponse = Channel<Wallet.Model.SettledSessionResponse>()
|
||||
private val onSessionSettleCallback = Channel<Wallet.Model.SettledSessionResponse>()
|
||||
|
||||
init {
|
||||
onSessionSettleCallback.receiveAsFlow()
|
||||
.filterIsInstance<Wallet.Model.SettledSessionResponse.Result>()
|
||||
.buffer()
|
||||
.onEach { settledResponse ->
|
||||
val savedPending = store.pendingApproval.first()
|
||||
val settledSession = settledResponse.session
|
||||
val savedPendingSession = savedPending
|
||||
.find { it.pairingTopic == settledSession.pairingTopic }
|
||||
?: return@onEach
|
||||
store.saveSession(savedPendingSession.session.copy(topic = settledSession.topic))
|
||||
store.removePendingApproval(setOf(savedPendingSession))
|
||||
}
|
||||
.launchIn(scope)
|
||||
}
|
||||
|
||||
suspend fun pair(
|
||||
url: String,
|
||||
|
|
@ -56,43 +76,20 @@ internal class WcPairSdkDelegate : WcSdkObserver {
|
|||
}.first()
|
||||
|
||||
suspend fun approve(
|
||||
pendingSessionForSave: WcPendingApprovalSessionDTO,
|
||||
sessionApprove: Wallet.Params.SessionApprove,
|
||||
): Either<WcPairError, Wallet.Model.SettledSessionResponse.Result> = coroutineScope {
|
||||
val approveCallback = async { withTimeout(CALLBACK_TIMEOUT.seconds) { approveCallback() } }
|
||||
val approveCall = async { sdkApprove(sessionApprove) }
|
||||
approveCall.await()
|
||||
.onLeft {
|
||||
approveCallback.cancel()
|
||||
return@coroutineScope it.left()
|
||||
}
|
||||
return@coroutineScope approveCallback.await().fold(
|
||||
ifLeft = { it.left() },
|
||||
ifRight = { result ->
|
||||
when (result) {
|
||||
is Wallet.Model.SettledSessionResponse.Result -> result.right()
|
||||
is Wallet.Model.SettledSessionResponse.Error -> ApprovalFailed(result.errorMessage).left()
|
||||
}
|
||||
): Either<WcPairError, Unit> = coroutineScope {
|
||||
val forSave = setOf(pendingSessionForSave)
|
||||
store.savePendingApproval(forSave)
|
||||
sdkApprove(sessionApprove).fold(
|
||||
ifRight = { Unit.right() },
|
||||
ifLeft = {
|
||||
store.removePendingApproval(forSave)
|
||||
it.left()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun approveCallback() = callbackFlow<Either<WcPairError, Wallet.Model.SettledSessionResponse>> {
|
||||
// wait first onSessionSettleResponse callback
|
||||
launch {
|
||||
val settledSessionResponse = onSessionSettleResponse.receiveAsFlow().first()
|
||||
trySend(settledSessionResponse.right())
|
||||
channel.close()
|
||||
}
|
||||
// OR
|
||||
// wait first onError callback
|
||||
launch {
|
||||
val error = onSdkErrorCallback.receiveAsFlow().first()
|
||||
trySend(error.throwable.toApproveError())
|
||||
channel.close()
|
||||
}
|
||||
awaitClose()
|
||||
}.first()
|
||||
|
||||
fun rejectSession(proposerPublicKey: String) {
|
||||
Timber.tag(WC_TAG).i("reject session proposerPublicKey = $proposerPublicKey")
|
||||
WalletKit.rejectSession(
|
||||
|
|
@ -120,7 +117,7 @@ internal class WcPairSdkDelegate : WcSdkObserver {
|
|||
|
||||
override fun onSessionSettleResponse(settleSessionResponse: Wallet.Model.SettledSessionResponse) {
|
||||
// Triggered when wallet receives the session settlement response from Dapp
|
||||
onSessionSettleResponse.trySend(settleSessionResponse)
|
||||
onSessionSettleCallback.trySend(settleSessionResponse)
|
||||
}
|
||||
|
||||
private suspend fun sdkApprove(sessionApprove: Wallet.Params.SessionApprove): Either<WcPairError, Unit> {
|
||||
|
|
|
|||
|
|
@ -6,10 +6,7 @@ import arrow.core.right
|
|||
import com.reown.walletkit.client.Wallet
|
||||
import com.reown.walletkit.client.WalletKit
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.walletconnect.utils.WC_TAG
|
||||
import com.tangem.data.walletconnect.utils.WcNetworksConverter
|
||||
import com.tangem.data.walletconnect.utils.WcSdkObserver
|
||||
import com.tangem.data.walletconnect.utils.WcSdkSessionConverter
|
||||
import com.tangem.data.walletconnect.utils.*
|
||||
import com.tangem.datasource.local.walletconnect.WalletConnectStore
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents
|
||||
|
|
@ -18,10 +15,12 @@ import com.tangem.domain.walletconnect.model.WcSessionDTO
|
|||
import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.joda.time.DateTime
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
|
|
@ -32,7 +31,7 @@ internal class DefaultWcSessionsManager(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val wcNetworksConverter: WcNetworksConverter,
|
||||
private val analytics: AnalyticsEventHandler,
|
||||
private val scope: CoroutineScope,
|
||||
private val scope: WcScope,
|
||||
) : WcSessionsManager, WcSdkObserver {
|
||||
|
||||
private val onSessionDelete = Channel<Wallet.Model.SessionDelete>(capacity = Channel.BUFFERED)
|
||||
|
|
@ -55,18 +54,6 @@ internal class DefaultWcSessionsManager(
|
|||
extendSessions()
|
||||
}
|
||||
|
||||
override suspend fun saveSession(session: WcSession) {
|
||||
store.saveSession(
|
||||
WcSessionDTO(
|
||||
topic = session.sdkModel.topic,
|
||||
walletId = session.wallet.walletId,
|
||||
url = session.sdkModel.appMetaData.url,
|
||||
securityStatus = session.securityStatus,
|
||||
connectingTime = session.connectingTime ?: DateTime.now().millis,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun removeSession(session: WcSession): Either<Throwable, Unit> {
|
||||
val topic = session.sdkModel.topic
|
||||
val sdkCall = sdkDisconnectSession(topic)
|
||||
|
|
@ -91,7 +78,16 @@ internal class DefaultWcSessionsManager(
|
|||
inStore: Set<WcSessionDTO>,
|
||||
wallets: List<UserWallet>,
|
||||
): List<WcSession> {
|
||||
val wcSessions = inStore.mapNotNull { storeSession ->
|
||||
// if the WcSdk `onSessionSettleResponse` callback arrives late, merge pending approvals with WcSdk sessions
|
||||
val savedPending = store.pendingApproval.first()
|
||||
.mapNotNullTo(mutableSetOf()) { savedPendingSession ->
|
||||
val sdkSession = inSdk
|
||||
.find { sdkSession -> sdkSession.pairingTopic == savedPendingSession.pairingTopic }
|
||||
?: return@mapNotNullTo null
|
||||
savedPendingSession.session.copy(topic = sdkSession.topic)
|
||||
}
|
||||
|
||||
val wcSessions = savedPending.plus(inStore).mapNotNull { storeSession ->
|
||||
val wallet = wallets.find { it.walletId == storeSession.walletId } ?: return@mapNotNull null
|
||||
val sdkSession = inSdk.find { it.topic == storeSession.topic } ?: return@mapNotNull null
|
||||
val networks = wcNetworksConverter.findWalletNetworks(wallet, sdkSession)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.data.walletconnect.utils
|
||||
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
internal class WcScope(
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
) : CoroutineScope {
|
||||
|
||||
override val coroutineContext: CoroutineContext = SupervisorJob() + dispatchers.io
|
||||
}
|
||||
|
|
@ -16,11 +16,7 @@ import com.tangem.data.walletconnect.pair.WcPairSdkDelegate
|
|||
import com.tangem.data.walletconnect.utils.WcSdkSessionConverter
|
||||
import com.tangem.domain.blockaid.BlockAidVerifier
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
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.model.*
|
||||
import com.tangem.domain.walletconnect.usecase.pair.WcPairState
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerifyOrder
|
||||
|
|
@ -32,7 +28,6 @@ import org.junit.Test
|
|||
|
||||
internal class DefaultWcPairUseCaseTest {
|
||||
|
||||
private val sessionsManager: WcSessionsManager = mockk<WcSessionsManager>()
|
||||
private val associateNetworksDelegate: AssociateNetworksDelegate = mockk<AssociateNetworksDelegate>()
|
||||
private val caipNamespaceDelegate: CaipNamespaceDelegate = mockk<CaipNamespaceDelegate>()
|
||||
private val analytics: AnalyticsEventHandler = mockk<AnalyticsEventHandler>(relaxed = true)
|
||||
|
|
@ -83,11 +78,6 @@ internal class DefaultWcPairUseCaseTest {
|
|||
namespaces = mapOf(),
|
||||
)
|
||||
|
||||
private val sdkApproveSuccess: Wallet.Model.SettledSessionResponse.Result
|
||||
get() = Wallet.Model.SettledSessionResponse.Result(
|
||||
session = sdkSession,
|
||||
)
|
||||
|
||||
private val sdkSession: Wallet.Model.Session
|
||||
get() = Wallet.Model.Session(
|
||||
pairingTopic = "",
|
||||
|
|
@ -115,7 +105,6 @@ internal class DefaultWcPairUseCaseTest {
|
|||
)
|
||||
|
||||
private fun useCaseFactory() = DefaultWcPairUseCase(
|
||||
sessionsManager = sessionsManager,
|
||||
associateNetworksDelegate = associateNetworksDelegate,
|
||||
caipNamespaceDelegate = caipNamespaceDelegate,
|
||||
sdkDelegate = sdkDelegate,
|
||||
|
|
@ -155,12 +144,11 @@ internal class DefaultWcPairUseCaseTest {
|
|||
@Test
|
||||
fun `success pair and approve flow`() = runTest {
|
||||
val approveLoading = WcPairState.Approving.Loading(sessionForApprove)
|
||||
val sessionForSave = sdkSession.sessionForSave
|
||||
val result = WcPairState.Approving.Result(sessionForApprove, sessionForSave.right())
|
||||
val appMetaData = sdkSession.sessionForSave.sdkModel.appMetaData
|
||||
val result = WcPairState.Approving.Result(sessionForApprove, appMetaData.right())
|
||||
|
||||
coEvery { sdkDelegate.pair(url) } returns (sdkProposal to sdkVerifyContext).right()
|
||||
coEvery { sdkDelegate.approve(sdkApprove) } returns sdkApproveSuccess.right()
|
||||
coEvery { sessionsManager.saveSession(any()) } returns Unit
|
||||
coEvery { sdkDelegate.approve(any(), any()) } returns Unit.right()
|
||||
coEvery { blockAidVerifier.verifyDApp(any()) } returns Either.catch { CheckDAppResult.SAFE }
|
||||
|
||||
val useCase = useCaseFactory()
|
||||
|
|
@ -177,8 +165,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
|
||||
assertEquals(approveLoading, awaitItem())
|
||||
coVerifyOrder {
|
||||
sdkDelegate.approve(sdkApprove)
|
||||
sessionsManager.saveSession(any())
|
||||
sdkDelegate.approve(any(), any())
|
||||
}
|
||||
val actual: WcPairState = awaitItem()
|
||||
assert(actual is WcPairState.Approving.Result)
|
||||
|
|
@ -250,7 +237,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
val approveLoading = WcPairState.Approving.Loading(sessionForApprove)
|
||||
val error = WcPairError.ApprovalFailed("error").left()
|
||||
coEvery { sdkDelegate.pair(url) } returns (sdkProposal to sdkVerifyContext).right()
|
||||
coEvery { sdkDelegate.approve(sdkApprove) } returns error
|
||||
coEvery { sdkDelegate.approve(any(), any()) } returns error
|
||||
coEvery { sdkDelegate.rejectSession(sdkApprove.proposerPublicKey) } returns Unit
|
||||
coEvery { blockAidVerifier.verifyDApp(any()) } returns Either.catch { CheckDAppResult.SAFE }
|
||||
|
||||
|
|
@ -269,7 +256,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
|
||||
assertEquals(approveLoading, awaitItem())
|
||||
coVerifyOrder {
|
||||
sdkDelegate.approve(sdkApprove)
|
||||
sdkDelegate.approve(any(), any())
|
||||
sdkDelegate.rejectSession(sdkApprove.proposerPublicKey)
|
||||
}
|
||||
assertEquals(errorResult, awaitItem())
|
||||
|
|
|
|||
|
|
@ -11,4 +11,15 @@ data class WcSessionDTO(
|
|||
val url: String?,
|
||||
val securityStatus: CheckDAppResult = CheckDAppResult.FAILED_TO_VERIFY,
|
||||
val connectingTime: Long? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* keep in mind that [WcSessionDTO.topic] will be empty
|
||||
* you must associate it with SdkSession by [pairingTopic]
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WcPendingApprovalSessionDTO(
|
||||
val pairingTopic: String,
|
||||
val session: WcSessionDTO,
|
||||
val expiredTime: Long,
|
||||
)
|
||||
|
|
@ -7,7 +7,6 @@ import kotlinx.coroutines.flow.Flow
|
|||
|
||||
interface WcSessionsManager {
|
||||
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?
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.domain.walletconnect.usecase.method
|
||||
|
||||
import com.domain.blockaid.models.transaction.CheckTransactionResult
|
||||
import com.domain.blockaid.models.transaction.simultation.TokenInfo
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
|
||||
interface BlockAidTransactionCheck {
|
||||
|
|
@ -13,14 +12,6 @@ interface BlockAidTransactionCheck {
|
|||
|
||||
data class Plain(override val result: CheckTransactionResult) : Result
|
||||
|
||||
data class Approval(
|
||||
override val result: CheckTransactionResult,
|
||||
val approval: WcApproval,
|
||||
val tokenInfo: TokenInfo,
|
||||
val isMutable: Boolean,
|
||||
) : Result {
|
||||
|
||||
suspend fun approvalAmount() = approval.getAmount()
|
||||
}
|
||||
data class Approval(override val result: CheckTransactionResult) : Result
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.walletconnect.usecase.pair
|
|||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.walletconnect.model.*
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface WcPairUseCase {
|
||||
|
|
@ -27,7 +28,7 @@ sealed interface WcPairState {
|
|||
data class Loading(override val session: WcSessionApprove) : Approving
|
||||
data class Result(
|
||||
override val session: WcSessionApprove,
|
||||
val result: Either<WcPairError, WcSession>,
|
||||
val result: Either<WcPairError, WcAppMetaData>,
|
||||
) : Approving
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ import com.tangem.domain.models.wallet.isLocked
|
|||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents
|
||||
import com.tangem.domain.walletconnect.model.*
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData
|
||||
import com.tangem.domain.walletconnect.usecase.pair.WcPairState
|
||||
import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
|
|
@ -96,7 +97,8 @@ internal class WcPairModel @Inject constructor(
|
|||
appInfoUiState.transformerUpdate(
|
||||
WcConnectButtonProgressTransformer(showProgress = false),
|
||||
)
|
||||
pairState.result
|
||||
pairState
|
||||
.result
|
||||
.onLeft(::processError)
|
||||
.onRight(::processSuccessfullyConnected)
|
||||
router.pop()
|
||||
|
|
@ -203,12 +205,12 @@ internal class WcPairModel @Inject constructor(
|
|||
stackNavigation.pushNew(WcAppInfoRoutes.Alert.Verified(appName))
|
||||
}
|
||||
|
||||
private fun processSuccessfullyConnected(session: WcSession) {
|
||||
private fun processSuccessfullyConnected(session: WcAppMetaData) {
|
||||
messageSender.send(
|
||||
SnackbarMessage(
|
||||
message = resourceReference(
|
||||
id = R.string.wc_connected_to,
|
||||
formatArgs = wrappedList(session.sdkModel.appMetaData.name),
|
||||
formatArgs = wrappedList(session.name),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue