Updated on 2026-08-14
This commit is contained in:
parent
71e4d3a6ed
commit
0594e1b680
23 changed files with 298 additions and 208 deletions
|
|
@ -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<TangemPayInitialCredentials> {
|
||||
): Either<Throwable, TangemPayInitialCredentials> {
|
||||
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<TangemPayInitialCredentials> -> result.data.right()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getWithdrawalSignature(cardId: String, hash: String): CompletionResult<String> {
|
||||
override suspend fun getWithdrawalSignature(
|
||||
cardId: String,
|
||||
hash: String,
|
||||
): Either<Throwable, WithdrawalSignatureResult> {
|
||||
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<String> -> {
|
||||
WithdrawalSignatureResult.Success(result.data).right()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
|
|
|||
|
|
@ -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<TangemPayInitialCredentials> {
|
||||
): Either<Throwable, TangemPayInitialCredentials> {
|
||||
error("Not implemented")
|
||||
}
|
||||
|
||||
override suspend fun getWithdrawalSignature(cardId: String, hash: String): CompletionResult<String> {
|
||||
override suspend fun getWithdrawalSignature(
|
||||
cardId: String,
|
||||
hash: String,
|
||||
): Either<Throwable, WithdrawalSignatureResult> {
|
||||
error("Not implemented")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<TangemPayInitialCredentials> {
|
||||
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<ExtendedPublicKey> -> generateAddressFromExtendedKey(derivationResult.data)
|
||||
is CompletionResult.Success<ExtendedPublicKey> -> 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
|
||||
|
|
|
|||
|
|
@ -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<String>) {
|
||||
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<String>,
|
||||
) {
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<VisaSignedDataByCustomerWallet>,
|
||||
) {
|
||||
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<VisaSignedDataByCustomerWallet>,
|
||||
) {
|
||||
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<VisaSignedDataByCustomerWallet>,
|
||||
) {
|
||||
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))
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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<Throwable, TangemPayInitialCredentials> {
|
||||
return when (val initialCredentials = tangemSdkManager.tangemPayProduceInitialCredentials(cardId = cardId)) {
|
||||
is CompletionResult.Failure<*> -> initialCredentials.error.left()
|
||||
is CompletionResult.Success<TangemPayInitialCredentials> -> initialCredentials.data.right()
|
||||
override suspend fun produceInitialCredentials(
|
||||
userWallet: UserWallet,
|
||||
): Either<Throwable, TangemPayInitialCredentials> {
|
||||
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<Throwable, WithdrawalSignatureResult> {
|
||||
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<String> -> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Throwable, TangemPayInitialCredentials> =
|
||||
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<Throwable, WithdrawalSignatureResult> = withUnlockedHotWallet(hotWallet) { unlockHotWallet ->
|
||||
val signature = getSignature(
|
||||
unlockHotWallet = unlockHotWallet,
|
||||
hash = hash.hexToBytes(),
|
||||
extendedPublicKey = getExtendedPublicKey(unlockHotWallet = unlockHotWallet),
|
||||
)
|
||||
|
||||
WithdrawalSignatureResult.Success(signature)
|
||||
}
|
||||
|
||||
private suspend fun Raise<Throwable>.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<Throwable>.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 <Error, T> withUnlockedHotWallet(
|
||||
hotWallet: UserWallet.Hot,
|
||||
block: Raise<Error>.(UnlockHotWallet) -> T,
|
||||
): Either<Error, T> = either {
|
||||
try {
|
||||
val unlockHotWallet = hotWalletAccessor.getContextualUnlock(hotWallet.hotWalletId)
|
||||
?: hotWalletAccessor.unlockContextual(hotWallet.hotWalletId)
|
||||
block(unlockHotWallet)
|
||||
} finally {
|
||||
hotWalletAccessor.clearContextualUnlock(hotWallet.hotWalletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -80,9 +80,8 @@ internal interface TangemPayDataModule {
|
|||
deviceSecurity: DeviceSecurityInfoProvider,
|
||||
): TangemPayMainScreenCustomerInfoUseCase {
|
||||
return TangemPayMainScreenCustomerInfoUseCase(
|
||||
repository = repository,
|
||||
onboardingRepository = repository,
|
||||
customerOrderRepository = customerOrderRepository,
|
||||
tangemPayOnboardingRepository = tangemPayOnboardingRepository,
|
||||
eligibilityManager = eligibilityManager,
|
||||
deviceSecurity = deviceSecurity,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<UniversalError, WithdrawalResult> {
|
||||
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]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UniversalError, WithdrawalResult> {
|
||||
return repository.withdraw(
|
||||
userWalletId = userWalletId,
|
||||
userWallet = userWallet,
|
||||
cryptoAmount = cryptoAmount,
|
||||
receiverAddress = receiverCexAddress,
|
||||
cryptoCurrencyId = cryptoCurrencyId,
|
||||
|
|
|
|||
|
|
@ -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 ->
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.domain.pay.model
|
||||
package com.tangem.domain.pay
|
||||
|
||||
sealed class WithdrawalSignatureResult {
|
||||
|
||||
|
|
@ -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<Throwable, TangemPayInitialCredentials>
|
||||
suspend fun produceInitialCredentials(userWallet: UserWallet): Either<Throwable, TangemPayInitialCredentials>
|
||||
|
||||
suspend fun getWithdrawalSignature(cardId: String, hash: String): Either<Throwable, WithdrawalSignatureResult>
|
||||
suspend fun getWithdrawalSignature(
|
||||
userWallet: UserWallet,
|
||||
hash: String,
|
||||
): Either<Throwable, WithdrawalSignatureResult>
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<TangemPayCustomerInfoError, MainScreenCustomerInfo> {
|
||||
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<TangemPayCustomerInfoError, MainScreenCustomerInfo> {
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<VisaSignedDataByCustomerWallet>
|
||||
|
||||
suspend fun tangemPayProduceInitialCredentials(cardId: String): CompletionResult<TangemPayInitialCredentials>
|
||||
suspend fun tangemPayProduceInitialCredentials(cardId: String): Either<Throwable, TangemPayInitialCredentials>
|
||||
|
||||
suspend fun getWithdrawalSignature(cardId: String, hash: String): CompletionResult<String>
|
||||
suspend fun getWithdrawalSignature(cardId: String, hash: String): Either<Throwable, WithdrawalSignatureResult>
|
||||
// endregion
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue