Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-15 16:31:16 +03:00
commit 5ee276974f
16 changed files with 182 additions and 131 deletions

View file

@ -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,
),
)
}

View file

@ -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) }
}
}

View file

@ -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>
}

View file

@ -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

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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

View file

@ -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> {

View file

@ -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)

View file

@ -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
}

View file

@ -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())

View file

@ -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,
)

View file

@ -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?
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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),
),
),
)