Updated on 2026-08-14

This commit is contained in:
Tangem 2025-01-27 12:45:57 +03:00
parent 10bb769916
commit bb32839f9d
32 changed files with 521 additions and 77 deletions

View file

@ -27,6 +27,7 @@ import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.visa.model.VisaActivationInput
import com.tangem.domain.visa.model.VisaAuthChallenge
import com.tangem.domain.visa.model.VisaCardActivationResponse
import com.tangem.domain.visa.model.VisaDataForApprove
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.operations.ScanTask
@ -35,6 +36,7 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
import com.tangem.operations.pins.SetUserCodeCommand
import com.tangem.operations.preflightread.PreflightReadFilter
import com.tangem.operations.sign.SignHashResponse
import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
@ -45,6 +47,7 @@ import com.tangem.tap.domain.tasks.product.ResetBackupCardTask
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
import com.tangem.tap.domain.tasks.product.ScanProductTask
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
import com.tangem.tap.domain.tasks.visa.VisaCustomerWalletApproveTask
import com.tangem.tap.domain.twins.CreateFirstTwinWalletTask
import com.tangem.tap.domain.twins.CreateSecondTwinWalletTask
import com.tangem.tap.domain.twins.FinalizeTwinTask
@ -494,6 +497,17 @@ internal class DefaultTangemSdkManager(
}
}
override suspend fun visaCustomerWalletApprove(
visaDataForApprove: VisaDataForApprove,
): CompletionResult<SignHashResponse> {
return runTaskAsyncReturnOnMain(
runnable = VisaCustomerWalletApproveTask(
visaDataForApprove = visaDataForApprove,
),
initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)),
)
}
// endregion
companion object {

View file

@ -20,9 +20,11 @@ import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.visa.model.VisaActivationInput
import com.tangem.domain.visa.model.VisaAuthChallenge
import com.tangem.domain.visa.model.VisaCardActivationResponse
import com.tangem.domain.visa.model.VisaDataForApprove
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.preflightread.PreflightReadFilter
import com.tangem.operations.sign.SignHashResponse
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.sdk.api.TangemSdkManager
@ -207,4 +209,12 @@ class MockTangemSdkManager(
): CompletionResult<VisaCardActivationResponse> {
error("Not implemented")
}
override suspend fun visaCustomerWalletApprove(
visaDataForApprove: VisaDataForApprove,
): CompletionResult<SignHashResponse> {
error("Not implemented")
}
// endregion
}

View file

@ -311,15 +311,12 @@ private class ScanWalletProcessor(
}
private fun getWalletProductType(card: CardDTO): ProductType {
if (RING_BATCH_IDS.contains(card.batchId) || card.batchId.startsWith(RING_BATCH_PREFIX)) {
return ProductType.Ring
}
return if (card.firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available &&
card.settings.isKeysImportAllowed
) {
ProductType.Wallet2
} else {
ProductType.Wallet
return when {
card.isVisa -> ProductType.Visa
RING_BATCH_IDS.contains(card.batchId) || card.batchId.startsWith(RING_BATCH_PREFIX) -> ProductType.Ring
card.firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available &&
card.settings.isKeysImportAllowed -> ProductType.Wallet2
else -> ProductType.Wallet
}
}

View file

@ -1,12 +1,12 @@
package com.tangem.tap.domain.tasks.visa
import com.reown.util.hexToBytes
import com.tangem.common.CompletionResult
import com.tangem.common.card.Card
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.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.common.map
import com.tangem.datasource.local.visa.VisaAuthTokenStorage

View file

@ -0,0 +1,188 @@
package com.tangem.tap.domain.tasks.visa
import arrow.core.getOrElse
import com.tangem.common.CompletionResult
import com.tangem.common.card.Card
import com.tangem.common.card.CardWallet
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.hexToBytes
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.common.visa.VisaUtilities
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDerivation
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.visa.model.VisaActivationError
import com.tangem.domain.visa.model.VisaDataForApprove
import com.tangem.operations.ScanTask
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
import com.tangem.operations.sign.SignHashCommand
import com.tangem.operations.sign.SignHashResponse
class VisaCustomerWalletApproveTask(
private val visaDataForApprove: VisaDataForApprove,
) : CardSessionRunnable<SignHashResponse> {
override fun run(session: CardSession, callback: CompletionCallback<SignHashResponse>) {
val card = session.environment.card ?: run {
callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead()))
return
}
if (VisaUtilities.isVisaCard(card.firmwareVersion.doubleValue, card.batchId).not()) {
callback(CompletionResult.Failure(TangemSdkError.Underlying("Card is identified as not Visa")))
return
}
if (card.settings.isHDWalletAllowed) {
proceedApprove(card, session, callback)
} else {
proceedApproveWithLegacyCard(card, session, callback)
}
}
private fun proceedApprove(card: Card, session: CardSession, callback: CompletionCallback<SignHashResponse>) {
val cardDTO = CardDTO(card)
val derivationStyle = cardDTO.derivationStyleProvider.getDerivationStyle() ?: run {
proceedApproveWithLegacyCard(
card = card,
session = session,
callback = callback,
)
return
}
val derivationPath = VisaUtilities.visaDefaultDerivationPath(derivationStyle) ?: run {
callback(
CompletionResult.Failure(
TangemSdkError.Underlying("Failed to generate derivation path with provided derivation style"),
),
)
return
}
val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } ?: run {
callback(CompletionResult.Failure(TangemSdkError.Underlying(VisaActivationError.MissingWallet.message)))
return
}
val derivationTask = DeriveWalletPublicKeyTask(
walletPublicKey = wallet.publicKey,
derivationPath = derivationPath,
)
derivationTask.run(session) { result ->
when (result) {
is CompletionResult.Success -> {
processDerivedKey(
wallet = wallet,
extendedPublicKey = result.data,
derivationPath = derivationPath,
session = session,
callback = callback,
)
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(result.error))
}
}
}
}
private fun processDerivedKey(
wallet: CardWallet,
extendedPublicKey: ExtendedPublicKey,
derivationPath: DerivationPath,
session: CardSession,
callback: CompletionCallback<SignHashResponse>,
) {
val validationResult = VisaWalletPublicKeyUtility.validateExtendedPublicKey(
targetAddress = visaDataForApprove.targetAddress,
extendedPublicKey = extendedPublicKey,
)
validationResult.onLeft {
callback(CompletionResult.Failure(TangemSdkError.Underlying(it.message)))
return
}
signApproveData(
targetWalletPublicKey = wallet.publicKey,
derivationPath = derivationPath,
session = session,
callback = callback,
)
}
private fun proceedApproveWithLegacyCard(
card: Card,
session: CardSession,
callback: CompletionCallback<SignHashResponse>,
) {
val publicKey = findKeyWithoutDerivation(
targetAddress = visaDataForApprove.targetAddress,
card = CardDTO(card),
).getOrElse {
callback(CompletionResult.Failure(TangemSdkError.Underlying(it.message)))
return
}
signApproveData(
targetWalletPublicKey = publicKey,
derivationPath = null,
session = session,
callback = callback,
)
}
private fun signApproveData(
targetWalletPublicKey: ByteArray,
derivationPath: DerivationPath?,
session: CardSession,
callback: CompletionCallback<SignHashResponse>,
) {
val signTask = SignHashCommand(
hash = visaDataForApprove.approveHash.hexToBytes(),
walletPublicKey = targetWalletPublicKey,
derivationPath = derivationPath,
)
signTask.run(session) { result ->
when (result) {
is CompletionResult.Success -> {
scanCard(
signHashResponse = result.data,
session = session,
callback = callback,
)
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(result.error))
}
}
}
}
private fun scanCard(
signHashResponse: SignHashResponse,
session: CardSession,
callback: CompletionCallback<SignHashResponse>,
) {
val scanTask = ScanTask()
scanTask.run(session) { result ->
when (result) {
is CompletionResult.Success -> {
callback(CompletionResult.Success(signHashResponse))
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(result.error))
}
}
}
}
}