diff --git a/data/wallet-connect/build.gradle.kts b/data/wallet-connect/build.gradle.kts index 159348516a..0aa064eccb 100644 --- a/data/wallet-connect/build.gradle.kts +++ b/data/wallet-connect/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { /* Tangem libraries */ implementation(tangemDeps.blockchain) + implementation(tangemDeps.card.core) /* Reown - WalletConnect */ implementation(deps.reownCore) { @@ -47,4 +48,10 @@ dependencies { /* Other */ implementation(deps.kotlin.coroutines) implementation(deps.arrow.core) + + /* Tests */ + testImplementation(deps.test.coroutine) + testImplementation(deps.test.junit) + testImplementation(deps.test.mockk) + testImplementation(deps.test.turbine) } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt index edab0d12af..e36fcd3b18 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt @@ -14,6 +14,7 @@ import com.tangem.data.walletconnect.pair.DefaultWcPairUseCase import com.tangem.data.walletconnect.request.DefaultWcRequestService import com.tangem.data.walletconnect.request.WcMethodHandler import com.tangem.data.walletconnect.respond.DefaultWcRespondService +import com.tangem.data.walletconnect.respond.WcRespondService import com.tangem.data.walletconnect.sessions.DefaultWcSessionsManager import com.tangem.data.walletconnect.utils.WcNamespaceConverter import com.tangem.datasource.di.SdkMoshi @@ -25,7 +26,6 @@ import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsReposit import com.tangem.domain.walletconnect.repository.WalletConnectRepository import com.tangem.domain.walletconnect.repository.WcSessionsManager import com.tangem.domain.walletconnect.request.WcRequestService -import com.tangem.domain.walletconnect.respond.WcRespondService import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase import com.tangem.domain.walletmanager.WalletManagersFacade @@ -131,9 +131,8 @@ internal object WalletConnectDataModule { @Provides @Singleton - fun wcEthNetwork(@SdkMoshi moshi: Moshi, respondService: WcRespondService): WcEthNetwork = WcEthNetwork( + fun wcEthNetwork(@SdkMoshi moshi: Moshi): WcEthNetwork = WcEthNetwork( moshi = moshi, - respondService = respondService, ) @Provides diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt index 69407681e0..ffe057712c 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt @@ -8,10 +8,8 @@ import com.tangem.data.walletconnect.request.WcMethodHandler import com.tangem.data.walletconnect.utils.WcNamespaceConverter import com.tangem.domain.walletconnect.model.WcMethod import com.tangem.domain.walletconnect.model.WcRequest -import com.tangem.domain.walletconnect.respond.WcRespondService import com.tangem.domain.walletconnect.usecase.WcUseCase import com.tangem.domain.walletconnect.usecase.WcUseCasesFlowProvider -import com.tangem.domain.walletconnect.usecase.ethereum.EthPersonalSignUseCase import com.tangem.domain.walletconnect.usecase.ethereum.WcEthMethod import com.tangem.domain.walletconnect.usecase.ethereum.WcEthMethod.SignMessage import kotlinx.coroutines.channels.Channel @@ -19,7 +17,6 @@ import kotlinx.coroutines.flow.receiveAsFlow internal class WcEthNetwork( private val moshi: Moshi, - private val respondService: WcRespondService, ) : WcMethodHandler, WcUseCasesFlowProvider, WcNamespaceConverter { private val _useCases: Channel = Channel(Channel.BUFFERED) @@ -46,7 +43,7 @@ internal class WcEthNetwork( override fun handle(wcRequest: WcRequest) { wcRequest as WcRequest val useCase = when (wcRequest.method) { - is SignMessage -> EthPersonalSignUseCase(wcRequest as WcRequest, respondService) + is SignMessage -> TODO() } _useCases.trySend(useCase) } diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt index 99f06b1f7a..08839fa987 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt @@ -1,13 +1,13 @@ package com.tangem.data.walletconnect.request import com.reown.walletkit.client.Wallet +import com.tangem.data.walletconnect.respond.WcRespondService import com.tangem.data.walletconnect.utils.WcSdkObserver import com.tangem.data.walletconnect.utils.WcSdkSessionRequestConverter import com.tangem.domain.walletconnect.model.WcMethod import com.tangem.domain.walletconnect.model.WcRequest import com.tangem.domain.walletconnect.repository.WcSessionsManager import com.tangem.domain.walletconnect.request.WcRequestService -import com.tangem.domain.walletconnect.respond.WcRespondService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/respond/DefaultWcRespondService.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/respond/DefaultWcRespondService.kt index 7381e9b548..025e9834b2 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/respond/DefaultWcRespondService.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/respond/DefaultWcRespondService.kt @@ -6,7 +6,6 @@ import arrow.core.right import com.reown.walletkit.client.Wallet import com.reown.walletkit.client.WalletKit import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest -import com.tangem.domain.walletconnect.respond.WcRespondService import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume @@ -50,4 +49,19 @@ internal class DefaultWcRespondService : WcRespondService { }, ) } + + override fun rejectRequestNonBlock(request: WcSdkSessionRequest, message: String) { + WalletKit.respondSessionRequest( + params = Wallet.Params.SessionRequestResponse( + sessionTopic = request.topic, + jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcError( + id = request.request.id, + code = 0, + message = message, + ), + ), + onSuccess = {}, + onError = {}, + ) + } } \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/respond/WcRespondService.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/respond/WcRespondService.kt similarity index 72% rename from domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/respond/WcRespondService.kt rename to data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/respond/WcRespondService.kt index c74ce0e017..0eb8948d59 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/respond/WcRespondService.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/respond/WcRespondService.kt @@ -1,4 +1,4 @@ -package com.tangem.domain.walletconnect.respond +package com.tangem.data.walletconnect.respond import arrow.core.Either import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest @@ -6,4 +6,5 @@ import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest interface WcRespondService { suspend fun respond(request: WcSdkSessionRequest, response: String): Either suspend fun rejectRequest(request: WcSdkSessionRequest, message: String = ""): Either + fun rejectRequestNonBlock(request: WcSdkSessionRequest, message: String = "") } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/BaseWcSignUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/BaseWcSignUseCase.kt new file mode 100644 index 0000000000..dd432f1df6 --- /dev/null +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/BaseWcSignUseCase.kt @@ -0,0 +1,65 @@ +package com.tangem.data.walletconnect.sign + +import com.tangem.domain.walletconnect.model.WcSession +import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest +import com.tangem.data.walletconnect.respond.WcRespondService +import com.tangem.domain.walletconnect.usecase.WcMethodUseCase +import com.tangem.domain.walletconnect.usecase.sign.WcSignState +import com.tangem.domain.walletconnect.usecase.sign.WcSignUseCase +import kotlinx.coroutines.flow.FlowCollector + +internal abstract class BaseWcSignUseCase : + WcMethodUseCase, + WcSignUseCase.FinalAction, + FinalActionCollector, + MiddleActionCollector { + + abstract val respondService: WcRespondService + + abstract val context: WcMethodUseCaseContext + override val session: WcSession get() = context.session + override val rawSdkRequest: WcSdkSessionRequest get() = context.rawSdkRequest + + protected val delegate by lazy { + WcSignUseCaseDelegate( + finalActionCollector = this, + middleActionCollector = this, + ) + } + + override val onCancel: suspend (currentState: WcSignState) -> Unit = { + defaultReject() + } + + override fun sign() = delegate.sign() + override fun cancel() = delegate.cancel() + protected fun middleAction(action: MiddleAction) = delegate.middleAction(action) + + protected fun defaultReject() { + respondService.rejectRequestNonBlock(rawSdkRequest) + } +} + +internal interface MiddleActionCollector { + + val onMiddleAction: OnMiddle get() = { _, _ -> } +} + +internal interface FinalActionCollector { + + val onSign: OnSign get() = {} + + val onCancel: OnCancel get() = {} +} + +internal class WcMethodUseCaseContext( + val session: WcSession, + val rawSdkRequest: WcSdkSessionRequest, +) + +internal typealias OnSign = + suspend FlowCollector>.(state: WcSignState) -> Unit +internal typealias OnCancel = + suspend (currentState: WcSignState) -> Unit +internal typealias OnMiddle = + suspend FlowCollector.(currentState: WcSignState, middleAction: MiddleAction) -> Unit \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/SignStateConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/SignStateConverter.kt new file mode 100644 index 0000000000..f77af76789 --- /dev/null +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/SignStateConverter.kt @@ -0,0 +1,28 @@ +package com.tangem.data.walletconnect.sign + +import arrow.core.Either +import com.tangem.domain.walletconnect.usecase.sign.WcSignState +import com.tangem.domain.walletconnect.usecase.sign.WcSignStep + +object SignStateConverter { + + internal fun preSign(signModel: M) = WcSignState(signModel, WcSignStep.PreSign) + internal fun signing(signModel: M) = WcSignState(signModel, WcSignStep.Signing) + internal fun result(result: Either, signModel: M) = + WcSignState(signModel, WcSignStep.Result(result)) + + internal fun WcSignState.toPreSign(signModel: M = this.signModel) = copy( + signModel = signModel, + domainStep = WcSignStep.PreSign, + ) + + internal fun WcSignState.toSigning(signModel: M = this.signModel) = copy( + domainStep = WcSignStep.Signing, + signModel = signModel, + ) + + internal fun WcSignState.toResult(result: Either, signModel: M = this.signModel) = copy( + domainStep = WcSignStep.Result(result), + signModel = signModel, + ) +} \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/WcSignUseCaseDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/WcSignUseCaseDelegate.kt new file mode 100644 index 0000000000..c84bc39ac7 --- /dev/null +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/WcSignUseCaseDelegate.kt @@ -0,0 +1,91 @@ +package com.tangem.data.walletconnect.sign + +import arrow.core.left +import com.tangem.data.walletconnect.sign.SignStateConverter.toPreSign +import com.tangem.data.walletconnect.sign.SignStateConverter.toResult +import com.tangem.data.walletconnect.sign.SignStateConverter.toSigning +import com.tangem.domain.walletconnect.usecase.sign.WcSignState +import com.tangem.domain.walletconnect.usecase.sign.WcSignStep +import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch + +internal class WcSignUseCaseDelegate( + private val finalActionCollector: FinalActionCollector, + private val middleActionCollector: MiddleActionCollector, +) : FinalActionCollector by finalActionCollector, + MiddleActionCollector by middleActionCollector { + + private val middleActionsChannel = Channel() + private val finalActionsChannel = Channel() + + fun cancel() { + finalActionsChannel.trySend(Action.Cancel) + } + + fun sign() { + finalActionsChannel.trySend(Action.Sign) + } + + fun middleAction(action: MiddleAction) { + middleActionsChannel.trySend(action) + } + + operator fun invoke(initModel: SignModel) = channelFlow { + val state = MutableStateFlow(WcSignState(initModel, WcSignStep.PreSign)) + + state + .onEach { newState -> channel.send(newState) } + .launchIn(this) + + fun listenMiddle() = middleActionsChannel.receiveAsFlow() + .buffer() + .transform { middleActions -> this.onMiddleAction(state.value, middleActions) } + .onEach { updatedModel -> state.update { it.toPreSign(updatedModel) } } + .launchIn(this) + + var listenMiddleJob: Job = listenMiddle() + + fun signFlow() = flow { onSign(state.updateAndGet { it.toSigning() }) } + .onEach { newState -> state.update { newState } } + .catch { exception -> + val errorResult = state.value.toResult(exception.left()) + state.update { errorResult } + } + + var signJob: Job? = null + + finalActionsChannel.receiveAsFlow() + .transformLatest { finalAction -> + when (finalAction) { + Action.Cancel -> { + onCancel.invoke(state.value) + channel.close() + } + Action.Sign -> { + val isSigningNow = signJob?.isActive == true + if (isSigningNow) return@transformLatest + listenMiddleJob.cancel() + signJob = launch { + signFlow().collect() + listenMiddleJob = listenMiddle() + } + } + } + } + .launchIn(this) + + /** + * keep flow running to attempt re-signing after an error + * or do something after a successful sign + */ + awaitClose() + } + + sealed interface Action { + data object Cancel : Action + data object Sign : Action + } +} \ No newline at end of file diff --git a/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/WcSignUseCaseDelegateTest.kt b/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/WcSignUseCaseDelegateTest.kt new file mode 100644 index 0000000000..e80bd9a4f8 --- /dev/null +++ b/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/WcSignUseCaseDelegateTest.kt @@ -0,0 +1,254 @@ +package com.tangem.domain.walletconnect + +import app.cash.turbine.test +import arrow.core.left +import arrow.core.right +import com.tangem.data.walletconnect.sign.FinalActionCollector +import com.tangem.data.walletconnect.sign.MiddleActionCollector +import com.tangem.data.walletconnect.sign.SignStateConverter.toResult +import com.tangem.data.walletconnect.sign.SignStateConverter.toSigning +import com.tangem.data.walletconnect.sign.WcSignUseCaseDelegate +import com.tangem.domain.walletconnect.usecase.sign.WcSignState +import com.tangem.domain.walletconnect.usecase.sign.WcSignStep +import io.mockk.every +import io.mockk.mockk +import junit.framework.TestCase.assertEquals +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test + +internal class WcSignUseCaseDelegateTest { + + private val middleActionCollector = mockk>() + private val finalActionCollector = mockk>() + private val useCase = WcSignUseCaseDelegate( + finalActionCollector = finalActionCollector, + middleActionCollector = middleActionCollector, + ) + private val initSignModel = TestSignModel() + + private val initState = WcSignState(initSignModel, WcSignStep.PreSign) + private val signing = initState.toSigning() + private val result = signing.toResult(Unit.right()) + private val testException = RuntimeException("test") + + private val successSign: suspend FlowCollector>.( + currentState: WcSignState, + ) -> Unit = { state -> + delay(2) + emit(state.toResult(Unit.right())) + } + + private val failedSign: suspend FlowCollector>.( + currentState: WcSignState, + ) -> Unit + get() = { state -> + delay(2) + emit(state.toResult(testException.left())) + } + + @Before + fun setup() { + every { middleActionCollector.onMiddleAction } returns { _, _ -> } + every { finalActionCollector.onSign } returns { } + every { finalActionCollector.onCancel } returns { } + } + + @Test + fun `invoke and keep flow running`() = runTest { + every { finalActionCollector.onSign } returns successSign + + useCase.invoke(initSignModel).test { + assertEquals(initState, awaitItem()) + expectNoEvents() + } + } + + @Test + fun `success sign, keep flow running`() = runTest { + every { finalActionCollector.onSign } returns successSign + + useCase.invoke(initModel = initSignModel).test { + assertEquals(initState, awaitItem()) + useCase.sign() + assertEquals(signing, awaitItem()) + assertEquals(result, awaitItem()) + expectNoEvents() + } + } + + @Test + fun `failed sign, keep flow running`() = runTest { + val failedResult = signing.toResult(testException.left()) + every { finalActionCollector.onSign } returns failedSign + + useCase.invoke(initSignModel).test { + assertEquals(initState, awaitItem()) + useCase.sign() + assertEquals(signing, awaitItem()) + assertEquals(failedResult, awaitItem()) + expectNoEvents() + } + } + + @Test + fun `failed sign and catch unknown exception`() = runTest { + val exception = RuntimeException("asd") + val expectedErrorState = signing.toResult(exception.left()) + every { finalActionCollector.onSign } returns { + delay(2) + throw exception + } + + useCase.invoke(initSignModel).test { + assertEquals(initState, awaitItem()) + useCase.sign() + assertEquals(signing, awaitItem()) + assertEquals(expectedErrorState, awaitItem()) + expectNoEvents() + } + } + + @Test + fun `interrupt signing and complete flow on cancel call`() = runTest { + every { finalActionCollector.onSign } returns { + delay(5) + emit(result) + } + + useCase.invoke(initSignModel).test { + assertEquals(initState, awaitItem()) + useCase.sign() + assertEquals(signing, awaitItem()) + delay(2) + useCase.cancel() + awaitComplete() + } + } + + @Test + fun `ignore multi time sign call till signed`() = runTest { + var count = 0 + val startLoading = WcSignState(TestSignModel("startLoading 1"), WcSignStep.Signing) + val startLoading2 = WcSignState(TestSignModel("startLoading 2"), WcSignStep.Signing) + val expectedSignResult = result + + every { finalActionCollector.onSign } returns { + // should emit single time in this test + emit(if (count % 2 == 0) startLoading else startLoading2) + count = count.inc() + delay(10) + emit(expectedSignResult) + } + + useCase.invoke(initSignModel).test { + useCase.sign() + delay(2) + assertEquals(startLoading, expectMostRecentItem()) + + // should ignore + useCase.sign() + delay(2) + expectNoEvents() + + // should ignore + useCase.sign() + expectNoEvents() + + delay(8) + assertEquals(expectedSignResult, expectMostRecentItem()) + expectNoEvents() + } + } + + @Test + fun `ignore middle actions while signing, on failed collect middle actions again`() = runTest { + val firstTextMode = TestSignModel(TestMiddleAction.One().newTestStr) + val firstMiddleUpdate = WcSignState( + signModel = firstTextMode, + domainStep = WcSignStep.PreSign, + ) + val startLoading = firstMiddleUpdate.toSigning() + val failedSign = startLoading.toResult(testException.left()) + val thirdMiddleUpdate = WcSignState( + signModel = TestSignModel(TestMiddleAction.Three().newTestStr), + domainStep = WcSignStep.PreSign, + ) + + every { finalActionCollector.onSign } returns { + delay(6) + emit(failedSign) + } + + every { middleActionCollector.onMiddleAction } returns { currentState, middleAction -> + emit(currentState.signModel.copy(testStr = middleAction.newTestStr)) + } + + useCase.invoke(initSignModel).test { + delay(2) + useCase.middleAction(TestMiddleAction.One()) + assertEquals(firstMiddleUpdate, expectMostRecentItem()) + + useCase.sign() + assertEquals(startLoading, awaitItem()) + + // should ignore + delay(2) + useCase.middleAction(TestMiddleAction.Two()) + + delay(3) + assertEquals(failedSign, awaitItem()) + + // continue listen + delay(2) + useCase.middleAction(TestMiddleAction.Three()) + assertEquals(thirdMiddleUpdate, awaitItem()) + } + } + + @Test + fun `buffered middle actions and drop on sign call`() = runTest { + val firstTextMode = TestSignModel(TestMiddleAction.One().newTestStr) + val expectedFirst = initState.copy(signModel = firstTextMode) + val expectedSecond = initState.copy(signModel = TestSignModel(TestMiddleAction.Two().newTestStr)) + + every { finalActionCollector.onSign } returns successSign + every { middleActionCollector.onMiddleAction } returns { currentState, middleAction -> + emit(currentState.signModel.copy(testStr = middleAction.newTestStr)) + delay(4) + } + + useCase.invoke(initSignModel).test { + assertEquals(initState, awaitItem()) + useCase.middleAction(TestMiddleAction.One()) + useCase.middleAction(TestMiddleAction.Two()) + // must be dropped + useCase.middleAction(TestMiddleAction.Three()) + + // 0 - 4 -> "one" is emitted + // 4 - 8 -> "two" is emitted + // 8 - 12 -> "Signing" is emitted, "three" ignored + delay(2) + assertEquals(expectedFirst, awaitItem()) + delay(4) + assertEquals(expectedSecond, awaitItem()) + + useCase.sign() + delay(4) + assertEquals(expectedSecond.toSigning(), awaitItem()) + cancelAndIgnoreRemainingEvents() + } + } + + internal data class TestSignModel(val testStr: String = "testStr") + + internal sealed interface TestMiddleAction { + val newTestStr: String + + data class One(override val newTestStr: String = "Middle Action One") : TestMiddleAction + data class Two(override val newTestStr: String = "Middle Action Two") : TestMiddleAction + data class Three(override val newTestStr: String = "Middle Action Three") : TestMiddleAction + } +} \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcSimpleSignUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcSimpleSignUseCase.kt deleted file mode 100644 index e0bb5239a4..0000000000 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcSimpleSignUseCase.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.domain.walletconnect.usecase - -import arrow.core.Either -import kotlinx.coroutines.flow.Flow - -interface WcSimpleSignUseCase : WcUseCase { - - fun signFlow(initModel: SignModel): Flow> - - fun action(action: MiddleAction) - - fun cancel() - fun sign(toSign: SignModel) - - sealed interface State { - val model: SignModel - - data class PreSign(override val model: SignModel) : State - data class Signing(override val model: SignModel) : State - data class Result( - val result: Either, - override val model: SignModel, - ) : State - } -} \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcUseCase.kt index a2efe639cd..6ff2c38c73 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcUseCase.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcUseCase.kt @@ -1,3 +1,11 @@ package com.tangem.domain.walletconnect.usecase -interface WcUseCase \ No newline at end of file +import com.tangem.domain.walletconnect.model.WcSession +import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest + +interface WcUseCase + +interface WcMethodUseCase : WcUseCase { + val session: WcSession + val rawSdkRequest: WcSdkSessionRequest +} \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/ethereum/EthPersonalSignUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/ethereum/EthPersonalSignUseCase.kt deleted file mode 100644 index 5e86f84006..0000000000 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/ethereum/EthPersonalSignUseCase.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.tangem.domain.walletconnect.usecase.ethereum - -import arrow.core.Either -import com.tangem.domain.walletconnect.model.WcRequest -import com.tangem.domain.walletconnect.respond.WcRespondService -import com.tangem.domain.walletconnect.usecase.WcUseCase -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.* - -class EthPersonalSignUseCase( - private val wcRequest: WcRequest, - private val respondService: WcRespondService, -) : WcUseCase { - - private val onCallTerminalAction = Channel() - - fun signFlow(): Flow = flow { - val model = SignModel(wcRequest.method.raw) - emit(State.PreSign(model)) - - when (val action = onCallTerminalAction.receiveAsFlow().first()) { - TerminalAction.Cancel -> { - val result = respondService.rejectRequest(wcRequest.rawSdkRequest) - emit(State.Result(result, model)) - } - is TerminalAction.Sign -> { - emit(State.Signing(action.toSign)) - val signed = signAndPrepareForSend() - val result = - if (signed != null) { - respondService.respond(wcRequest.rawSdkRequest, signed) - } else { - respondService.rejectRequest(wcRequest.rawSdkRequest) - } - emit(State.Result(result, model)) - } - } - } - - @Suppress("FunctionOnlyReturningConstant") // todo(wc) remove later - private suspend fun signAndPrepareForSend(): String? { - return null // todo(wc) - } - - fun sign(toSign: SignModel) { - onCallTerminalAction.trySend(TerminalAction.Sign(toSign)) - } - - fun cancel() { - onCallTerminalAction.trySend(TerminalAction.Cancel) - } - - sealed interface TerminalAction { - data class Sign(val toSign: SignModel) : TerminalAction - data object Cancel : TerminalAction - } - - sealed interface State { - val model: SignModel - - data class PreSign(override val model: SignModel) : State - data class Signing(override val model: SignModel) : State - data class Result( - val result: Either, - override val model: SignModel, - ) : State - } - - data class SignModel( - val raw: List, - ) -} \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/sign/WcSignState.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/sign/WcSignState.kt new file mode 100644 index 0000000000..047148fade --- /dev/null +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/sign/WcSignState.kt @@ -0,0 +1,14 @@ +package com.tangem.domain.walletconnect.usecase.sign + +import arrow.core.Either + +data class WcSignState( + val signModel: SignModel, + val domainStep: WcSignStep, +) + +sealed interface WcSignStep { + data object PreSign : WcSignStep + data object Signing : WcSignStep + data class Result(val result: Either) : WcSignStep +} \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/sign/WcSignUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/sign/WcSignUseCase.kt new file mode 100644 index 0000000000..a0db5aa0e7 --- /dev/null +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/sign/WcSignUseCase.kt @@ -0,0 +1,20 @@ +package com.tangem.domain.walletconnect.usecase.sign + +import com.tangem.domain.walletconnect.usecase.WcMethodUseCase +import kotlinx.coroutines.flow.Flow + +interface WcSignUseCase : WcMethodUseCase { + + interface FinalAction { + fun cancel() + fun sign() + } + + interface SimpleRun { + operator fun invoke(): Flow> + } + + interface ArgsRun { + operator fun invoke(args: Args): Flow> + } +} \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 8fbee2f5e1..3f1defd17d 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -102,6 +102,7 @@ espresso-intents = "3.5.1" junit = "4.13.2" junitAndroidExt = "1.1.5" mockk = "1.13.4" +turbine = "1.2.0" truth = "1.1.3" ultron-android = "2.5.4" ultron-compose = "2.5.4" @@ -199,6 +200,7 @@ test-junit = { module = "junit:junit", version.ref = "junit" } test-junit-android = { module = "androidx.test.ext:junit", version.ref = "junitAndroidExt" } test-truth = { module = "com.google.truth:truth", version.ref = "truth" } test-mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +test-turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } test-ultron-android = { module = "com.atiurin:ultron-android", version.ref = "ultron-android" } test-ultron-compose = { module = "com.atiurin:ultron-compose", version.ref = "ultron-compose" } test-ultron-allure = { module = "com.atiurin:ultron-allure", version.ref = "ultron-allure" }