From 0594e1b680642e643aa1046fe8560520c7c8089c Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 19 Dec 2025 17:25:04 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../sdk/impl/DefaultTangemSdkManager.kt | 33 ++++- .../domain/sdk/impl/MockTangemSdkManager.kt | 14 +- ...mPayGenerateAddressAndSignChallengeTask.kt | 15 +- .../visa/TangemPaySignWithdrawalHashTask.kt | 15 +- .../visa/VisaCustomerWalletApproveTask.kt | 80 ++--------- data/visa/build.gradle.kts | 2 + .../DefaultTangemPayAuthDataSource.kt | 34 ++--- .../pay/datasource/TangemPayHotSdkManager.kt | 130 ++++++++++++++++++ .../tangem/data/pay/di/TangemPayDataModule.kt | 3 +- .../repository/DefaultOnboardingRepository.kt | 15 +- .../DefaultTangemPaySwapRepository.kt | 36 ++--- .../DefaultTangemPayWithdrawUseCase.kt | 6 +- .../visa/DefaultTangemPayRemoteDataSource.kt | 3 +- .../ethereum/WcEthMessageSignUseCase.kt | 1 + .../domain/card/common/visa/VisaUtilities.kt | 35 ++++- .../domain/pay}/WithdrawalSignatureResult.kt | 2 +- .../pay/datasource/TangemPayAuthDataSource.kt | 10 +- .../pay/repository/TangemPaySwapRepository.kt | 4 +- .../TangemPayMainScreenCustomerInfoUseCase.kt | 23 ++-- .../tangempay/TangemPayWithdrawUseCase.kt | 4 +- .../tangem/feature/swap/model/SwapModel.kt | 2 +- .../model/TangemPayOnboardingModel.kt | 33 ++--- .../com/tangem/sdk/api/TangemSdkManager.kt | 6 +- 23 files changed, 298 insertions(+), 208 deletions(-) create mode 100644 data/visa/src/main/kotlin/com/tangem/data/pay/datasource/TangemPayHotSdkManager.kt rename domain/visa/{src/main/kotlin/com/tangem/domain/pay/model => models/src/main/kotlin/com/tangem/domain/pay}/WithdrawalSignatureResult.kt (83%) diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt index a303cd8df9..981b99ecf8 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt @@ -3,6 +3,9 @@ package com.tangem.tap.domain.sdk.impl import android.content.res.Resources import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import arrow.core.Either +import arrow.core.left +import arrow.core.right import com.tangem.Log import com.tangem.Message import com.tangem.TangemSdk @@ -24,6 +27,7 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.WithdrawalSignatureResult import com.tangem.domain.visa.model.* import com.tangem.domain.wallets.derivations.derivationStyleProvider import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles @@ -516,23 +520,44 @@ internal class DefaultTangemSdkManager( override suspend fun tangemPayProduceInitialCredentials( cardId: String, - ): CompletionResult { + ): Either { return coroutineScope { - runTaskAsyncReturnOnMain( + val result = runTaskAsyncReturnOnMain( runnable = tangemPayChallengeTaskFactory.create(coroutineScope = this), cardId = cardId, initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)), ) + + return@coroutineScope when (result) { + is CompletionResult.Failure<*> -> result.error.left() + is CompletionResult.Success -> result.data.right() + } } } - override suspend fun getWithdrawalSignature(cardId: String, hash: String): CompletionResult { + override suspend fun getWithdrawalSignature( + cardId: String, + hash: String, + ): Either { return coroutineScope { - runTaskAsyncReturnOnMain( + val result = runTaskAsyncReturnOnMain( runnable = TangemPaySignWithdrawalHashTask(cardId = cardId, hash = hash.hexToBytes()), cardId = cardId, initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)), ) + + return@coroutineScope when (result) { + is CompletionResult.Failure<*> -> { + if (result.error is TangemSdkError.UserCancelled) { + WithdrawalSignatureResult.Cancelled.right() + } else { + result.error.left() + } + } + is CompletionResult.Success -> { + WithdrawalSignatureResult.Success(result.data).right() + } + } } } // endregion diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt index 91fe1414d6..54e9e33aca 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt @@ -3,6 +3,7 @@ package com.tangem.tap.domain.sdk.impl import android.content.res.Resources import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import arrow.core.Either import com.tangem.Message import com.tangem.common.CompletionResult import com.tangem.common.KeyPair @@ -18,7 +19,11 @@ import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.visa.model.* +import com.tangem.domain.pay.WithdrawalSignatureResult +import com.tangem.domain.visa.model.TangemPayInitialCredentials +import com.tangem.domain.visa.model.VisaActivationInput +import com.tangem.domain.visa.model.VisaDataForApprove +import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.preflightread.PreflightReadFilter import com.tangem.operations.wallet.CreateWalletResponse @@ -215,11 +220,14 @@ class MockTangemSdkManager( override suspend fun tangemPayProduceInitialCredentials( cardId: String, - ): CompletionResult { + ): Either { error("Not implemented") } - override suspend fun getWithdrawalSignature(cardId: String, hash: String): CompletionResult { + override suspend fun getWithdrawalSignature( + cardId: String, + hash: String, + ): Either { error("Not implemented") } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/visa/TangemPayGenerateAddressAndSignChallengeTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/TangemPayGenerateAddressAndSignChallengeTask.kt index bc05e53d63..4550d3dc52 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/TangemPayGenerateAddressAndSignChallengeTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/TangemPayGenerateAddressAndSignChallengeTask.kt @@ -3,7 +3,6 @@ package com.tangem.tap.domain.tasks.visa import arrow.core.getOrElse import com.tangem.common.CompletionResult import com.tangem.common.card.CardWallet -import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.CompletionCallback @@ -42,12 +41,14 @@ class TangemPayGenerateAddressAndSignChallengeTask @AssistedInject constructor( private suspend fun runSuspend(session: CardSession): CompletionResult { val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) - val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } + val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.curve } ?: return CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError) val address = when (val derivationResult = runDerivationTask(session, wallet)) { is CompletionResult.Failure<*> -> return CompletionResult.Failure(derivationResult.error) - is CompletionResult.Success -> generateAddressFromExtendedKey(derivationResult.data) + is CompletionResult.Success -> VisaUtilities.generateAddressFromExtendedKey( + extendedPublicKey = derivationResult.data, + ) } val userWalletId = UserWalletIdBuilder.walletPublicKey(wallet.publicKey) @@ -119,14 +120,6 @@ class TangemPayGenerateAddressAndSignChallengeTask @AssistedInject constructor( return deferred.await() } - private fun generateAddressFromExtendedKey(extendedPublicKey: ExtendedPublicKey): String { - val derivationData = VisaUtilities.visaBlockchain.makeAddressesFromExtendedPublicKey( - extendedPublicKey = extendedPublicKey, - cachedIndex = null, - ) - return derivationData.address - } - @AssistedFactory interface Factory { fun create(coroutineScope: CoroutineScope): TangemPayGenerateAddressAndSignChallengeTask diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/visa/TangemPaySignWithdrawalHashTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/TangemPaySignWithdrawalHashTask.kt index d5bc3b64c1..2cf1b0f42c 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/TangemPaySignWithdrawalHashTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/TangemPaySignWithdrawalHashTask.kt @@ -1,15 +1,11 @@ package com.tangem.tap.domain.tasks.visa -import com.tangem.blockchain.common.UnmarshalHelper import com.tangem.common.CompletionResult import com.tangem.common.card.Card -import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.CompletionCallback import com.tangem.common.core.TangemSdkError -import com.tangem.common.extensions.toDecompressedPublicKey -import com.tangem.common.extensions.toHexString import com.tangem.core.error.ext.tangemError import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey @@ -40,7 +36,7 @@ class TangemPaySignWithdrawalHashTask( private fun proceedSign(card: Card, session: CardSession, callback: CompletionCallback) { val derivationPath = VisaUtilities.customDerivationPath - val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run { + val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.curve } ?: run { callback(CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError)) return } @@ -71,7 +67,7 @@ class TangemPaySignWithdrawalHashTask( private fun signData( targetWalletPublicKey: ByteArray, derivationPath: DerivationPath?, - extendedPublicKey: ExtendedPublicKey?, + extendedPublicKey: ExtendedPublicKey, session: CardSession, callback: CompletionCallback, ) { @@ -84,12 +80,11 @@ class TangemPaySignWithdrawalHashTask( signTask.run(session) { result -> when (result) { is CompletionResult.Success -> { - val rsvSignature = UnmarshalHelper.unmarshalSignatureExtended( + val rsvSignature = VisaUtilities.unmarshallSignature( signature = result.data.signature, hash = hash, - publicKey = extendedPublicKey?.publicKey?.toDecompressedPublicKey() - ?: targetWalletPublicKey.toDecompressedPublicKey(), - ).asRSVLegacyEVM().toHexString().lowercase() + extendedPublicKey = extendedPublicKey, + ) callback(CompletionResult.Success(rsvSignature)) } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt index 3439d72093..d4c6dc4800 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt @@ -1,28 +1,19 @@ package com.tangem.tap.domain.tasks.visa -import arrow.core.getOrElse -import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.toKeccak -import com.tangem.blockchain.common.UnmarshalHelper import com.tangem.common.CompletionResult import com.tangem.common.card.Card import com.tangem.common.card.CardWallet -import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.CompletionCallback import com.tangem.common.core.TangemSdkError -import com.tangem.common.extensions.toDecompressedPublicKey -import com.tangem.common.extensions.toHexString import com.tangem.core.error.ext.tangemError import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.domain.card.common.visa.VisaUtilities import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility -import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDerivation -import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.visa.error.VisaActivationError import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet -import com.tangem.operations.ScanTask import com.tangem.operations.derivation.DeriveWalletPublicKeyTask import com.tangem.operations.sign.SignHashCommand @@ -46,11 +37,7 @@ class VisaCustomerWalletApproveTask( return } - if (card.settings.isHDWalletAllowed) { - proceedApprove(card, session, callback) - } else { - proceedApproveWithLegacyCard(card, session, callback) - } + proceedApprove(card, session, callback) } private fun proceedApprove( @@ -60,7 +47,7 @@ class VisaCustomerWalletApproveTask( ) { val derivationPath = VisaUtilities.customDerivationPath - val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run { + val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.curve } ?: run { callback(CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError)) return } @@ -114,43 +101,15 @@ class VisaCustomerWalletApproveTask( ) } - private fun proceedApproveWithLegacyCard( - card: Card, - session: CardSession, - callback: CompletionCallback, - ) { - val publicKey = findKeyWithoutDerivation( - targetAddress = visaDataForApprove.targetAddress, - card = CardDTO(card), - ).getOrElse { error -> - callback(CompletionResult.Failure(error.tangemError)) - return - } - - signApproveData( - targetWalletPublicKey = publicKey, - derivationPath = null, - extendedPublicKey = null, - session = session, - callback = callback, - ) - } - - // TODO: [REDACTED_TASK_KEY] - Get this public function from Blockchain SDK - private fun hashPersonalMessage(message: ByteArray): ByteArray { - val prefix = "\u0019Ethereum Signed Message:\n${message.size}".toByteArray() - return (prefix + message).toKeccak() - } - private fun signApproveData( targetWalletPublicKey: ByteArray, derivationPath: DerivationPath?, - extendedPublicKey: ExtendedPublicKey?, + extendedPublicKey: ExtendedPublicKey, session: CardSession, callback: CompletionCallback, ) { - val content = "Tangem Pay wants to sign in with your account. Nonce: ${visaDataForApprove.hashToSign}" - val hash = hashPersonalMessage(content.toByteArray(Charsets.UTF_8)) + val content = VisaUtilities.signWithNonceMessage(visaDataForApprove.hashToSign) + val hash = VisaUtilities.hashPersonalMessage(content.toByteArray(Charsets.UTF_8)) val signTask = SignHashCommand( hash = hash, @@ -161,36 +120,13 @@ class VisaCustomerWalletApproveTask( signTask.run(session) { result -> when (result) { is CompletionResult.Success -> { - val rsvSignature = UnmarshalHelper.unmarshalSignatureExtended( + val rsvSignature = VisaUtilities.unmarshallSignature( signature = result.data.signature, hash = hash, - publicKey = extendedPublicKey?.publicKey?.toDecompressedPublicKey() - ?: targetWalletPublicKey.toDecompressedPublicKey(), - ).asRSVLegacyEVM().toHexString().lowercase() - - scanCard( - session = session, - callback = callback, - signedData = visaDataForApprove.sign(rsvSignature, visaDataForApprove.targetAddress), + extendedPublicKey = extendedPublicKey, ) - } - is CompletionResult.Failure -> { - callback(CompletionResult.Failure(result.error)) - } - } - } - } - private fun scanCard( - signedData: VisaSignedDataByCustomerWallet, - session: CardSession, - callback: CompletionCallback, - ) { - val scanTask = ScanTask() - scanTask.run(session) { result -> - when (result) { - is CompletionResult.Success -> { - callback(CompletionResult.Success(signedData)) + visaDataForApprove.sign(rsvSignature, visaDataForApprove.targetAddress) } is CompletionResult.Failure -> { callback(CompletionResult.Failure(result.error)) diff --git a/data/visa/build.gradle.kts b/data/visa/build.gradle.kts index 1470c2b5d9..f893a530d6 100644 --- a/data/visa/build.gradle.kts +++ b/data/visa/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { /** Project - Data */ implementation(projects.core.datasource) implementation(projects.core.error) + implementation(projects.core.error.ext) implementation(projects.core.security) implementation(projects.data.common) @@ -60,6 +61,7 @@ dependencies { /** Libs - Tangem */ implementation(tangemDeps.blockchain) implementation(tangemDeps.card.core) + implementation(tangemDeps.hot.core) implementation(projects.libs.tangemSdkApi) /** DI */ diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/datasource/DefaultTangemPayAuthDataSource.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/datasource/DefaultTangemPayAuthDataSource.kt index 9eea6d7429..6af402c591 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/datasource/DefaultTangemPayAuthDataSource.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/datasource/DefaultTangemPayAuthDataSource.kt @@ -1,42 +1,34 @@ package com.tangem.data.pay.datasource import arrow.core.Either -import arrow.core.left -import arrow.core.right -import com.tangem.common.CompletionResult -import com.tangem.common.core.TangemSdkError +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.pay.WithdrawalSignatureResult import com.tangem.domain.pay.datasource.TangemPayAuthDataSource -import com.tangem.domain.pay.model.WithdrawalSignatureResult import com.tangem.domain.visa.model.TangemPayInitialCredentials import com.tangem.sdk.api.TangemSdkManager import javax.inject.Inject internal class DefaultTangemPayAuthDataSource @Inject constructor( private val tangemSdkManager: TangemSdkManager, + private val tangemPayHotSdkManager: TangemPayHotSdkManager, ) : TangemPayAuthDataSource { - override suspend fun produceInitialCredentials(cardId: String): Either { - return when (val initialCredentials = tangemSdkManager.tangemPayProduceInitialCredentials(cardId = cardId)) { - is CompletionResult.Failure<*> -> initialCredentials.error.left() - is CompletionResult.Success -> initialCredentials.data.right() + override suspend fun produceInitialCredentials( + userWallet: UserWallet, + ): Either { + return when (userWallet) { + is UserWallet.Cold -> tangemSdkManager.tangemPayProduceInitialCredentials(cardId = userWallet.cardId) + is UserWallet.Hot -> tangemPayHotSdkManager.produceInitialCredentials(userWallet) } } override suspend fun getWithdrawalSignature( - cardId: String, + userWallet: UserWallet, hash: String, ): Either { - return when (val signResult = tangemSdkManager.getWithdrawalSignature(cardId, hash)) { - is CompletionResult.Failure<*> -> { - if (signResult.error is TangemSdkError.UserCancelled) { - WithdrawalSignatureResult.Cancelled.right() - } else { - signResult.error.left() - } - } - is CompletionResult.Success -> { - WithdrawalSignatureResult.Success(signResult.data).right() - } + return when (userWallet) { + is UserWallet.Cold -> tangemSdkManager.getWithdrawalSignature(cardId = userWallet.cardId, hash = hash) + is UserWallet.Hot -> tangemPayHotSdkManager.getWithdrawalSignature(hotWallet = userWallet, hash = hash) } } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/datasource/TangemPayHotSdkManager.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/datasource/TangemPayHotSdkManager.kt new file mode 100644 index 0000000000..6c2eac2122 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/datasource/TangemPayHotSdkManager.kt @@ -0,0 +1,130 @@ +package com.tangem.data.pay.datasource + +import arrow.core.Either +import arrow.core.getOrElse +import arrow.core.raise.Raise +import arrow.core.raise.either +import com.tangem.common.extensions.hexToBytes +import com.tangem.core.error.ext.tangemError +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.domain.card.common.visa.VisaUtilities +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.pay.WithdrawalSignatureResult +import com.tangem.domain.visa.datasource.TangemPayRemoteDataSource +import com.tangem.domain.visa.error.VisaActivationError +import com.tangem.domain.visa.error.VisaCardScanError +import com.tangem.domain.visa.model.TangemPayInitialCredentials +import com.tangem.domain.wallets.hot.HotWalletAccessor +import com.tangem.hot.sdk.TangemHotSdk +import com.tangem.hot.sdk.model.DataToSign +import com.tangem.hot.sdk.model.DeriveWalletRequest +import com.tangem.hot.sdk.model.UnlockHotWallet +import javax.inject.Inject + +internal class TangemPayHotSdkManager @Inject constructor( + private val hotWalletAccessor: HotWalletAccessor, + private val tangemHotSdk: TangemHotSdk, + private val tangemPayAuthRemoteDataSource: TangemPayRemoteDataSource, +) { + + suspend fun produceInitialCredentials(hotWallet: UserWallet.Hot): Either = + withUnlockedHotWallet(hotWallet) { unlockHotWallet -> + val extendedPublicKey = getExtendedPublicKey(unlockHotWallet = unlockHotWallet) + val address = VisaUtilities.generateAddressFromExtendedKey(extendedPublicKey) + val challenge = tangemPayAuthRemoteDataSource.getCustomerWalletAuthChallenge( + customerWalletAddress = address, + customerWalletId = hotWallet.walletId.stringValue, + ).getOrElse { raise(it.tangemError) } + + val content = VisaUtilities.signWithNonceMessage(challenge.challenge) + val hash = VisaUtilities.hashPersonalMessage(content.toByteArray(Charsets.UTF_8)) + val signature = getSignature( + unlockHotWallet = unlockHotWallet, + hash = hash, + extendedPublicKey = extendedPublicKey, + ) + + val authTokens = tangemPayAuthRemoteDataSource.getTokenWithCustomerWallet( + sessionId = challenge.session.sessionId, + signature = signature, + nonce = challenge.challenge, + ).getOrElse { raise(VisaActivationError.FailedRemoteState.tangemError) } + + TangemPayInitialCredentials( + customerWalletAddress = address, + authTokens = authTokens, + ) + } + + suspend fun getWithdrawalSignature( + hotWallet: UserWallet.Hot, + hash: String, + ): Either = withUnlockedHotWallet(hotWallet) { unlockHotWallet -> + val signature = getSignature( + unlockHotWallet = unlockHotWallet, + hash = hash.hexToBytes(), + extendedPublicKey = getExtendedPublicKey(unlockHotWallet = unlockHotWallet), + ) + + WithdrawalSignatureResult.Success(signature) + } + + private suspend fun Raise.getExtendedPublicKey(unlockHotWallet: UnlockHotWallet): ExtendedPublicKey { + val publicKeyResponse = tangemHotSdk.derivePublicKey( + unlockHotWallet = unlockHotWallet, + request = DeriveWalletRequest( + requests = listOf( + DeriveWalletRequest.Request( + curve = VisaUtilities.curve, + paths = listOf(VisaUtilities.customDerivationPath), + ), + ), + ), + ) + return publicKeyResponse.responses + .firstOrNull { it.curve == VisaUtilities.curve } + ?.publicKeys[VisaUtilities.customDerivationPath] + ?: raise(VisaActivationError.MissingWallet.tangemError) + } + + private suspend fun Raise.getSignature( + unlockHotWallet: UnlockHotWallet, + hash: ByteArray, + extendedPublicKey: ExtendedPublicKey, + ): String { + val signedHashes = tangemHotSdk.signHashes( + unlockHotWallet = unlockHotWallet, + dataToSign = listOf( + DataToSign( + curve = VisaUtilities.curve, + derivationPath = VisaUtilities.customDerivationPath, + hashes = listOf(hash), + ), + ), + ) + val signature = signedHashes + .firstOrNull { it.curve == VisaUtilities.curve } + ?.signatures + ?.firstOrNull() + ?: raise(VisaCardScanError.FailedToSignChallenge.tangemError) + + return VisaUtilities.unmarshallSignature( + signature = signature, + hash = hash, + extendedPublicKey = extendedPublicKey, + ) + } + + private suspend inline fun withUnlockedHotWallet( + hotWallet: UserWallet.Hot, + block: Raise.(UnlockHotWallet) -> T, + ): Either = either { + try { + val unlockHotWallet = hotWalletAccessor.getContextualUnlock(hotWallet.hotWalletId) + ?: hotWalletAccessor.unlockContextual(hotWallet.hotWalletId) + block(unlockHotWallet) + } finally { + hotWalletAccessor.clearContextualUnlock(hotWallet.hotWalletId) + } + } +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt index ffcd79fb85..719542728a 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt @@ -80,9 +80,8 @@ internal interface TangemPayDataModule { deviceSecurity: DeviceSecurityInfoProvider, ): TangemPayMainScreenCustomerInfoUseCase { return TangemPayMainScreenCustomerInfoUseCase( - repository = repository, + onboardingRepository = repository, customerOrderRepository = customerOrderRepository, - tangemPayOnboardingRepository = tangemPayOnboardingRepository, eligibilityManager = eligibilityManager, deviceSecurity = deviceSecurity, ) diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt index c3ab4f66c0..348ae72b3b 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt @@ -65,15 +65,16 @@ internal class DefaultOnboardingRepository @Inject constructor( override suspend fun produceInitialData(userWalletId: UserWalletId) { withContext(dispatcherProvider.io) { - val initialCredentials = authDataSource.produceInitialCredentials(cardId = getCardId(userWalletId)) + val userWallet = getUserWallet(userWalletId) + val initialCredentials = authDataSource.produceInitialCredentials(userWallet) .fold( ifLeft = { error -> error("Can not produce initial data: ${error.message}") }, ifRight = { it }, ) // should storeCheckCustomerWalletResult because we already know this - tangemPayStorage.storeCheckCustomerWalletResult(userWalletId, true) + tangemPayStorage.storeCheckCustomerWalletResult(userWallet.walletId, true) tangemPayStorage.storeCustomerWalletAddress( - userWalletId = userWalletId, + userWalletId = userWallet.walletId, customerWalletAddress = initialCredentials.customerWalletAddress, ) tangemPayStorage.storeAuthTokens( @@ -120,17 +121,13 @@ internal class DefaultOnboardingRepository @Inject constructor( } } - private fun getCardId(userWalletId: UserWalletId): String { + private fun getUserWallet(userWalletId: UserWalletId): UserWallet { val userWallet = if (hotWalletFeatureToggles.isHotWalletEnabled) { userWalletsListRepository.userWallets.value?.firstOrNull { it.walletId == userWalletId } } else { userWalletsListManager.userWalletsSync.firstOrNull { it.walletId == userWalletId } } ?: error("no userWallet found") - return if (userWallet is UserWallet.Cold) { - userWallet.cardId - } else { - TODO("[REDACTED_JIRA]") - } + return userWallet } private suspend fun getCustomerInfo( diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPaySwapRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPaySwapRepository.kt index 042686b6e7..6bc1774f28 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPaySwapRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPaySwapRepository.kt @@ -1,23 +1,20 @@ package com.tangem.data.pay.repository import arrow.core.Either +import arrow.core.left import com.tangem.core.error.UniversalError import com.tangem.data.common.quote.QuotesFetcher import com.tangem.datasource.api.pay.TangemPayApi import com.tangem.datasource.api.pay.models.request.WithdrawDataRequest import com.tangem.datasource.api.pay.models.request.WithdrawRequest import com.tangem.datasource.local.visa.TangemPayStorage -import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.pay.WithdrawalResult +import com.tangem.domain.pay.WithdrawalSignatureResult import com.tangem.domain.pay.datasource.TangemPayAuthDataSource -import com.tangem.domain.pay.model.WithdrawalSignatureResult import com.tangem.domain.pay.repository.TangemPaySwapRepository import com.tangem.domain.visa.error.VisaApiError -import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.features.hotwallet.HotWalletFeatureToggles import com.tangem.utils.extensions.addHexPrefix import java.math.BigDecimal import java.math.RoundingMode @@ -30,29 +27,25 @@ internal class DefaultTangemPaySwapRepository @Inject constructor( private val tangemPayApi: TangemPayApi, private val requestHelper: TangemPayRequestPerformer, private val authDataSource: TangemPayAuthDataSource, - private val userWalletsListManager: UserWalletsListManager, - private val userWalletsListRepository: UserWalletsListRepository, - private val hotWalletFeatureToggles: HotWalletFeatureToggles, private val quotesFetcher: QuotesFetcher, private val tangemPayStorage: TangemPayStorage, ) : TangemPaySwapRepository { override suspend fun withdraw( - userWalletId: UserWalletId, + userWallet: UserWallet, receiverAddress: String, cryptoAmount: BigDecimal, cryptoCurrencyId: CryptoCurrency.RawID, ): Either { val amountInCents = getAmountInCents(cryptoAmount, cryptoCurrencyId) if (amountInCents.isNullOrEmpty()) return Either.Left(VisaApiError.WithdrawalDataError) - return requestHelper.performRequest(userWalletId) { authHeader -> + return requestHelper.performRequest(userWallet.walletId) { authHeader -> val request = WithdrawDataRequest(amountInCents = amountInCents, recipientAddress = receiverAddress) tangemPayApi.getWithdrawData(authHeader = authHeader, body = request) }.map { data -> - val result = data.result - if (result == null) return Either.Left(VisaApiError.WithdrawalDataError) + val result = data.result ?: return VisaApiError.WithdrawalDataError.left() val signatureResult = authDataSource.getWithdrawalSignature( - cardId = getCardId(userWalletId), + userWallet = userWallet, hash = result.hash, ).getOrNull() @@ -61,7 +54,7 @@ internal class DefaultTangemPaySwapRepository @Inject constructor( Either.Right(WithdrawalResult.Cancelled) } is WithdrawalSignatureResult.Success -> { - requestHelper.performRequest(userWalletId) { authHeader -> + requestHelper.performRequest(userWallet.walletId) { authHeader -> val request = WithdrawRequest( amountInCents = amountInCents, recipientAddress = receiverAddress, @@ -74,7 +67,7 @@ internal class DefaultTangemPaySwapRepository @Inject constructor( .mapLeft { return Either.Left(VisaApiError.WithdrawError) } .map { response -> val orderId = response.result?.orderId - if (orderId != null) tangemPayStorage.storeWithdrawOrder(userWalletId, orderId) + if (orderId != null) tangemPayStorage.storeWithdrawOrder(userWallet.walletId, orderId) WithdrawalResult.Success } } @@ -103,17 +96,4 @@ internal class DefaultTangemPaySwapRepository @Inject constructor( return quotes?.quotes[cryptoCurrencyId.value]?.price } - - private fun getCardId(userWalletId: UserWalletId): String { - val userWallet = if (hotWalletFeatureToggles.isHotWalletEnabled) { - userWalletsListRepository.userWallets.value?.firstOrNull { it.walletId == userWalletId } - } else { - userWalletsListManager.userWalletsSync.firstOrNull { it.walletId == userWalletId } - } ?: error("No User Wallet found") - return if (userWallet is UserWallet.Cold) { - userWallet.cardId - } else { - TODO("[REDACTED_JIRA]") - } - } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/usecase/DefaultTangemPayWithdrawUseCase.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/usecase/DefaultTangemPayWithdrawUseCase.kt index 3ccde5d629..36b85985ad 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/usecase/DefaultTangemPayWithdrawUseCase.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/usecase/DefaultTangemPayWithdrawUseCase.kt @@ -3,7 +3,7 @@ package com.tangem.data.pay.usecase import arrow.core.Either import com.tangem.core.error.UniversalError import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.pay.WithdrawalResult import com.tangem.domain.pay.repository.TangemPaySwapRepository import com.tangem.domain.tangempay.TangemPayWithdrawUseCase @@ -15,13 +15,13 @@ internal class DefaultTangemPayWithdrawUseCase @Inject constructor( ) : TangemPayWithdrawUseCase { override suspend fun invoke( - userWalletId: UserWalletId, + userWallet: UserWallet, cryptoAmount: BigDecimal, cryptoCurrencyId: CryptoCurrency.RawID, receiverCexAddress: String, ): Either { return repository.withdraw( - userWalletId = userWalletId, + userWallet = userWallet, cryptoAmount = cryptoAmount, receiverAddress = receiverCexAddress, cryptoCurrencyId = cryptoCurrencyId, diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultTangemPayRemoteDataSource.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultTangemPayRemoteDataSource.kt index c2a927964b..63d2853352 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultTangemPayRemoteDataSource.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultTangemPayRemoteDataSource.kt @@ -9,6 +9,7 @@ import com.tangem.datasource.api.pay.models.request.GenerateNonceByCustomerWalle import com.tangem.datasource.api.pay.models.request.GetTokenByCustomerWalletRequest import com.tangem.datasource.api.pay.models.response.TangemPayErrorResponse import com.tangem.datasource.di.NetworkMoshi +import com.tangem.domain.card.common.visa.VisaUtilities import com.tangem.domain.visa.datasource.TangemPayRemoteDataSource import com.tangem.domain.visa.error.VisaApiError import com.tangem.domain.visa.model.TangemPayAuthTokens @@ -56,7 +57,7 @@ internal class DefaultTangemPayRemoteDataSource @Inject constructor( authType = "customer_wallet", sessionId = sessionId, signature = signature, - messageFormat = "Tangem Pay wants to sign in with your account. Nonce: $nonce", + messageFormat = VisaUtilities.signWithNonceMessage(nonce), ), ).getOrThrow() }.map { response -> diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthMessageSignUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthMessageSignUseCase.kt index 36d3087190..033ffd4461 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthMessageSignUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthMessageSignUseCase.kt @@ -83,6 +83,7 @@ internal class WcEthMessageSignUseCase @AssistedInject constructor( object LegacySdkHelper { private const val ETH_MESSAGE_PREFIX = "\u0019Ethereum Signed Message:\n" + // TODO: [REDACTED_TASK_KEY] - Get this public function from Blockchain SDK fun prepareToSendMessageData(signedHash: ByteArray, hashToSign: ByteArray, walletManager: WalletManager): String = UnmarshalHelper.unmarshalSignatureExtended( signature = signedHash, diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/common/visa/VisaUtilities.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/common/visa/VisaUtilities.kt index fe41ac4f05..2ed1be037d 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/common/visa/VisaUtilities.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/common/visa/VisaUtilities.kt @@ -1,9 +1,15 @@ package com.tangem.domain.card.common.visa +import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.toKeccak import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.UnmarshalHelper import com.tangem.blockchain.common.derivation.DerivationStyle +import com.tangem.common.card.EllipticCurve import com.tangem.common.card.FirmwareVersion +import com.tangem.common.extensions.toDecompressedPublicKey +import com.tangem.common.extensions.toHexString import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.domain.models.scan.CardDTO private const val VISA_BATCH_START = "AE" @@ -11,15 +17,16 @@ private const val VISA_BATCH_START_2 = "FFFC" object VisaUtilities { - const val tokenId = "tether" - val visaBlockchain = Blockchain.Polygon val visaDefaultDerivationPath get() = visaBlockchain.derivationPath(DerivationStyle.V3) val customDerivationPath = DerivationPath("m/44'/60'/999999'/0/0") + val curve = EllipticCurve.Secp256k1 - fun visaDefaultDerivationPath(style: DerivationStyle) = visaBlockchain.derivationPath(style) + fun signWithNonceMessage(nonce: String): String { + return "Tangem Pay wants to sign in with your account. Nonce: $nonce" + } fun isVisaCard(card: CardDTO): Boolean { return isVisaCard(card.firmwareVersion.doubleValue, card.batchId) @@ -29,4 +36,26 @@ object VisaUtilities { return firmwareVersion in FirmwareVersion.visaRange && (batchId.startsWith(VISA_BATCH_START) || batchId.startsWith(VISA_BATCH_START_2)) } + + // TODO: [REDACTED_TASK_KEY] - Get this public function from Blockchain SDK + fun hashPersonalMessage(message: ByteArray): ByteArray { + val prefix = "\u0019Ethereum Signed Message:\n${message.size}".toByteArray() + return (prefix + message).toKeccak() + } + + fun generateAddressFromExtendedKey(extendedPublicKey: ExtendedPublicKey): String { + val derivationData = visaBlockchain.makeAddressesFromExtendedPublicKey( + extendedPublicKey = extendedPublicKey, + cachedIndex = null, + ) + return derivationData.address + } + + fun unmarshallSignature(signature: ByteArray, hash: ByteArray, extendedPublicKey: ExtendedPublicKey): String { + return UnmarshalHelper.unmarshalSignatureExtended( + signature = signature, + hash = hash, + publicKey = extendedPublicKey.publicKey.toDecompressedPublicKey(), + ).asRSVLegacyEVM().toHexString().lowercase() + } } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/WithdrawalSignatureResult.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/pay/WithdrawalSignatureResult.kt similarity index 83% rename from domain/visa/src/main/kotlin/com/tangem/domain/pay/model/WithdrawalSignatureResult.kt rename to domain/visa/models/src/main/kotlin/com/tangem/domain/pay/WithdrawalSignatureResult.kt index 42f55abd15..18e996d13d 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/WithdrawalSignatureResult.kt +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/pay/WithdrawalSignatureResult.kt @@ -1,4 +1,4 @@ -package com.tangem.domain.pay.model +package com.tangem.domain.pay sealed class WithdrawalSignatureResult { diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/datasource/TangemPayAuthDataSource.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/datasource/TangemPayAuthDataSource.kt index 66a037381f..4ab30e9ff7 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/datasource/TangemPayAuthDataSource.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/datasource/TangemPayAuthDataSource.kt @@ -1,12 +1,16 @@ package com.tangem.domain.pay.datasource import arrow.core.Either -import com.tangem.domain.pay.model.WithdrawalSignatureResult +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.pay.WithdrawalSignatureResult import com.tangem.domain.visa.model.TangemPayInitialCredentials interface TangemPayAuthDataSource { - suspend fun produceInitialCredentials(cardId: String): Either + suspend fun produceInitialCredentials(userWallet: UserWallet): Either - suspend fun getWithdrawalSignature(cardId: String, hash: String): Either + suspend fun getWithdrawalSignature( + userWallet: UserWallet, + hash: String, + ): Either } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/TangemPaySwapRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/TangemPaySwapRepository.kt index d27ac30af7..fed66aab7d 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/TangemPaySwapRepository.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/TangemPaySwapRepository.kt @@ -3,14 +3,14 @@ package com.tangem.domain.pay.repository import arrow.core.Either import com.tangem.core.error.UniversalError import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.pay.WithdrawalResult import java.math.BigDecimal interface TangemPaySwapRepository { suspend fun withdraw( - userWalletId: UserWalletId, + userWallet: UserWallet, receiverAddress: String, cryptoAmount: BigDecimal, cryptoCurrencyId: CryptoCurrency.RawID, diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt index 10a21fbb78..9636a5136b 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt @@ -21,9 +21,8 @@ private const val TAG = "TangemPayMainScreenCustomerInfoUseCase" * Works only if the user already authorised at least once (won't emit anything otherwise) */ class TangemPayMainScreenCustomerInfoUseCase( - private val repository: OnboardingRepository, + private val onboardingRepository: OnboardingRepository, private val customerOrderRepository: CustomerOrderRepository, - private val tangemPayOnboardingRepository: OnboardingRepository, private val eligibilityManager: TangemPayEligibilityManager, private val deviceSecurity: DeviceSecurityInfoProvider, ) { @@ -32,7 +31,7 @@ class TangemPayMainScreenCustomerInfoUseCase( field = MutableStateFlow(value = mapOf()) suspend fun fetch(userWalletId: UserWalletId) { - Timber.tag(TAG).i("fetch: $userWalletId") + Timber.tag(TAG).i("fetch: ${userWalletId.stringValue}") if (deviceSecurity.isSecurityExposed()) { Timber.tag(TAG).i("fetch security info: rooted: ${deviceSecurity.isRooted}") @@ -43,7 +42,7 @@ class TangemPayMainScreenCustomerInfoUseCase( return // fast exit } - repository.checkCustomerWallet(userWalletId) + onboardingRepository.checkCustomerWallet(userWalletId) .fold( ifLeft = { error -> Timber.tag(TAG).e("Failed checkCustomerWallet for $userWalletId: ${error.javaClass.simpleName}") @@ -64,7 +63,7 @@ class TangemPayMainScreenCustomerInfoUseCase( // if there's no tangem pay, check eligibility and show onboarding banner val isEligible = eligibilityManager.getEligibleWallets().any { it.walletId == userWalletId } if (isEligible) { - if (tangemPayOnboardingRepository.getHideMainOnboardingBanner(userWalletId)) { + if (onboardingRepository.getHideMainOnboardingBanner(userWalletId)) { updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left()) } else { updateState(userWalletId, MainCustomerInfoContentState.OnboardingBanner.right()) @@ -95,10 +94,10 @@ class TangemPayMainScreenCustomerInfoUseCase( private suspend fun proceedWithPaeraCustomerResult( userWalletId: UserWalletId, ): Either { - if (!tangemPayOnboardingRepository.isTangemPayInitialDataProduced(userWalletId)) { + if (!onboardingRepository.isTangemPayInitialDataProduced(userWalletId)) { return TangemPayCustomerInfoError.RefreshNeededError.left() } - val orderId = repository.getOrderId(userWalletId) + val orderId = onboardingRepository.getOrderId(userWalletId) return if (orderId != null) { proceedWithOrderId(userWalletId = userWalletId, orderId = orderId) } else { @@ -109,7 +108,7 @@ class TangemPayMainScreenCustomerInfoUseCase( private suspend fun proceedWithoutOrder( userWalletId: UserWalletId, ): Either { - return repository.getCustomerInfo(userWalletId) + return onboardingRepository.getCustomerInfo(userWalletId) .mapLeft { error -> Timber.tag(TAG).e("mapErrorForCustomer: $error") error.mapErrorForCustomer() @@ -118,7 +117,7 @@ class TangemPayMainScreenCustomerInfoUseCase( Timber.tag(TAG).i("customerInfo") if (customerInfo.cardInfo == null && customerInfo.isKycApproved) { // If order id wasn't saved -> start order creation and get customer info - repository.createOrder(userWalletId) + onboardingRepository.createOrder(userWalletId) } MainScreenCustomerInfo(info = customerInfo, orderStatus = OrderStatus.UNKNOWN) } @@ -148,10 +147,10 @@ class TangemPayMainScreenCustomerInfoUseCase( OrderStatus.CANCELED, OrderStatus.UNKNOWN, -> { - repository.clearOrderId(userWalletId) + onboardingRepository.clearOrderId(userWalletId) // If order was cancelled -> start order creation - if (orderStatus == OrderStatus.CANCELED) repository.createOrder(userWalletId) - repository.getCustomerInfo(userWalletId = userWalletId) + if (orderStatus == OrderStatus.CANCELED) onboardingRepository.createOrder(userWalletId) + onboardingRepository.getCustomerInfo(userWalletId = userWalletId) .mapLeft { it.mapErrorForCustomer() } .map { customerInfo -> MainScreenCustomerInfo(info = customerInfo, orderStatus = orderStatus) diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayWithdrawUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayWithdrawUseCase.kt index 910f50090c..e2abfd227f 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayWithdrawUseCase.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayWithdrawUseCase.kt @@ -3,14 +3,14 @@ package com.tangem.domain.tangempay import arrow.core.Either import com.tangem.core.error.UniversalError import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.pay.WithdrawalResult import java.math.BigDecimal interface TangemPayWithdrawUseCase { suspend operator fun invoke( - userWalletId: UserWalletId, + userWallet: UserWallet, cryptoAmount: BigDecimal, cryptoCurrencyId: CryptoCurrency.RawID, receiverCexAddress: String, diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index f4b0a59776..cbed111a44 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -845,7 +845,7 @@ internal class SwapModel @Inject constructor( private suspend fun processTangemPayWithdrawal(swapTransactionState: SwapTransactionState.TangemPayWithdrawalData) { tangemPayWithdrawUseCase( - userWalletId = userWalletId, + userWallet = userWallet, cryptoAmount = swapTransactionState.cryptoAmount, cryptoCurrencyId = swapTransactionState.cryptoCurrencyId, receiverCexAddress = swapTransactionState.cexAddress, diff --git a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt index bc39e6d47b..764ff69042 100644 --- a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt +++ b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt @@ -89,9 +89,7 @@ internal class TangemPayOnboardingModel @Inject constructor( private fun checkCustomerInfo(userWalletId: UserWalletId) { modelScope.launch { uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = true)) - repository.getCustomerInfo( - userWalletId = userWalletId, - ) + repository.getCustomerInfo(userWalletId = userWalletId) .onRight { customerInfo -> uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = false)) when { @@ -159,21 +157,20 @@ internal class TangemPayOnboardingModel @Inject constructor( uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = false)) return@launch } - repository.getCustomerInfo( - userWalletId = userWalletId, - ).fold( - ifLeft = { error -> - Timber.e("Error getCustomerInfo: ${error.errorCode}") - uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = false)) - }, - ifRight = { customerInfo -> - if (customerInfo.isKycApproved) { - back() - } else { - openKyc(userWalletId) - } - }, - ) + repository.getCustomerInfo(userWalletId = userWalletId) + .fold( + ifLeft = { error -> + Timber.e("Error getCustomerInfo: ${error.errorCode}") + uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = false)) + }, + ifRight = { customerInfo -> + if (customerInfo.isKycApproved) { + back() + } else { + openKyc(userWalletId) + } + }, + ) } } diff --git a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt index 628ecd897d..07a2d02773 100644 --- a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt +++ b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt @@ -2,6 +2,7 @@ package com.tangem.sdk.api import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import arrow.core.Either import com.tangem.Message import com.tangem.common.CompletionResult import com.tangem.common.KeyPair @@ -16,6 +17,7 @@ import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.WithdrawalSignatureResult import com.tangem.domain.visa.model.TangemPayInitialCredentials import com.tangem.domain.visa.model.VisaActivationInput import com.tangem.domain.visa.model.VisaDataForApprove @@ -159,8 +161,8 @@ interface TangemSdkManager { visaDataForApprove: VisaDataForApprove, ): CompletionResult - suspend fun tangemPayProduceInitialCredentials(cardId: String): CompletionResult + suspend fun tangemPayProduceInitialCredentials(cardId: String): Either - suspend fun getWithdrawalSignature(cardId: String, hash: String): CompletionResult + suspend fun getWithdrawalSignature(cardId: String, hash: String): Either // endregion } \ No newline at end of file